proxytypes3 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .proxies import *
2
+
3
+ VERSION = (1, 0, 2)
4
+ __version__ = '.'.join(map(str, VERSION))
proxytypes3/proxies.py ADDED
@@ -0,0 +1,199 @@
1
+ class AbstractProxy:
2
+ """
3
+ Delegates all operations (except ``.__subject__``) to another object.
4
+ """
5
+
6
+ __slots__ = ()
7
+
8
+ def __call__(self, *args, **kw):
9
+ return self.__subject__(*args, **kw)
10
+
11
+ def __getattribute__(self, attr, oga=object.__getattribute__):
12
+ subject = oga(self, '__subject__')
13
+ if attr == '__subject__':
14
+ return subject
15
+ return getattr(subject, attr)
16
+
17
+ def __setattr__(self, attr, val, osa=object.__setattr__):
18
+ if attr == '__subject__':
19
+ osa(self, attr, val)
20
+ else:
21
+ setattr(self.__subject__, attr, val)
22
+
23
+ def __delattr__(self, attr, oda=object.__delattr__):
24
+ if attr == '__subject__':
25
+ oda(self, attr)
26
+ else:
27
+ delattr(self.__subject__, attr)
28
+
29
+ if hasattr(int, '__nonzero__'):
30
+ def __nonzero__(self):
31
+ return bool(self.__subject__)
32
+
33
+ def __getitem__(self, arg):
34
+ return self.__subject__[arg]
35
+
36
+ def __setitem__(self, arg, val):
37
+ self.__subject__[arg] = val
38
+
39
+ def __delitem__(self, arg):
40
+ del self.__subject__[arg]
41
+
42
+ def __getslice__(self, i, j):
43
+ return self.__subject__[i:j]
44
+
45
+ def __setslice__(self, i, j, val):
46
+ self.__subject__[i:j] = val
47
+
48
+ def __delslice__(self, i, j):
49
+ del self.__subject__[i:j]
50
+
51
+ def __contains__(self, ob):
52
+ return ob in self.__subject__
53
+
54
+ for name in 'repr str hash len abs complex int long float iter oct hex bool operator.index math.trunc'.split():
55
+ if name in ('len', 'complex') or hasattr(int, f'__{name.split(".")[-1]}__'):
56
+ if '.' in name:
57
+ name = name.split('.')
58
+ exec(f"global {name[1]}; from {name[0]} import {name[1]}")
59
+ name = name[1]
60
+ exec(f"def __{name}__(self): return {name}(self.__subject__)")
61
+
62
+ for name in 'cmp', 'coerce', 'divmod':
63
+ if hasattr(int, f'__{name}__'):
64
+ exec(f"def __{name}__(self,ob): return {name}(self.__subject__,ob)")
65
+
66
+ for name, op in [
67
+ ('lt', '<'), ('gt', '>'), ('le', '<='), ('ge', '>='),
68
+ ('eq', '=='), ('ne', '!=')
69
+ ]:
70
+ exec(f"def __{name}__(self,ob): return self.__subject__ {op} ob")
71
+
72
+ for name, op in [('neg', '-'), ('pos', '+'), ('invert', '~')]:
73
+ exec(f"def __{name}__(self): return {op} self.__subject__")
74
+
75
+ for name, op in [
76
+ ('or', '|'), ('and', '&'), ('xor', '^'), ('lshift', '<<'), ('rshift', '>>'),
77
+ ('add', '+'), ('sub', '-'), ('mul', '*'), ('div', '/'), ('mod', '%'),
78
+ ('truediv', '/'), ('floordiv', '//')
79
+ ]:
80
+ if name == 'div' and not hasattr(int, '__div__'):
81
+ continue
82
+ exec((
83
+ f"def __{name}__(self, ob):\n"
84
+ f" return self.__subject__ {op} ob\n"
85
+ "\n"
86
+ f"def __r{name}__(self, ob):\n"
87
+ f" return ob {op} self.__subject__\n"
88
+ "\n"
89
+ f"def __i{name}__(self, ob):\n"
90
+ f" self.__subject__ {op}=ob\n"
91
+ " return self\n"
92
+ ))
93
+
94
+ del name, op
95
+
96
+ # Oddball signatures
97
+
98
+ def __rdivmod__(self, ob):
99
+ return divmod(ob, self.__subject__)
100
+
101
+ def __pow__(self, *args): # pylint: disable=unexpected-special-method-signature
102
+ # Deliberately variadic: pow(subject), pow(subject, exp), pow(subject, exp, mod).
103
+ return pow(self.__subject__, *args)
104
+
105
+ def __ipow__(self, ob):
106
+ self.__subject__ **= ob # pylint: disable=assigning-non-slot
107
+ return self
108
+
109
+ def __rpow__(self, ob):
110
+ return pow(ob, self.__subject__)
111
+
112
+
113
+ class ObjectProxy(AbstractProxy):
114
+ """
115
+ Proxy for a specific object.
116
+ """
117
+
118
+ __slots__ = ('__subject__',)
119
+
120
+ def __init__(self, subject):
121
+ self.__subject__ = subject
122
+
123
+
124
+ class CallbackProxy(AbstractProxy):
125
+ """
126
+ Proxy for a dynamically-chosen object.
127
+ """
128
+
129
+ __slots__ = ('__callback__',)
130
+
131
+ def __init__(self, func):
132
+ set_callback(self, func)
133
+
134
+ get_callback = CallbackProxy.__callback__.__get__
135
+ set_callback = CallbackProxy.__callback__.__set__
136
+ CallbackProxy.__subject__ = property(lambda self, gc=get_callback: gc(self)())
137
+
138
+
139
+ class LazyProxy(CallbackProxy):
140
+ """
141
+ Proxy for a lazily-obtained object, that is cached on first use.
142
+ """
143
+ __slots__ = ('__cache__',)
144
+
145
+ get_cache = LazyProxy.__cache__.__get__
146
+ set_cache = LazyProxy.__cache__.__set__
147
+
148
+ def __subject__(self, get_cache=get_cache, set_cache=set_cache): # pylint: disable=redefined-outer-name
149
+ try:
150
+ return get_cache(self)
151
+ except AttributeError:
152
+ set_cache(self, get_callback(self)())
153
+ return get_cache(self)
154
+
155
+ LazyProxy.__subject__ = property(__subject__, set_cache)
156
+ del __subject__
157
+
158
+
159
+ class AbstractWrapper(AbstractProxy):
160
+ """
161
+ Mixin to allow extra behaviors and attributes on proxy instance.
162
+ """
163
+
164
+ __slots__ = ()
165
+
166
+ def __getattribute__(self, attr, oga=object.__getattribute__):
167
+ if attr.startswith('__'):
168
+ subject = oga(self, '__subject__')
169
+ if attr == '__subject__':
170
+ return subject
171
+ return getattr(subject, attr)
172
+ return oga(self, attr)
173
+
174
+ def __getattr__(self, attr, oga=object.__getattribute__):
175
+ return getattr(oga(self, '__subject__'), attr)
176
+
177
+ def __setattr__(self, attr, val, osa=object.__setattr__):
178
+ if attr == '__subject__' or hasattr(type(self), attr) and not attr.startswith('__'):
179
+ osa(self, attr, val)
180
+ else:
181
+ setattr(self.__subject__, attr, val)
182
+
183
+ def __delattr__(self, attr, oda=object.__delattr__):
184
+ if attr == '__subject__' or hasattr(type(self), attr) and not attr.startswith('__'):
185
+ oda(self, attr)
186
+ else:
187
+ delattr(self.__subject__, attr)
188
+
189
+
190
+ class ObjectWrapper(ObjectProxy, AbstractWrapper):
191
+ __slots__ = ()
192
+
193
+
194
+ class CallbackWrapper(CallbackProxy, AbstractWrapper):
195
+ __slots__ = ()
196
+
197
+
198
+ class LazyWrapper(LazyProxy, AbstractWrapper):
199
+ __slots__ = ()
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.4
2
+ Name: proxytypes3
3
+ Version: 1.0.2
4
+ Summary: General purpose proxy and wrapper types.
5
+ Home-page: https://github.com/chrisspen/ProxyTypes3
6
+ Author: Chris Spencer
7
+ Author-email: chrisspen@gmail.com
8
+ License: MIT
9
+ Classifier: Development Status :: 6 - Mature
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: license-file
28
+ Dynamic: summary
29
+
30
+ Simple Proxy Types
31
+ ==================
32
+
33
+ [![](https://img.shields.io/pypi/v/proxytypes3.svg)](https://pypi.python.org/pypi/proxytypes3) [![CI](https://github.com/chrisspen/proxytypes3/actions/workflows/ci.yml/badge.svg)](https://github.com/chrisspen/proxytypes3/actions/workflows/ci.yml)
34
+
35
+ The `proxytypes3` package provides some useful base classes for creating
36
+ proxies and wrappers for ordinary Python objects. Proxy objects automatically
37
+ delegate all attribute access and operations to the proxied object. Wrappers
38
+ are similar, but can be subclassed to allow additional attributes and
39
+ operations to be added to the wrapped object.
40
+
41
+ Note that these proxy types are not intended to be tamper-proof; the unproxied
42
+ form of an object can be readily accessed using a proxy's `__subject__`
43
+ attribute, and some proxy types even allow this attribute to be set. (This can
44
+ be handy for algorithms that lazily create circular structures and thus need to
45
+ be able to hand out "forward reference" proxies.)
46
+
47
+ This package is a fork of the [peak.util.proxies](https://pypi.org/project/ProxyTypes/) module,
48
+ extended to include support for Python 3.* as well as better testing and continuous integration.
49
+
50
+ Proxy Basics
51
+ ------------
52
+
53
+ Here's a quick demo of the `ObjectProxy` type::
54
+
55
+ >>> from proxytypes3 import ObjectProxy
56
+ >>> p = ObjectProxy(42)
57
+
58
+ >>> p
59
+ 42
60
+
61
+ >>> isinstance(p, int)
62
+ True
63
+
64
+ >>> p.__class__
65
+ <... 'int'>
66
+
67
+ >>> p*2
68
+ 84
69
+
70
+ >>> 'X' * p
71
+ 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
72
+
73
+ >>> hex(p)
74
+ '0x2a'
75
+
76
+ >>> chr(p)
77
+ '*'
78
+
79
+ >>> p ^ 1
80
+ 43
81
+
82
+ >>> p ** 2
83
+ 1764
84
+
85
+
86
+ As you can see, a proxy is virtually indistinguishable from the object it
87
+ proxies, except via its `__subject__` attribute, and its `type()`::
88
+
89
+ >>> p.__subject__
90
+ 42
91
+
92
+ >>> type(p)
93
+ <class 'proxytypes3.proxies.ObjectProxy'>
94
+
95
+ You can change the `__subject__` of an `ObjectProxy`, and it will then
96
+ refer to something else::
97
+
98
+ >>> p.__subject__ = 99
99
+ >>> p
100
+ 99
101
+ >>> p-33
102
+ 66
103
+
104
+ >>> p.__subject__ = "foo"
105
+ >>> p
106
+ 'foo'
107
+
108
+ All operations are delegated to the subject, including setattr and delattr::
109
+
110
+ >>> class Dummy: pass
111
+ >>> d = Dummy()
112
+ >>> p = ObjectProxy(d)
113
+
114
+ >>> p.foo = "bar"
115
+ >>> d.foo
116
+ 'bar'
117
+
118
+ >>> del p.foo
119
+ >>> hasattr(d,'foo')
120
+ False
121
+
122
+
123
+ Callback Proxies
124
+ ----------------
125
+
126
+ Sometimes, you may want a proxy's subject to be determined dynamically whenever
127
+ the proxy is used. For this purpose, you can use the `CallbackProxy` type,
128
+ which accepts a callback function and creates a proxy that will invoke the
129
+ callback in order to get the target. Here's a quick example of a counter
130
+ that gets incremented each time it's used, from zero to three::
131
+
132
+ >>> from proxytypes3 import CallbackProxy
133
+
134
+ >>> ct = -1
135
+ >>> def callback():
136
+ ... global ct
137
+ ... if ct == 3: raise StopIteration
138
+ ... ct += 1
139
+ ... return ct
140
+
141
+ >>> counter = CallbackProxy(callback)
142
+
143
+ >>> counter
144
+ 0
145
+ >>> counter
146
+ 1
147
+ >>> str(counter)
148
+ '2'
149
+ >>> hex(counter)
150
+ '0x3'
151
+
152
+ >>> counter
153
+ Traceback (most recent call last):
154
+ ...
155
+ StopIteration
156
+
157
+ As you can see, the callback is automatically invoked on any attempt to use the
158
+ proxy. This is a somewhat silly example; a better one would be something like
159
+ a `thread_id` proxy that is always equal to the ID # of the thread it's
160
+ running in.
161
+
162
+ A callback proxy's callback can be obtained or changed via the `get_callback`
163
+ and `set_callback` functions::
164
+
165
+ >>> from proxytypes3 import get_callback, set_callback
166
+ >>> set_callback(counter, lambda: 42)
167
+
168
+ >>> counter
169
+ 42
170
+
171
+ >>> get_callback(counter)
172
+ <function <lambda> at ...>
173
+
174
+
175
+ Lazy Proxies
176
+ ------------
177
+
178
+ A `LazyProxy` is similar to a `CallbackProxy`, but its callback is called
179
+ at most once, and then cached::
180
+
181
+ >>> from proxytypes3 import LazyProxy
182
+
183
+ >>> def callback():
184
+ ... print("called!")
185
+ ... return 42
186
+
187
+ >>> lazy = LazyProxy(callback)
188
+ >>> lazy
189
+ called!
190
+ 42
191
+ >>> lazy
192
+ 42
193
+
194
+ You can use the `get_callback` and `set_callback` functions on lazy
195
+ proxies, but it has no effect if the callback was already called::
196
+
197
+ >>> set_callback(lazy, lambda: 99)
198
+ >>> lazy
199
+ 42
200
+
201
+ But you can use the `get_cache` and `set_cache` functions to tamper with
202
+ the cached value::
203
+
204
+ >>> from proxytypes3 import get_cache, set_cache
205
+ >>> get_cache(lazy)
206
+ 42
207
+ >>> set_cache(lazy, 99)
208
+ >>> lazy
209
+ 99
210
+
211
+
212
+ Wrappers
213
+ --------
214
+
215
+ The `ObjectWrapper`, `CallbackWrapper` and `LazyWrapper` classes are
216
+ similar to their proxy counterparts, except that they are intended to be
217
+ subclassed in order to add custom extra attributes or methods. Any attribute
218
+ that exists in a subclass of these classes will be read or written from the
219
+ wrapper instance, instead of the wrapped object. For example::
220
+
221
+ >>> from proxytypes3 import ObjectWrapper
222
+ >>> class NameWrapper(ObjectWrapper):
223
+ ... name = None
224
+ ... def __init__(self, ob, name):
225
+ ... ObjectWrapper.__init__(self, ob)
226
+ ... self.name = name
227
+ ... def __str__(self):
228
+ ... return self.name
229
+
230
+ >>> w = NameWrapper(42, "The Ultimate Answer")
231
+ >>> w
232
+ 42
233
+
234
+ >>> print(w)
235
+ The Ultimate Answer
236
+
237
+ >>> w * 2
238
+ 84
239
+
240
+ >>> w.name
241
+ 'The Ultimate Answer'
242
+
243
+ Notice that any attributes you add must be defined *in the class*. You can't
244
+ add arbitary attributes at runtime, because they'll be set on the wrapped
245
+ object instead of the wrapper::
246
+
247
+ >>> w.foo = 'bar'
248
+ Traceback (most recent call last):
249
+ ...
250
+ AttributeError: 'int' object has no attribute 'foo'
251
+
252
+ Note that this means that all instance attributes must be implemented as either
253
+ slots, properties, or have a default value defined in the class body (like the
254
+ `name = None` shown in the example above).
255
+
256
+ The `CallbackWrapper` and `LazyWrapper` base classes are basically the same
257
+ as `ObjectWrapper`, except that they use a callback or cached lazy callback
258
+ instead of expecting an object as their subject.
259
+
260
+
261
+ Creating Custom Subclasses and Mixins
262
+ -------------------------------------
263
+
264
+ In addition to all the concrete classes described above, there are also two
265
+ abstract base classes: `AbstractProxy` and `AbstractWrapper`. If you want
266
+ to create a mixin type that can be used with any of the concrete types, you
267
+ should subclass the abstract version and set `__slots__` to an empty list::
268
+
269
+ >>> from proxytypes3 import AbstractWrapper
270
+
271
+ >>> class NamedMixin(AbstractWrapper):
272
+ ... __slots__ = []
273
+ ... name = None
274
+ ... def __init__(self, ob, name):
275
+ ... super(NamedMixin, self).__init__(ob)
276
+ ... self.name = name
277
+ ... def __str__(self):
278
+ ... return self.name
279
+
280
+ Then, when you mix it in with the respective base class, you can add back in
281
+ any necessary slots, or leave off `__slots__` to give the subclass instances
282
+ a dictionary of their own::
283
+
284
+ >>> from proxytypes3 import CallbackWrapper, LazyWrapper
285
+
286
+ >>> class NamedObject(NamedMixin, ObjectWrapper): pass
287
+ >>> class NamedCallback(NamedMixin, CallbackWrapper): pass
288
+ >>> class NamedLazy(NamedMixin, LazyWrapper): pass
289
+
290
+ >>> print(NamedObject(42, "The Answer"))
291
+ The Answer
292
+
293
+ >>> n = NamedCallback(callback, "Test")
294
+ >>> n
295
+ called!
296
+ 42
297
+ >>> n
298
+ called!
299
+ 42
300
+
301
+ >>> n = NamedLazy(callback, "Once")
302
+ >>> n
303
+ called!
304
+ 42
305
+ >>> n
306
+ 42
307
+
308
+ Both the `AbstractProxy` and `AbstractWrapper` base classes work by
309
+ assuming that `self.__subject__` will be the wrapped or proxied object. If
310
+ you don't want to use any of the standard three ways of defining
311
+ `__subject__` (i.e., as an object, callback, or lazy callback), you will need
312
+ to subclass `AbstractProxy` or `AbstractWrapper` and provide your own
313
+ way of defining `__subject__`.
314
+
315
+ Tests
316
+ -----
317
+
318
+ To run unittests across multiple Python versions, first install the necessary Python versions. On Ubuntu, run:
319
+
320
+ sudo add-apt-repository ppa:deadsnakes/ppa
321
+ sudo apt-get update
322
+ sudo apt-get install python-dev python3.4-minimal python3.4-dev python3.5-minimal python3.5-dev python3.6 python3.6-dev
323
+
324
+ Then to run all [tests](http://tox.readthedocs.org/en/latest/):
325
+
326
+ tox
327
+
328
+ To run tests for a specific environment (e.g. Python 2.7):
329
+
330
+ tox -e py27
331
+
332
+ To run a specific test:
333
+
334
+ export TESTNAME=.additional_tests; tox -e py34
335
+
336
+ export TESTNAME=.TestObjectProxy.testNumbers; tox -e py36
@@ -0,0 +1,7 @@
1
+ proxytypes3/__init__.py,sha256=iStQKZVmXdLAzxIev91bdFyg1tEifX_54r7FRnP9dXE,86
2
+ proxytypes3/proxies.py,sha256=iTdkI6eZOmWIw9OlEzLroTWnrKlPDzlhJABwuUIcbsQ,5953
3
+ proxytypes3-1.0.2.dist-info/licenses/LICENSE,sha256=pLlF16xT90vKyzvgdlRdmk9ORfgGw1GGopRpRvFXDEo,1070
4
+ proxytypes3-1.0.2.dist-info/METADATA,sha256=QjnCCHjppAQBVLSohhvdsjY45J7fp5fkWJUHQsB00Ak,9346
5
+ proxytypes3-1.0.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
+ proxytypes3-1.0.2.dist-info/top_level.txt,sha256=J4eEVvN3R1hlMSO_OIHu7YPQjHPyB1QOa5t5xr24iqk,12
7
+ proxytypes3-1.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Chris Spencer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ proxytypes3