cs-obj 20241005__tar.gz
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.
- cs_obj-20241005/MANIFEST.in +1 -0
- cs_obj-20241005/PKG-INFO +329 -0
- cs_obj-20241005/README.md +306 -0
- cs_obj-20241005/lib/python/cs/obj.py +549 -0
- cs_obj-20241005/lib/python/cs.obj.egg-info/PKG-INFO +329 -0
- cs_obj-20241005/lib/python/cs.obj.egg-info/SOURCES.txt +9 -0
- cs_obj-20241005/lib/python/cs.obj.egg-info/dependency_links.txt +1 -0
- cs_obj-20241005/lib/python/cs.obj.egg-info/requires.txt +2 -0
- cs_obj-20241005/lib/python/cs.obj.egg-info/top_level.txt +1 -0
- cs_obj-20241005/pyproject.toml +360 -0
- cs_obj-20241005/setup.cfg +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include README.md
|
cs_obj-20241005/PKG-INFO
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cs.obj
|
|
3
|
+
Version: 20241005
|
|
4
|
+
Summary: Convenience facilities for objects.
|
|
5
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
6
|
+
License: GNU General Public License v3 or later (GPLv3+)
|
|
7
|
+
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
|
|
8
|
+
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
|
|
9
|
+
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
|
|
10
|
+
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/obj.py
|
|
11
|
+
Keywords: python2,python3
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 2
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: cs.deco>=20241003
|
|
22
|
+
Requires-Dist: cs.py3>=20220523
|
|
23
|
+
|
|
24
|
+
Convenience facilities for objects.
|
|
25
|
+
|
|
26
|
+
*Latest release 20241005*:
|
|
27
|
+
New public_subclasses(cls) returning all subclasses with public names.
|
|
28
|
+
|
|
29
|
+
## <a name="as_dict"></a>`as_dict(o, selector=None)`
|
|
30
|
+
|
|
31
|
+
Return a dictionary with keys mapping to the values of the attributes of `o`.
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
* `o`: the object to map
|
|
35
|
+
* `selector`: the optional selection criterion
|
|
36
|
+
|
|
37
|
+
If `selector` is omitted or `None`, select "public" attributes,
|
|
38
|
+
those not commencing with an underscore.
|
|
39
|
+
|
|
40
|
+
If `selector` is a `str`, select attributes starting with `selector`.
|
|
41
|
+
|
|
42
|
+
Otherwise presume `selector` is callable
|
|
43
|
+
and select attributes `attr` where `selector(attr)` is true.
|
|
44
|
+
|
|
45
|
+
## <a name="copy"></a>`copy(obj, *a, **kw)`
|
|
46
|
+
|
|
47
|
+
Convenient function to shallow copy an object with simple modifications.
|
|
48
|
+
|
|
49
|
+
Performs a shallow copy of `self` using copy.copy.
|
|
50
|
+
|
|
51
|
+
Treat all positional parameters as attribute names, and
|
|
52
|
+
replace those attributes with shallow copies of the original
|
|
53
|
+
attribute.
|
|
54
|
+
|
|
55
|
+
Treat all keyword arguments as (attribute,value) tuples and
|
|
56
|
+
replace those attributes with the supplied values.
|
|
57
|
+
|
|
58
|
+
## <a name="flavour"></a>`flavour(obj)`
|
|
59
|
+
|
|
60
|
+
Return constants indicating the ``flavour'' of an object:
|
|
61
|
+
* `T_MAP`: DictType, DictionaryType, objects with an __keys__ or keys attribute.
|
|
62
|
+
* `T_SEQ`: TupleType, ListType, objects with an __iter__ attribute.
|
|
63
|
+
* `T_SCALAR`: Anything else.
|
|
64
|
+
|
|
65
|
+
## <a name="O"></a>Class `O(types.SimpleNamespace)`
|
|
66
|
+
|
|
67
|
+
The `O` class is now obsolete, please subclass `types.SimpleNamespace`
|
|
68
|
+
or use a dataclass.
|
|
69
|
+
|
|
70
|
+
## <a name="O_attritems"></a>`O_attritems(o)`
|
|
71
|
+
|
|
72
|
+
Generator yielding `(attr,value)` for relevant attributes of `o`.
|
|
73
|
+
|
|
74
|
+
## <a name="O_attrs"></a>`O_attrs(o)`
|
|
75
|
+
|
|
76
|
+
Yield attribute names from `o` which are pertinent to `O_str`.
|
|
77
|
+
|
|
78
|
+
Note: this calls `getattr(o,attr)` to inspect it in order to
|
|
79
|
+
prune callables.
|
|
80
|
+
|
|
81
|
+
## <a name="O_merge"></a>`O_merge(o, _conflict=None, _overwrite=False, **kw)`
|
|
82
|
+
|
|
83
|
+
Merge key:value pairs from a mapping into an object.
|
|
84
|
+
|
|
85
|
+
Ignore keys that do not start with a letter.
|
|
86
|
+
New attributes or attributes whose values compare equal are
|
|
87
|
+
merged in. Unequal values are passed to:
|
|
88
|
+
|
|
89
|
+
_conflict(o, attr, old_value, new_value)
|
|
90
|
+
|
|
91
|
+
to resolve the conflict. If _conflict is omitted or None
|
|
92
|
+
then the new value overwrites the old if _overwrite is true.
|
|
93
|
+
|
|
94
|
+
## <a name="O_str"></a>`O_str(o, no_recurse=False, seen=None)`
|
|
95
|
+
|
|
96
|
+
Return a `str` representation of the object `o`.
|
|
97
|
+
|
|
98
|
+
Parameters:
|
|
99
|
+
* `o`: the object to describe.
|
|
100
|
+
* `no_recurse`: if true, do not recurse into the object's structure.
|
|
101
|
+
Default: `False`.
|
|
102
|
+
* `seen`: a set of previously sighted objects
|
|
103
|
+
to prevent recursion loops.
|
|
104
|
+
|
|
105
|
+
## <a name="obj_as_dict"></a>`obj_as_dict(o, **kw)`
|
|
106
|
+
|
|
107
|
+
OBSOLETE obj_as_dict
|
|
108
|
+
|
|
109
|
+
OBSOLETE convesion of an object to a `dict`. Please us `cs.obj.as_dict`.
|
|
110
|
+
|
|
111
|
+
## <a name="Proxy"></a>Class `Proxy`
|
|
112
|
+
|
|
113
|
+
An extremely simple proxy object
|
|
114
|
+
that passes all unmatched attribute accesses to the proxied object.
|
|
115
|
+
|
|
116
|
+
Note that setattr and delattr work directly on the proxy, not the proxied object.
|
|
117
|
+
|
|
118
|
+
## <a name="public_subclasses"></a>`public_subclasses(cls)`
|
|
119
|
+
|
|
120
|
+
Return a list of the subclasses of `cls` which has public names.
|
|
121
|
+
|
|
122
|
+
## <a name="Sentinel"></a>Class `Sentinel`
|
|
123
|
+
|
|
124
|
+
A simple class for named sentinels whose `str()` is just the name
|
|
125
|
+
and whose `==` uses `is`.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
|
|
129
|
+
>>> from cs.obj import Sentinel
|
|
130
|
+
>>> MISSING = Sentinel("MISSING")
|
|
131
|
+
>>> print(MISSING)
|
|
132
|
+
MISSING
|
|
133
|
+
>>> other = Sentinel("other")
|
|
134
|
+
>>> MISSING == other
|
|
135
|
+
False
|
|
136
|
+
>>> MISSING == MISSING
|
|
137
|
+
True
|
|
138
|
+
|
|
139
|
+
## <a name="singleton"></a>`singleton(registry, key, factory, fargs, fkwargs)`
|
|
140
|
+
|
|
141
|
+
Obtain an object for `key` via `registry` (a mapping of `key`=>object).
|
|
142
|
+
Return `(is_new,object)`.
|
|
143
|
+
|
|
144
|
+
If the `key` exists in the registry, return the associated object.
|
|
145
|
+
Otherwise create a new object by calling `factory(*fargs,**fkwargs)`
|
|
146
|
+
and store it as `key` in the `registry`.
|
|
147
|
+
|
|
148
|
+
The `registry` may be any mapping of `key`s to objects
|
|
149
|
+
but will usually be a `weakref.WeakValueDictionary`
|
|
150
|
+
in order that object references expire as normal,
|
|
151
|
+
allowing garbage collection.
|
|
152
|
+
|
|
153
|
+
*Note*: this function *is not* thread safe.
|
|
154
|
+
Multithreaded users should hold a mutex.
|
|
155
|
+
|
|
156
|
+
See the `SingletonMixin` class for a simple mixin to create
|
|
157
|
+
singleton classes,
|
|
158
|
+
which does provide thread safe operations.
|
|
159
|
+
|
|
160
|
+
## <a name="SingletonMixin"></a>Class `SingletonMixin`
|
|
161
|
+
|
|
162
|
+
A mixin turning a subclass into a singleton factory.
|
|
163
|
+
|
|
164
|
+
*Note*: this mixin overrides `object.__new__`
|
|
165
|
+
and may not play well with other classes which override `__new__`.
|
|
166
|
+
|
|
167
|
+
*Warning*: because of the mechanics of `__new__`,
|
|
168
|
+
the instance's `__init__` method will always be called
|
|
169
|
+
after `__new__`,
|
|
170
|
+
even when a preexisting object is returned.
|
|
171
|
+
Therefore that method should be sensible
|
|
172
|
+
even for an already initialised
|
|
173
|
+
and probably subsequently modified object.
|
|
174
|
+
|
|
175
|
+
My suggested approach is to access some attribute,
|
|
176
|
+
and preemptively return if it already exists.
|
|
177
|
+
Example:
|
|
178
|
+
|
|
179
|
+
def __init__(self, x, y):
|
|
180
|
+
if 'x' in self.__dict__:
|
|
181
|
+
return
|
|
182
|
+
self.x = x
|
|
183
|
+
self.y = y
|
|
184
|
+
|
|
185
|
+
*Note*: we probe `self.__dict__` above to accomodate classes
|
|
186
|
+
with a `__getattr__` method.
|
|
187
|
+
|
|
188
|
+
*Note*: each class registry has a lock,
|
|
189
|
+
which ensures that reuse of an object
|
|
190
|
+
in multiple threads will call the `__init__` method
|
|
191
|
+
in a thread safe serialised fashion.
|
|
192
|
+
|
|
193
|
+
Implementation requirements:
|
|
194
|
+
a subclass should:
|
|
195
|
+
* provide a method `_singleton_key(*args,**kwargs)`
|
|
196
|
+
returning a key for use in the single registry,
|
|
197
|
+
computed from the positional and keyword arguments
|
|
198
|
+
supplied on instance creation
|
|
199
|
+
i.e. those which `__init__` would normally receive.
|
|
200
|
+
This should have the same signature as `__init__`
|
|
201
|
+
but using `cls` instead of `self`.
|
|
202
|
+
* provide a normal `__init__` method
|
|
203
|
+
which can be safely called again
|
|
204
|
+
after some earlier initialisation.
|
|
205
|
+
|
|
206
|
+
This class is thread safe for the registry operations.
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
|
|
210
|
+
class Pool(SingletonMixin):
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def _singleton_key(cls, foo, bah=3):
|
|
214
|
+
return foo, bah
|
|
215
|
+
|
|
216
|
+
def __init__(self, foo, bah=3):
|
|
217
|
+
if hasattr(self, 'foo'):
|
|
218
|
+
return
|
|
219
|
+
... normal __init__ stuff here ...
|
|
220
|
+
self.foo = foo
|
|
221
|
+
...
|
|
222
|
+
|
|
223
|
+
*`SingletonMixin.__hash__(self)`*:
|
|
224
|
+
default hash and equality methods
|
|
225
|
+
|
|
226
|
+
*`SingletonMixin.singleton_also_by(also_key, key)`*:
|
|
227
|
+
Obtain a singleton by a secondary key.
|
|
228
|
+
Return the instance or `None`.
|
|
229
|
+
|
|
230
|
+
Parameters:
|
|
231
|
+
* `also_key`: the name of the secondary key index
|
|
232
|
+
* `key`: the key for the index
|
|
233
|
+
|
|
234
|
+
## <a name="TrackedClassMixin"></a>Class `TrackedClassMixin`
|
|
235
|
+
|
|
236
|
+
A mixin to track all instances of a particular class.
|
|
237
|
+
|
|
238
|
+
This is aimed at checking the global state of objects of a
|
|
239
|
+
particular type, particularly states like counters. The
|
|
240
|
+
tracking is attached to the class itself.
|
|
241
|
+
|
|
242
|
+
The class to be tracked includes this mixin as a superclass and calls:
|
|
243
|
+
|
|
244
|
+
TrackedClassMixin.__init__(class_to_track)
|
|
245
|
+
|
|
246
|
+
from its __init__ method. Note that `class_to_track` is
|
|
247
|
+
typically the class name itself, not `type(self)` which would
|
|
248
|
+
track the specific subclass. At some relevant point one can call:
|
|
249
|
+
|
|
250
|
+
self.tcm_dump(class_to_track[, file])
|
|
251
|
+
|
|
252
|
+
`class_to_track` needs a `tcm_get_state` method to return the
|
|
253
|
+
salient information, such as this from cs.resources.MultiOpenMixin:
|
|
254
|
+
|
|
255
|
+
def tcm_get_state(self):
|
|
256
|
+
return {'opened': self.opened, 'opens': self._opens}
|
|
257
|
+
|
|
258
|
+
See cs.resources.MultiOpenMixin for example use.
|
|
259
|
+
|
|
260
|
+
*`TrackedClassMixin.tcm_all_state(klass)`*:
|
|
261
|
+
Generator yielding tracking information
|
|
262
|
+
for objects of type `klass`
|
|
263
|
+
in the form `(o,state)`
|
|
264
|
+
where `o` if a tracked object
|
|
265
|
+
and `state` is the object's `get_tcm_state` method result.
|
|
266
|
+
|
|
267
|
+
*`TrackedClassMixin.tcm_dump(klass, f=None)`*:
|
|
268
|
+
Dump the tracking information for `klass` to the file `f`
|
|
269
|
+
(default `sys.stderr`).
|
|
270
|
+
|
|
271
|
+
# Release Log
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
*Release 20241005*:
|
|
276
|
+
New public_subclasses(cls) returning all subclasses with public names.
|
|
277
|
+
|
|
278
|
+
*Release 20220918*:
|
|
279
|
+
* SingletonMixin: change example to probe self__dict__ instead of hasattr, faster and less fragile.
|
|
280
|
+
* New Sentinel class for named sentinel objects, equal only to their own instance.
|
|
281
|
+
|
|
282
|
+
*Release 20220530*:
|
|
283
|
+
SingletonMixin: add default __hash__ and __eq__ methods to support dict and set membership.
|
|
284
|
+
|
|
285
|
+
*Release 20210717*:
|
|
286
|
+
SingletonMixin: if cls._singleton_key returns None we always make a new instance and do not register it.
|
|
287
|
+
|
|
288
|
+
*Release 20210306*:
|
|
289
|
+
SingletonMixin: make singleton_also_by() a public method.
|
|
290
|
+
|
|
291
|
+
*Release 20210131*:
|
|
292
|
+
SingletonMixin: new _singleton_also_indexmap method to return a mapping of secondary keys to values to secondary lookup, _singleton_also_index() to update these indices, _singleton_also_by to look up a secondary index.
|
|
293
|
+
|
|
294
|
+
*Release 20210122*:
|
|
295
|
+
SingletonMixin: new _singleton_instances() method returning a list of the current instances.
|
|
296
|
+
|
|
297
|
+
*Release 20201227*:
|
|
298
|
+
SingletonMixin: correctly invoke __new__, a surprisingly fiddly task to get right.
|
|
299
|
+
|
|
300
|
+
*Release 20201021*:
|
|
301
|
+
* @OBSOLETE(obj_as_dict), recommend "as_dict()".
|
|
302
|
+
* [BREAKING] change as_dict() to accept a single optional selector instead of various mutually exclusive keywords.
|
|
303
|
+
|
|
304
|
+
*Release 20200716*:
|
|
305
|
+
SingletonMixin: no longer require special _singleton_init method, reuse default __init__ implicitly through __new__ mechanics.
|
|
306
|
+
|
|
307
|
+
*Release 20200517*:
|
|
308
|
+
Documentation improvements.
|
|
309
|
+
|
|
310
|
+
*Release 20200318*:
|
|
311
|
+
* Replace obsolete O class with a new subclass of SimpleNamespace which issues a warning.
|
|
312
|
+
* New singleton() generic factory function and SingletonMixin mixin class for making singleton classes.
|
|
313
|
+
|
|
314
|
+
*Release 20190103*:
|
|
315
|
+
* New mixin class TrackedClassMixin to track all instances of a particular class.
|
|
316
|
+
* Documentation updates.
|
|
317
|
+
|
|
318
|
+
*Release 20170904*:
|
|
319
|
+
Minor cleanups.
|
|
320
|
+
|
|
321
|
+
*Release 20160828*:
|
|
322
|
+
* Use "install_requires" instead of "requires" in DISTINFO.
|
|
323
|
+
* Minor tweaks.
|
|
324
|
+
|
|
325
|
+
*Release 20150118*:
|
|
326
|
+
move long_description into cs/README-obj.rst
|
|
327
|
+
|
|
328
|
+
*Release 20150110*:
|
|
329
|
+
cleaned out some old junk, readied metadata for PyPI
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
Convenience facilities for objects.
|
|
2
|
+
|
|
3
|
+
*Latest release 20241005*:
|
|
4
|
+
New public_subclasses(cls) returning all subclasses with public names.
|
|
5
|
+
|
|
6
|
+
## <a name="as_dict"></a>`as_dict(o, selector=None)`
|
|
7
|
+
|
|
8
|
+
Return a dictionary with keys mapping to the values of the attributes of `o`.
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
* `o`: the object to map
|
|
12
|
+
* `selector`: the optional selection criterion
|
|
13
|
+
|
|
14
|
+
If `selector` is omitted or `None`, select "public" attributes,
|
|
15
|
+
those not commencing with an underscore.
|
|
16
|
+
|
|
17
|
+
If `selector` is a `str`, select attributes starting with `selector`.
|
|
18
|
+
|
|
19
|
+
Otherwise presume `selector` is callable
|
|
20
|
+
and select attributes `attr` where `selector(attr)` is true.
|
|
21
|
+
|
|
22
|
+
## <a name="copy"></a>`copy(obj, *a, **kw)`
|
|
23
|
+
|
|
24
|
+
Convenient function to shallow copy an object with simple modifications.
|
|
25
|
+
|
|
26
|
+
Performs a shallow copy of `self` using copy.copy.
|
|
27
|
+
|
|
28
|
+
Treat all positional parameters as attribute names, and
|
|
29
|
+
replace those attributes with shallow copies of the original
|
|
30
|
+
attribute.
|
|
31
|
+
|
|
32
|
+
Treat all keyword arguments as (attribute,value) tuples and
|
|
33
|
+
replace those attributes with the supplied values.
|
|
34
|
+
|
|
35
|
+
## <a name="flavour"></a>`flavour(obj)`
|
|
36
|
+
|
|
37
|
+
Return constants indicating the ``flavour'' of an object:
|
|
38
|
+
* `T_MAP`: DictType, DictionaryType, objects with an __keys__ or keys attribute.
|
|
39
|
+
* `T_SEQ`: TupleType, ListType, objects with an __iter__ attribute.
|
|
40
|
+
* `T_SCALAR`: Anything else.
|
|
41
|
+
|
|
42
|
+
## <a name="O"></a>Class `O(types.SimpleNamespace)`
|
|
43
|
+
|
|
44
|
+
The `O` class is now obsolete, please subclass `types.SimpleNamespace`
|
|
45
|
+
or use a dataclass.
|
|
46
|
+
|
|
47
|
+
## <a name="O_attritems"></a>`O_attritems(o)`
|
|
48
|
+
|
|
49
|
+
Generator yielding `(attr,value)` for relevant attributes of `o`.
|
|
50
|
+
|
|
51
|
+
## <a name="O_attrs"></a>`O_attrs(o)`
|
|
52
|
+
|
|
53
|
+
Yield attribute names from `o` which are pertinent to `O_str`.
|
|
54
|
+
|
|
55
|
+
Note: this calls `getattr(o,attr)` to inspect it in order to
|
|
56
|
+
prune callables.
|
|
57
|
+
|
|
58
|
+
## <a name="O_merge"></a>`O_merge(o, _conflict=None, _overwrite=False, **kw)`
|
|
59
|
+
|
|
60
|
+
Merge key:value pairs from a mapping into an object.
|
|
61
|
+
|
|
62
|
+
Ignore keys that do not start with a letter.
|
|
63
|
+
New attributes or attributes whose values compare equal are
|
|
64
|
+
merged in. Unequal values are passed to:
|
|
65
|
+
|
|
66
|
+
_conflict(o, attr, old_value, new_value)
|
|
67
|
+
|
|
68
|
+
to resolve the conflict. If _conflict is omitted or None
|
|
69
|
+
then the new value overwrites the old if _overwrite is true.
|
|
70
|
+
|
|
71
|
+
## <a name="O_str"></a>`O_str(o, no_recurse=False, seen=None)`
|
|
72
|
+
|
|
73
|
+
Return a `str` representation of the object `o`.
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
* `o`: the object to describe.
|
|
77
|
+
* `no_recurse`: if true, do not recurse into the object's structure.
|
|
78
|
+
Default: `False`.
|
|
79
|
+
* `seen`: a set of previously sighted objects
|
|
80
|
+
to prevent recursion loops.
|
|
81
|
+
|
|
82
|
+
## <a name="obj_as_dict"></a>`obj_as_dict(o, **kw)`
|
|
83
|
+
|
|
84
|
+
OBSOLETE obj_as_dict
|
|
85
|
+
|
|
86
|
+
OBSOLETE convesion of an object to a `dict`. Please us `cs.obj.as_dict`.
|
|
87
|
+
|
|
88
|
+
## <a name="Proxy"></a>Class `Proxy`
|
|
89
|
+
|
|
90
|
+
An extremely simple proxy object
|
|
91
|
+
that passes all unmatched attribute accesses to the proxied object.
|
|
92
|
+
|
|
93
|
+
Note that setattr and delattr work directly on the proxy, not the proxied object.
|
|
94
|
+
|
|
95
|
+
## <a name="public_subclasses"></a>`public_subclasses(cls)`
|
|
96
|
+
|
|
97
|
+
Return a list of the subclasses of `cls` which has public names.
|
|
98
|
+
|
|
99
|
+
## <a name="Sentinel"></a>Class `Sentinel`
|
|
100
|
+
|
|
101
|
+
A simple class for named sentinels whose `str()` is just the name
|
|
102
|
+
and whose `==` uses `is`.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
|
|
106
|
+
>>> from cs.obj import Sentinel
|
|
107
|
+
>>> MISSING = Sentinel("MISSING")
|
|
108
|
+
>>> print(MISSING)
|
|
109
|
+
MISSING
|
|
110
|
+
>>> other = Sentinel("other")
|
|
111
|
+
>>> MISSING == other
|
|
112
|
+
False
|
|
113
|
+
>>> MISSING == MISSING
|
|
114
|
+
True
|
|
115
|
+
|
|
116
|
+
## <a name="singleton"></a>`singleton(registry, key, factory, fargs, fkwargs)`
|
|
117
|
+
|
|
118
|
+
Obtain an object for `key` via `registry` (a mapping of `key`=>object).
|
|
119
|
+
Return `(is_new,object)`.
|
|
120
|
+
|
|
121
|
+
If the `key` exists in the registry, return the associated object.
|
|
122
|
+
Otherwise create a new object by calling `factory(*fargs,**fkwargs)`
|
|
123
|
+
and store it as `key` in the `registry`.
|
|
124
|
+
|
|
125
|
+
The `registry` may be any mapping of `key`s to objects
|
|
126
|
+
but will usually be a `weakref.WeakValueDictionary`
|
|
127
|
+
in order that object references expire as normal,
|
|
128
|
+
allowing garbage collection.
|
|
129
|
+
|
|
130
|
+
*Note*: this function *is not* thread safe.
|
|
131
|
+
Multithreaded users should hold a mutex.
|
|
132
|
+
|
|
133
|
+
See the `SingletonMixin` class for a simple mixin to create
|
|
134
|
+
singleton classes,
|
|
135
|
+
which does provide thread safe operations.
|
|
136
|
+
|
|
137
|
+
## <a name="SingletonMixin"></a>Class `SingletonMixin`
|
|
138
|
+
|
|
139
|
+
A mixin turning a subclass into a singleton factory.
|
|
140
|
+
|
|
141
|
+
*Note*: this mixin overrides `object.__new__`
|
|
142
|
+
and may not play well with other classes which override `__new__`.
|
|
143
|
+
|
|
144
|
+
*Warning*: because of the mechanics of `__new__`,
|
|
145
|
+
the instance's `__init__` method will always be called
|
|
146
|
+
after `__new__`,
|
|
147
|
+
even when a preexisting object is returned.
|
|
148
|
+
Therefore that method should be sensible
|
|
149
|
+
even for an already initialised
|
|
150
|
+
and probably subsequently modified object.
|
|
151
|
+
|
|
152
|
+
My suggested approach is to access some attribute,
|
|
153
|
+
and preemptively return if it already exists.
|
|
154
|
+
Example:
|
|
155
|
+
|
|
156
|
+
def __init__(self, x, y):
|
|
157
|
+
if 'x' in self.__dict__:
|
|
158
|
+
return
|
|
159
|
+
self.x = x
|
|
160
|
+
self.y = y
|
|
161
|
+
|
|
162
|
+
*Note*: we probe `self.__dict__` above to accomodate classes
|
|
163
|
+
with a `__getattr__` method.
|
|
164
|
+
|
|
165
|
+
*Note*: each class registry has a lock,
|
|
166
|
+
which ensures that reuse of an object
|
|
167
|
+
in multiple threads will call the `__init__` method
|
|
168
|
+
in a thread safe serialised fashion.
|
|
169
|
+
|
|
170
|
+
Implementation requirements:
|
|
171
|
+
a subclass should:
|
|
172
|
+
* provide a method `_singleton_key(*args,**kwargs)`
|
|
173
|
+
returning a key for use in the single registry,
|
|
174
|
+
computed from the positional and keyword arguments
|
|
175
|
+
supplied on instance creation
|
|
176
|
+
i.e. those which `__init__` would normally receive.
|
|
177
|
+
This should have the same signature as `__init__`
|
|
178
|
+
but using `cls` instead of `self`.
|
|
179
|
+
* provide a normal `__init__` method
|
|
180
|
+
which can be safely called again
|
|
181
|
+
after some earlier initialisation.
|
|
182
|
+
|
|
183
|
+
This class is thread safe for the registry operations.
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
|
|
187
|
+
class Pool(SingletonMixin):
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def _singleton_key(cls, foo, bah=3):
|
|
191
|
+
return foo, bah
|
|
192
|
+
|
|
193
|
+
def __init__(self, foo, bah=3):
|
|
194
|
+
if hasattr(self, 'foo'):
|
|
195
|
+
return
|
|
196
|
+
... normal __init__ stuff here ...
|
|
197
|
+
self.foo = foo
|
|
198
|
+
...
|
|
199
|
+
|
|
200
|
+
*`SingletonMixin.__hash__(self)`*:
|
|
201
|
+
default hash and equality methods
|
|
202
|
+
|
|
203
|
+
*`SingletonMixin.singleton_also_by(also_key, key)`*:
|
|
204
|
+
Obtain a singleton by a secondary key.
|
|
205
|
+
Return the instance or `None`.
|
|
206
|
+
|
|
207
|
+
Parameters:
|
|
208
|
+
* `also_key`: the name of the secondary key index
|
|
209
|
+
* `key`: the key for the index
|
|
210
|
+
|
|
211
|
+
## <a name="TrackedClassMixin"></a>Class `TrackedClassMixin`
|
|
212
|
+
|
|
213
|
+
A mixin to track all instances of a particular class.
|
|
214
|
+
|
|
215
|
+
This is aimed at checking the global state of objects of a
|
|
216
|
+
particular type, particularly states like counters. The
|
|
217
|
+
tracking is attached to the class itself.
|
|
218
|
+
|
|
219
|
+
The class to be tracked includes this mixin as a superclass and calls:
|
|
220
|
+
|
|
221
|
+
TrackedClassMixin.__init__(class_to_track)
|
|
222
|
+
|
|
223
|
+
from its __init__ method. Note that `class_to_track` is
|
|
224
|
+
typically the class name itself, not `type(self)` which would
|
|
225
|
+
track the specific subclass. At some relevant point one can call:
|
|
226
|
+
|
|
227
|
+
self.tcm_dump(class_to_track[, file])
|
|
228
|
+
|
|
229
|
+
`class_to_track` needs a `tcm_get_state` method to return the
|
|
230
|
+
salient information, such as this from cs.resources.MultiOpenMixin:
|
|
231
|
+
|
|
232
|
+
def tcm_get_state(self):
|
|
233
|
+
return {'opened': self.opened, 'opens': self._opens}
|
|
234
|
+
|
|
235
|
+
See cs.resources.MultiOpenMixin for example use.
|
|
236
|
+
|
|
237
|
+
*`TrackedClassMixin.tcm_all_state(klass)`*:
|
|
238
|
+
Generator yielding tracking information
|
|
239
|
+
for objects of type `klass`
|
|
240
|
+
in the form `(o,state)`
|
|
241
|
+
where `o` if a tracked object
|
|
242
|
+
and `state` is the object's `get_tcm_state` method result.
|
|
243
|
+
|
|
244
|
+
*`TrackedClassMixin.tcm_dump(klass, f=None)`*:
|
|
245
|
+
Dump the tracking information for `klass` to the file `f`
|
|
246
|
+
(default `sys.stderr`).
|
|
247
|
+
|
|
248
|
+
# Release Log
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
*Release 20241005*:
|
|
253
|
+
New public_subclasses(cls) returning all subclasses with public names.
|
|
254
|
+
|
|
255
|
+
*Release 20220918*:
|
|
256
|
+
* SingletonMixin: change example to probe self__dict__ instead of hasattr, faster and less fragile.
|
|
257
|
+
* New Sentinel class for named sentinel objects, equal only to their own instance.
|
|
258
|
+
|
|
259
|
+
*Release 20220530*:
|
|
260
|
+
SingletonMixin: add default __hash__ and __eq__ methods to support dict and set membership.
|
|
261
|
+
|
|
262
|
+
*Release 20210717*:
|
|
263
|
+
SingletonMixin: if cls._singleton_key returns None we always make a new instance and do not register it.
|
|
264
|
+
|
|
265
|
+
*Release 20210306*:
|
|
266
|
+
SingletonMixin: make singleton_also_by() a public method.
|
|
267
|
+
|
|
268
|
+
*Release 20210131*:
|
|
269
|
+
SingletonMixin: new _singleton_also_indexmap method to return a mapping of secondary keys to values to secondary lookup, _singleton_also_index() to update these indices, _singleton_also_by to look up a secondary index.
|
|
270
|
+
|
|
271
|
+
*Release 20210122*:
|
|
272
|
+
SingletonMixin: new _singleton_instances() method returning a list of the current instances.
|
|
273
|
+
|
|
274
|
+
*Release 20201227*:
|
|
275
|
+
SingletonMixin: correctly invoke __new__, a surprisingly fiddly task to get right.
|
|
276
|
+
|
|
277
|
+
*Release 20201021*:
|
|
278
|
+
* @OBSOLETE(obj_as_dict), recommend "as_dict()".
|
|
279
|
+
* [BREAKING] change as_dict() to accept a single optional selector instead of various mutually exclusive keywords.
|
|
280
|
+
|
|
281
|
+
*Release 20200716*:
|
|
282
|
+
SingletonMixin: no longer require special _singleton_init method, reuse default __init__ implicitly through __new__ mechanics.
|
|
283
|
+
|
|
284
|
+
*Release 20200517*:
|
|
285
|
+
Documentation improvements.
|
|
286
|
+
|
|
287
|
+
*Release 20200318*:
|
|
288
|
+
* Replace obsolete O class with a new subclass of SimpleNamespace which issues a warning.
|
|
289
|
+
* New singleton() generic factory function and SingletonMixin mixin class for making singleton classes.
|
|
290
|
+
|
|
291
|
+
*Release 20190103*:
|
|
292
|
+
* New mixin class TrackedClassMixin to track all instances of a particular class.
|
|
293
|
+
* Documentation updates.
|
|
294
|
+
|
|
295
|
+
*Release 20170904*:
|
|
296
|
+
Minor cleanups.
|
|
297
|
+
|
|
298
|
+
*Release 20160828*:
|
|
299
|
+
* Use "install_requires" instead of "requires" in DISTINFO.
|
|
300
|
+
* Minor tweaks.
|
|
301
|
+
|
|
302
|
+
*Release 20150118*:
|
|
303
|
+
move long_description into cs/README-obj.rst
|
|
304
|
+
|
|
305
|
+
*Release 20150110*:
|
|
306
|
+
cleaned out some old junk, readied metadata for PyPI
|