Welcome, guest | Sign In | My Account | Store | Cart

Here we will demonstrate a technique that will make it possible to semi-transparently proxy an object and override/proxy methods or data including special methods like __call__. this module will define several general tools and components that can be used as a basis for creation of various proxy classes...

Python, 329 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#=======================================================================

__version__ = '''0.1.01'''
__sub_version__ = '''20040919004511'''
__copyright__ = '''(c) Alex A. Naanou 2003'''


#-----------------------------------------------------------------------

__doc__ = '''\
this module will define a set of utilities and classes to be used to build
various proxies...
'''


#-----------------------------------------------------------------------

import new
import types
import weakref


#-----------------------------------------------------------------------
#------------------------------------------------------getproxytarget---
def getproxytarget(obj):
    '''
    this will return the unproxied target object.
    '''
    ogetattribute = object.__getattribute__
    try:
        return ogetattribute(obj, ogetattribute(obj, '__proxy_target_attr_name__'))
    except:
        if not isproxy(obj):
            raise TypeError, 'obj must be a proxy (got: %s).' % obj
        raise


#-------------------------------------------------------------isproxy---
def isproxy(obj):
    '''
    this will return True if obj is a proxy object (relative to AbstractProxy).

    NOTE: this will work only for the pli framework proxies that inherit 
          from AbstractProxy.
    '''
    return isinstance(obj, AbstractProxy)



#-----------------------------------------------------------------------
#-------------------------------------------------------AbstractProxy---
# this is here for:
#   1) use of isproxy.
#      it is helpful if all *similar* classes be a subclass of one
#      parent class for easy identification...
#   2) define the default configuration for use with the 'proxymethod'
#      and helper functions...
class AbstractProxy(object):
    '''
    this is a base class for all proxies...
    '''
    __proxy_target_attr_name__ = 'proxy_target'


#----------------------------------------------------------BasicProxy---
class BasicProxy(AbstractProxy):
    '''
    this defines a nice proxy repr mixin.
    '''
    def __repr__(self):
        '''
        '''
        ogetattribute = object.__getattribute__
        return '<%s proxy at %s to %s>' % (ogetattribute(self, '__class__').__name__, 
                                            hex(id(self)),
                                            repr(getproxytarget(self)))



#-----------------------------------------------------------------------
# this section defines component mix-ins...
#-----------------------------------------------------ComparibleProxy---
class ComparibleProxy(BasicProxy):
    '''
    proxy mixin. this will transfer the rich comparison calls directly 
    to the target...
    '''
    __proxy_target_attr_name__ = 'proxy_target'

    # these cant be avoided without eval...
    def __eq__(self, other):
        return getproxytarget(self) == other    
    def __ne__(self, other):
        return getproxytarget(self) != other
    def __gt__(self, other):
        return getproxytarget(self) > other 
    def __lt__(self, other):
        return getproxytarget(self) < other 
    def __ge__(self, other):
        return getproxytarget(self) >= other    
    def __le__(self, other):
        return getproxytarget(self) <= other    


#---------------------------------------------------------CachedProxy---
# NOTE: from here on all proxies are by default cached...
class CachedProxy(BasicProxy):
    '''
    this defaines the basic proxy cache manager functionality.
    '''
    # this may either be None or a dict-like (usualy a weakref.WeakKeyDictionary)
    # if None the proxy caching will be disabled
    __proxy_cache__ = None

    def __new__(cls, source, *p, **n):
        '''
        return the cached proxy or create a one and add it to cache.
        '''
        res = cls._getcached(source)
        if res == None and hasattr(cls, '__proxy_cache__') \
                and cls.__proxy_cache__ != None:
            obj = super(CachedProxy, cls).__new__(cls, source, *p, **n)
            cls._setcache(source, obj)
            return obj
        return super(CachedProxy, cls).__new__(cls, source, *p, **n)
    @classmethod
    def _getcached(cls, source):
        '''
        get an object from cache.
        if the object is not in cache or cache is disabled None will be returned.
        '''
        if hasattr(cls, '__proxy_cache__') and cls.__proxy_cache__ != None \
                and source in cls.__proxy_cache__:
            return cls.__proxy_cache__[source]
        return None
    @classmethod
    def _setcache(cls, source, obj):
        '''
        add an object to cache.
        '''
        if hasattr(cls, '__proxy_cache__') and cls.__proxy_cache__ != None:
            cls.__proxy_cache__[source] = obj

        

#-----------------------------------------------------------------------
# this section defines ready to use base proxies...
#---------------------------------------------InheritAndOverrideProxy---
# this is the Proxy cache...
_InheritAndOverrideProxy_cache = weakref.WeakKeyDictionary()
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# this works as follows:
# 1) create a class that inherits from InheritAndOverrideProxy and the
#    proxied objects class...
#    reference code:
#       class local_proxy(InheritAndOverrideProxy, proxied.__class__):
#           pass
# 2) creates an object from the above class, and sets its __dict__ to
#    reference the target objects __dict__. thus enabling setting and
#    referencing data to the proxied object....
#
class InheritAndOverrideProxy(CachedProxy):
    '''
    this is a general (semi-transparent) proxy.
    '''
    # this defines the attribute name where the proxy target is
    # stored...
    __proxy_target_attr_name__ = 'proxy_target'
    # this is used to generate unique proxy class names...
    __proxy_count__ = 0
    # this may either be None or a dict-like (usualy a weakref.WeakKeyDictionary)
    # if None the proxy caching will be disabled
    __proxy_cache__ = _InheritAndOverrideProxy_cache

    def __new__(cls, source, *p, **n):
        '''
        this will create a proxy, wrap the target and return the result... 
        else return the target.
        '''
        osetattr = object.__setattr__
        cls_name = cls.__name__
        try:
            # process proxy cache...
            _obj = cls._getcached(source)
            if _obj != None:
                return _obj
            # create an object of a class (also just created) inherited
            # from cls and source.__class__
            _obj = object.__new__(new.classobj('',(cls, source.__class__), {}))
            # get the new class....
            cls = object.__getattribute__(_obj, '__class__')
            # name the new class... 
            # NOTE: the name may not be unique!
            cls.__name__ = cls_name + '_' + str(cls.__proxy_count__)
            cls.__proxy_count__ += 1
            # considering that the class we just created is unique we
            # can use it as a data store... (and we do not want to
            # pollute the targets dict :) )
            setattr(cls, cls.__proxy_target_attr_name__, source)
            # replace the dict so that the proxy behaves exactly like
            # the target...
            osetattr(_obj, '__dict__', source.__dict__)
        # we fall here in case we either are a class constructor, function or a callable....
        # WARNING: this might be Python implementation specific!!
        except (TypeError, AttributeError), e:
            # function or callable
            if type(source) in (types.FunctionType, types.LambdaType, \
                                    types.MethodType, weakref.CallableProxyType):
                # callable wrapper hook...
                if hasattr(cls, '__proxy_call__') and cls.__proxy_call__ != None:
                    return cls.__proxy_call__(source)
                return source
            # class (nested class constructors...)
            elif callable(source):
                # class wrapper hook...
                if hasattr(cls, '__proxy_class__') and cls.__proxy_class__ != None:
                    return cls.__proxy_class__(source)
                return source
            return source
        # process proxy cache...
        cls._setcache(source, _obj)
        return _obj
    # this is here to define the minimal __init__ format...
    # WARNING: there is a danger to call the targets __init__ so
    #          keep this empty!!!
    def __init__(self, source, *p, **n):
        pass
    # these two methods are the optional function and class wrappers...
##  def __proxy_call__(self, target):
##      return target
##  def __proxy_class__(self, target):
##      return target


#-----------------------------------TranparentInheritAndOverrideProxy---
# this is the Proxy cache...
_TranparentInheritAndOverrideProxy_cache = weakref.WeakKeyDictionary() 
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class TranparentInheritAndOverrideProxy(InheritAndOverrideProxy, ComparibleProxy):
    '''
    this is a transparent variant of InheritAndOverrideProxy. its' behavior 
    is in no way different from the proxied object.

    NOTE: due to the fact that this explicitly proxies the __getattribute__ 
          and __setattr__ calls, it is slower then the semi-transparent 
          variant.
    '''
    __proxy_target_attr_name__ = 'proxy_target'
    __proxy_count__ = 0
    __proxy_cache__ = _TranparentInheritAndOverrideProxy_cache
    # this defines the attributes that are resolved to the proxy itself
    # (not the target object)...
    __proxy_public_attrs__ = (
                '__proxy_call__',
                '__proxy_class__',
            )

    def __getattribute__(self, name):
        '''
        '''
        ogetattribute = object.__getattribute__
        if name in ogetattribute(self, '__proxy_public_attrs__') \
                + (ogetattribute(self, '__proxy_target_attr_name__'),):
            return super(TranparentInheritAndOverrideProxy, self).__getattribute__(name)
        return self.proxy_target.__getattribute__(name)



#=======================================================================
if __name__ == '__main__':
    # Examples:
    #
    # here we create a class we will use as a target for our proxy...
    # this will define same special methods, normal methods and
    # attributes (both class and object)...
    class O(object):
        class_attr = 'some value...'
        def __init__(self):
            self.obj_attr = 1234567
        def __call__(self):
            print 'O object (__call__)! (', self.__class__, hex(id(self)), ').'
        def meth(self, arg):
            print 'O object (meth)! (', self.__class__, hex(id(self)), ').'
    # create an instance of the above...
    o = O()
    # now the fun starts..
    # we define a proxy that will intercept calls to the target object.
    class Proxy(TranparentInheritAndOverrideProxy):
        def __call__(self, *p, **n):
            print 'Proxy:\n\t',
            self.proxy_target(*p, **n)
    # bind a proxy to the target...
    p = Proxy(o)
    # call the original...
    o()
    # call the proxy...
    p()
    # raw access attributes...
    print p.obj_attr
    print p.class_attr
    # set attributes via the proxy...
    p.xxx = 'xxx value...'
    p.obj_attr = 7654321
    # access new values attributes...
    print o.xxx
    print o.obj_attr
    print o.class_attr
    # print the class of the proxy and the target...
    print 'o.__class__ is', o.__class__
    print 'p.__class__ is', p.__class__
    # compare the two...
    print 'o == p ->', o == p
    # isproxy tests...
    print 'p is a proxy test ->', isproxy(p)
    print 'o is a proxy test ->', isproxy(o)
    # print a nice repr...
    print 'o is', o
    print 'p is', p
    # now we test the cache...
    # create several proxies to the same object....
    p0 = Proxy(o)
    p1 = Proxy(o)
    # test if they are the same...
    print p is p0, p0 is p1



#=======================================================================
#                                   vim:set ts=4 sw=4 nowrap expandtab:

the goals here were: 1) speed -- the proxy should introduce minimal overhead to non-proxied functionality. 2) transparency -- the surroundings should not notice the presence of the proxy.

the way this works is when we wrap an object with the proxy class, this class actually does the following: - create a hybrid class which subclasses both the proxy and the objects class. this will enable attribute and method access via the default python MRO mechanisms... - create an object of this new class and replace its __dict__ with the target objects (e.g. the two objects will share the same state).

the above two actions are done in the InheritAndOverrideProxy class. this makes for fast attribute access as the same mechanisms are used as in any other python object while maintaining some transparency. but here goal #2 is not fully accomplished, this is because the proxy classes attributes override the target classes (e.g. the proxies __class__ attribute resolves to the combination class not the targets class). and the comparison of the proxy will differ from the original.

to solve the comparison problem one could use the ComparibleProxy as a mixin. but the only way (at least that I could think of :) ) to solve the class attribute problem is to use __getattribute__ which is relatively straight forward and shown in the TranparentInheritAndOverrideProxy class (the payoff here is the speed drop due to the use of __getattribute__).

NOTE: all the proxies here are cached, enabling reuse of proxies... NOTE: all proxies here are single level proxies. in some cases one would need a recursive proxy which is quite easy to make using the __getattribute__ to wrap the next node... NOTE: this can be easily used with Py2.3... just replace the decorators with the traditional classmethod dance... (not tested with python versions <2.2).

for a full version of this module and a recursive proxy example see the pli.pattern.proxy.generic module in the pli python library (http://pli.sf.net/)