|
1
|
|
|
""" |
|
2
|
|
|
How it works: the library is composed of 2 major parts: |
|
3
|
|
|
|
|
4
|
|
|
* The `sealers`. They return a class that implements a container according the given specification (list of field names |
|
5
|
|
|
and default values). |
|
6
|
|
|
* The `factory`. A metaclass that implements attribute/item access, so you can do ``Fields.a.b.c``. On each |
|
7
|
|
|
getattr/getitem it returns a new instance with the new state. Its ``__new__`` method takes extra arguments to store |
|
8
|
|
|
the contruction state and it works in two ways: |
|
9
|
|
|
|
|
10
|
|
|
* Construction phase (there are no bases). Make new instances of the `Factory` with new state. |
|
11
|
|
|
* Usage phase. When subclassed (there are bases) it will use the sealer to return the final class. |
|
12
|
|
|
""" |
|
13
|
|
|
import linecache |
|
14
|
|
|
import sys |
|
15
|
|
|
from itertools import chain |
|
16
|
|
|
from operator import itemgetter |
|
17
|
|
|
|
|
18
|
|
|
import zlib |
|
19
|
|
|
|
|
20
|
|
|
try: |
|
21
|
|
|
from collections import OrderedDict |
|
22
|
|
|
except ImportError: |
|
23
|
|
|
from .py2ordereddict import OrderedDict |
|
24
|
|
|
|
|
25
|
|
|
__version__ = "5.0.0" |
|
26
|
|
|
__all__ = ( |
|
27
|
|
|
'BareFields', |
|
28
|
|
|
'ComparableMixin', |
|
29
|
|
|
'ConvertibleFields', |
|
30
|
|
|
'ConvertibleMixin', |
|
31
|
|
|
'Fields', |
|
32
|
|
|
'PrintableMixin', |
|
33
|
|
|
'SlotsFields', |
|
34
|
|
|
'Tuple', |
|
35
|
|
|
# advanced stuff |
|
36
|
|
|
'factory', |
|
37
|
|
|
'make_init_func', |
|
38
|
|
|
'class_sealer', |
|
39
|
|
|
'slots_class_sealer', |
|
40
|
|
|
'tuple_sealer', |
|
41
|
|
|
# convenience things |
|
42
|
|
|
'Namespace' |
|
43
|
|
|
) |
|
44
|
|
|
PY2 = sys.version_info[0] == 2 |
|
45
|
|
|
MISSING = object() |
|
46
|
|
|
|
|
47
|
|
|
|
|
48
|
|
|
def _with_metaclass(meta, *bases): |
|
49
|
|
|
# See: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/#metaclass-syntax-changes |
|
50
|
|
|
|
|
51
|
|
|
class metaclass(meta): |
|
52
|
|
|
__call__ = type.__call__ |
|
53
|
|
|
__init__ = type.__init__ |
|
54
|
|
|
|
|
55
|
|
|
def __new__(cls, name, this_bases, d): |
|
56
|
|
|
if this_bases is None: |
|
57
|
|
|
return type.__new__(cls, name, (), d) |
|
58
|
|
|
return meta(name, bases, d) |
|
59
|
|
|
return metaclass('temporary_class', None, {}) |
|
60
|
|
|
|
|
61
|
|
|
|
|
62
|
|
|
class __base__(object): |
|
63
|
|
|
def __init__(self, *args, **kwargs): |
|
64
|
|
|
pass |
|
65
|
|
|
|
|
66
|
|
|
|
|
67
|
|
|
def make_init_func(fields, defaults, |
|
68
|
|
|
baseclass_name='FieldsBase', |
|
69
|
|
|
header_name='__init__', |
|
70
|
|
|
header_start='def {func_name}(self', |
|
71
|
|
|
header_end='):\n', |
|
72
|
|
|
super_call_start='super({baseclass_name}, self).__init__(', |
|
73
|
|
|
super_call_end=')\n', |
|
74
|
|
|
super_call=True, |
|
75
|
|
|
super_call_pass_allargs=True, |
|
76
|
|
|
super_call_pass_kwargs=True, |
|
77
|
|
|
set_attributes=True): |
|
78
|
|
|
func_name = '__fields_init_for__{0}__'.format('__'.join(fields)) |
|
79
|
|
|
parts = [header_start.format(func_name=func_name)] |
|
80
|
|
|
still_positional = True |
|
81
|
|
|
for var in fields: |
|
82
|
|
|
if var in defaults: |
|
83
|
|
|
still_positional = False |
|
84
|
|
|
parts.append(', {0}={0}'.format(var)) |
|
85
|
|
|
elif still_positional: |
|
86
|
|
|
parts.append(', {0}'.format(var)) |
|
87
|
|
|
else: |
|
88
|
|
|
raise ValueError("Cannot have positional fields after fields with defaults. " |
|
89
|
|
|
"Field {0!r} is missing a default value!".format(var)) |
|
90
|
|
|
parts.append(header_end if fields else header_end.lstrip(', ')) |
|
91
|
|
|
if set_attributes: |
|
92
|
|
|
for var in fields: |
|
93
|
|
|
parts.append(' self.{0} = {0}\n'.format(var)) |
|
94
|
|
|
if super_call: |
|
95
|
|
|
parts.append(' ') |
|
96
|
|
|
parts.append(super_call_start.format(baseclass_name=baseclass_name)) |
|
97
|
|
|
if super_call_pass_allargs: |
|
98
|
|
|
if super_call_pass_kwargs: |
|
99
|
|
|
parts.append(', '.join('{0}={0}'.format(var) for var in fields)) |
|
100
|
|
|
else: |
|
101
|
|
|
parts.append(', '.join('{0}'.format(var) for var in fields)) |
|
102
|
|
|
parts.append(super_call_end) |
|
103
|
|
|
else: |
|
104
|
|
|
parts.append(super_call_end.lstrip(', ')) |
|
105
|
|
|
local_namespace = dict(defaults) |
|
106
|
|
|
global_namespace = dict(super=super) if super_call else {} |
|
107
|
|
|
parts.append('{0} = {1}\ndel {1}'.format(header_name, func_name)) |
|
108
|
|
|
code = ''.join(parts) |
|
109
|
|
|
|
|
110
|
|
|
filename = "<fields-init-function-%x>" % zlib.adler32(code.encode('utf8')) |
|
111
|
|
|
codeobj = compile(code, filename, 'exec') |
|
112
|
|
|
if PY2: |
|
113
|
|
|
exec("exec codeobj in global_namespace, local_namespace") |
|
114
|
|
|
else: |
|
115
|
|
|
exec(codeobj, global_namespace, local_namespace) |
|
116
|
|
|
linecache.cache[filename] = len(code), None, code.splitlines(), filename |
|
117
|
|
|
return global_namespace, local_namespace |
|
118
|
|
|
|
|
119
|
|
|
|
|
120
|
|
|
def class_sealer(fields, defaults, |
|
121
|
|
|
base=__base__, make_init_func=make_init_func, |
|
122
|
|
|
initializer=True, comparable=True, printable=True, convertible=False, pass_kwargs=False): |
|
123
|
|
|
""" |
|
124
|
|
|
This sealer makes a normal container class. It's mutable and supports arguments with default values. |
|
125
|
|
|
""" |
|
126
|
|
|
baseclass_name = 'FieldsBase_for__{0}'.format('__'.join(fields)) |
|
127
|
|
|
if pass_kwargs: |
|
128
|
|
|
options = dict( |
|
129
|
|
|
header_end=', **__fields_kwargs__):\n', |
|
130
|
|
|
super_call_end=', **__fields_kwargs__)\n', |
|
131
|
|
|
super_call_pass_allargs=False, |
|
132
|
|
|
) |
|
133
|
|
|
else: |
|
134
|
|
|
options = {} |
|
135
|
|
|
|
|
136
|
|
|
if initializer: |
|
137
|
|
|
global_namespace, local_namespace = make_init_func(fields, defaults, baseclass_name, **options) |
|
138
|
|
|
|
|
139
|
|
|
class FieldsBase(base): |
|
140
|
|
|
if initializer: |
|
141
|
|
|
__init__ = local_namespace['__init__'] |
|
142
|
|
|
|
|
143
|
|
|
if comparable: |
|
144
|
|
|
def __eq__(self, other): |
|
145
|
|
|
if isinstance(other, self.__class__): |
|
146
|
|
|
return tuple(getattr(self, a) for a in fields) == tuple(getattr(other, a) for a in fields) |
|
147
|
|
|
else: |
|
148
|
|
|
return NotImplemented |
|
149
|
|
|
|
|
150
|
|
|
def __ne__(self, other): |
|
151
|
|
|
result = self.__eq__(other) |
|
152
|
|
|
if result is NotImplemented: |
|
153
|
|
|
return NotImplemented |
|
154
|
|
|
else: |
|
155
|
|
|
return not result |
|
156
|
|
|
|
|
157
|
|
|
def __lt__(self, other): |
|
158
|
|
|
if isinstance(other, self.__class__): |
|
159
|
|
|
return tuple(getattr(self, a) for a in fields) < tuple(getattr(other, a) for a in fields) |
|
160
|
|
|
else: |
|
161
|
|
|
return NotImplemented |
|
162
|
|
|
|
|
163
|
|
|
def __le__(self, other): |
|
164
|
|
|
if isinstance(other, self.__class__): |
|
165
|
|
|
return tuple(getattr(self, a) for a in fields) <= tuple(getattr(other, a) for a in fields) |
|
166
|
|
|
else: |
|
167
|
|
|
return NotImplemented |
|
168
|
|
|
|
|
169
|
|
|
def __gt__(self, other): |
|
170
|
|
|
if isinstance(other, self.__class__): |
|
171
|
|
|
return tuple(getattr(self, a) for a in fields) > tuple(getattr(other, a) for a in fields) |
|
172
|
|
|
else: |
|
173
|
|
|
return NotImplemented |
|
174
|
|
|
|
|
175
|
|
|
def __ge__(self, other): |
|
176
|
|
|
if isinstance(other, self.__class__): |
|
177
|
|
|
return tuple(getattr(self, a) for a in fields) >= tuple(getattr(other, a) for a in fields) |
|
178
|
|
|
else: |
|
179
|
|
|
return NotImplemented |
|
180
|
|
|
|
|
181
|
|
|
def __hash__(self): |
|
182
|
|
|
return hash(tuple(getattr(self, a) for a in fields)) |
|
183
|
|
|
|
|
184
|
|
|
if printable: |
|
185
|
|
|
def __repr__(self): |
|
186
|
|
|
return "{0}({1})".format( |
|
187
|
|
|
self.__class__.__name__, |
|
188
|
|
|
", ".join("{0}={1}".format(attr, repr(getattr(self, attr))) for attr in fields) |
|
189
|
|
|
) |
|
190
|
|
|
if convertible: |
|
191
|
|
|
@property |
|
192
|
|
|
def as_dict(self): |
|
193
|
|
|
return dict((attr, getattr(self, attr)) for attr in fields) |
|
194
|
|
|
|
|
195
|
|
|
@property |
|
196
|
|
|
def as_tuple(self): |
|
197
|
|
|
return tuple(getattr(self, attr) for attr in fields) |
|
198
|
|
|
|
|
199
|
|
|
if initializer: |
|
200
|
|
|
global_namespace[baseclass_name] = FieldsBase |
|
201
|
|
|
return FieldsBase |
|
202
|
|
|
|
|
203
|
|
|
|
|
204
|
|
|
def slots_class_sealer(fields, defaults): |
|
205
|
|
|
""" |
|
206
|
|
|
This sealer makes a container class that uses ``__slots__`` (it uses :func:`class_sealer` internally). |
|
207
|
|
|
|
|
208
|
|
|
The resulting class has a metaclass that forcibly sets ``__slots__`` on subclasses. |
|
209
|
|
|
""" |
|
210
|
|
|
class __slots_meta__(type): |
|
211
|
|
|
def __new__(mcs, name, bases, namespace): |
|
212
|
|
|
if "__slots__" not in namespace: |
|
213
|
|
|
namespace["__slots__"] = fields |
|
214
|
|
|
return type.__new__(mcs, name, bases, namespace) |
|
215
|
|
|
|
|
216
|
|
|
class __slots_base__(_with_metaclass(__slots_meta__, object)): |
|
217
|
|
|
__slots__ = () |
|
218
|
|
|
|
|
219
|
|
|
def __init__(self, *args, **kwargs): |
|
220
|
|
|
pass |
|
221
|
|
|
|
|
222
|
|
|
return class_sealer(fields, defaults, base=__slots_base__) |
|
223
|
|
|
|
|
224
|
|
|
|
|
225
|
|
|
def tuple_sealer(fields, defaults): |
|
226
|
|
|
""" |
|
227
|
|
|
This sealer returns an equivalent of a ``namedtuple``. |
|
228
|
|
|
""" |
|
229
|
|
|
baseclass_name = 'FieldsBase_for__{0}'.format('__'.join(fields)) |
|
230
|
|
|
global_namespace, local_namespace = make_init_func( |
|
231
|
|
|
fields, defaults, baseclass_name, |
|
232
|
|
|
header_name='__new__', |
|
233
|
|
|
header_start='def {func_name}(cls', |
|
234
|
|
|
header_end='):\n', |
|
235
|
|
|
super_call_start='return tuple.__new__(cls, (', |
|
236
|
|
|
super_call_end='))\n', |
|
237
|
|
|
super_call_pass_kwargs=False, set_attributes=False, |
|
238
|
|
|
) |
|
239
|
|
|
|
|
240
|
|
|
def __getnewargs__(self): |
|
241
|
|
|
return tuple(self) |
|
242
|
|
|
|
|
243
|
|
|
def __repr__(self): |
|
244
|
|
|
return "{0}({1})".format( |
|
245
|
|
|
self.__class__.__name__, |
|
246
|
|
|
", ".join(a + "=" + repr(getattr(self, a)) for a in fields) |
|
247
|
|
|
) |
|
248
|
|
|
|
|
249
|
|
|
return type(baseclass_name, (tuple,), dict( |
|
250
|
|
|
[(name, property(itemgetter(i))) for i, name in enumerate(fields)], |
|
251
|
|
|
__new__=local_namespace['__new__'], |
|
252
|
|
|
__getnewargs__=__getnewargs__, |
|
253
|
|
|
__repr__=__repr__, |
|
254
|
|
|
__slots__=(), |
|
255
|
|
|
)) |
|
256
|
|
|
|
|
257
|
|
|
|
|
258
|
|
|
class _SealerWrapper(object): |
|
259
|
|
|
""" |
|
260
|
|
|
Primitive wrapper around a function that makes it `un-bindable`. |
|
261
|
|
|
|
|
262
|
|
|
When you add a function in the namespace of a class it will be bound (become a method) when you try to access it. |
|
263
|
|
|
This class prevents that. |
|
264
|
|
|
""" |
|
265
|
|
|
def __init__(self, func, **kwargs): |
|
266
|
|
|
self.func = func |
|
267
|
|
|
self.kwargs = kwargs |
|
268
|
|
|
|
|
269
|
|
|
@property |
|
270
|
|
|
def __name__(self): |
|
271
|
|
|
return self.func.__name__ |
|
272
|
|
|
|
|
273
|
|
|
def __call__(self, *args, **kwargs): |
|
274
|
|
|
return self.func(*args, **dict(self.kwargs, **kwargs)) |
|
275
|
|
|
|
|
276
|
|
|
|
|
277
|
|
|
class _Factory(type): |
|
278
|
|
|
""" |
|
279
|
|
|
This class makes everything work. It a metaclass for the class that users are going to use. Each new chain |
|
280
|
|
|
rebuilds everything. |
|
281
|
|
|
""" |
|
282
|
|
|
|
|
283
|
|
|
__required = () |
|
284
|
|
|
__defaults = () |
|
285
|
|
|
__all_fields = () |
|
286
|
|
|
__last_field = None |
|
287
|
|
|
__full_required = () |
|
288
|
|
|
__sealer = None |
|
289
|
|
|
__concrete = None |
|
290
|
|
|
|
|
291
|
|
|
def __getattr__(cls, name): |
|
292
|
|
|
if name.startswith("__") and name.endswith("__"): |
|
293
|
|
|
return type.__getattribute__(cls, name) |
|
294
|
|
|
if name in cls.__required: |
|
295
|
|
|
raise TypeError("Field %r is already specified as required." % name) |
|
296
|
|
|
if name in cls.__defaults: |
|
297
|
|
|
raise TypeError("Field %r is already specified with a default value (%r)." % ( |
|
298
|
|
|
name, cls.__defaults[name] |
|
299
|
|
|
)) |
|
300
|
|
|
if name == cls.__last_field: |
|
301
|
|
|
raise TypeError("Field %r is already specified as required." % name) |
|
302
|
|
|
if cls.__defaults and cls.__last_field is not None: |
|
303
|
|
|
raise TypeError("Can't add required fields after fields with defaults.") |
|
304
|
|
|
|
|
305
|
|
|
return _Factory( |
|
306
|
|
|
required=cls.__full_required, |
|
307
|
|
|
defaults=cls.__defaults, |
|
308
|
|
|
last_field=name, |
|
309
|
|
|
sealer=cls.__sealer, |
|
310
|
|
|
) |
|
311
|
|
|
|
|
312
|
|
|
def __getitem__(cls, default): |
|
313
|
|
|
if cls.__last_field is None: |
|
314
|
|
|
raise TypeError("Can't set default %r. There's no previous field." % default) |
|
315
|
|
|
|
|
316
|
|
|
new_defaults = OrderedDict(cls.__defaults) |
|
317
|
|
|
new_defaults[cls.__last_field] = default |
|
318
|
|
|
return _Factory( |
|
319
|
|
|
required=cls.__required, |
|
320
|
|
|
defaults=new_defaults, |
|
321
|
|
|
sealer=cls.__sealer, |
|
322
|
|
|
) |
|
323
|
|
|
|
|
324
|
|
|
def __new__(mcs, name="__blank__", bases=(), namespace=None, last_field=None, required=(), defaults=(), |
|
325
|
|
|
sealer=_SealerWrapper(class_sealer)): |
|
326
|
|
|
|
|
327
|
|
|
if not bases: |
|
328
|
|
|
assert isinstance(sealer, _SealerWrapper) |
|
329
|
|
|
|
|
330
|
|
|
full_required = tuple(required) |
|
331
|
|
|
if last_field is not None: |
|
332
|
|
|
full_required += last_field, |
|
333
|
|
|
all_fields = list(chain(full_required, defaults)) |
|
334
|
|
|
|
|
335
|
|
|
return type.__new__( |
|
336
|
|
|
_Factory, |
|
337
|
|
|
"Fields<{0}>.{1}".format(sealer.__name__, ".".join(all_fields)) |
|
338
|
|
|
if all_fields else "Fields<{0}>".format(sealer.__name__), |
|
339
|
|
|
bases, |
|
340
|
|
|
dict( |
|
341
|
|
|
_Factory__required=required, |
|
342
|
|
|
_Factory__defaults=defaults, |
|
343
|
|
|
_Factory__all_fields=all_fields, |
|
344
|
|
|
_Factory__last_field=last_field, |
|
345
|
|
|
_Factory__full_required=full_required, |
|
346
|
|
|
_Factory__sealer=sealer, |
|
347
|
|
|
) |
|
348
|
|
|
) |
|
349
|
|
|
else: |
|
350
|
|
|
for pos, names in enumerate(zip(*[ |
|
351
|
|
|
k.__all_fields |
|
352
|
|
|
for k in bases |
|
353
|
|
|
if isinstance(k, _Factory) |
|
354
|
|
|
])): |
|
355
|
|
|
if names: |
|
356
|
|
|
if len(set(names)) != 1: |
|
357
|
|
|
raise TypeError("Field layout conflict: fields in position {0} have different names: {1}".format( |
|
358
|
|
|
pos, |
|
359
|
|
|
', '.join(repr(name) for name in names) |
|
360
|
|
|
)) |
|
361
|
|
|
|
|
362
|
|
|
return type(name, tuple( |
|
363
|
|
|
~k if isinstance(k, _Factory) else k for k in bases |
|
364
|
|
|
), {} if namespace is None else namespace) |
|
365
|
|
|
|
|
366
|
|
|
def __init__(cls, *args, **kwargs): |
|
367
|
|
|
pass |
|
368
|
|
|
|
|
369
|
|
|
def __call__(cls, *args, **kwargs): |
|
370
|
|
|
return (~cls)(*args, **kwargs) |
|
371
|
|
|
|
|
372
|
|
|
def __invert__(cls): |
|
373
|
|
|
if cls.__concrete is None: |
|
374
|
|
|
if not cls.__all_fields: |
|
375
|
|
|
raise TypeError("You're trying to use an empty Fields factory !") |
|
376
|
|
|
if cls.__defaults and cls.__last_field is not None: |
|
377
|
|
|
raise TypeError("Can't add required fields after fields with defaults.") |
|
378
|
|
|
|
|
379
|
|
|
cls.__concrete = cls.__sealer(cls.__all_fields, cls.__defaults) |
|
380
|
|
|
return cls.__concrete |
|
381
|
|
|
|
|
382
|
|
|
|
|
383
|
|
|
class Namespace(object): |
|
384
|
|
|
""" |
|
385
|
|
|
A backport of Python 3.3's ``types.SimpleNamespace``. |
|
386
|
|
|
""" |
|
387
|
|
|
def __init__(self, **kwargs): |
|
388
|
|
|
self.__dict__.update(kwargs) |
|
389
|
|
|
|
|
390
|
|
|
def __repr__(self): |
|
391
|
|
|
keys = sorted(self.__dict__) |
|
392
|
|
|
items = ("{0}={1!r}".format(k, self.__dict__[k]) for k in keys) |
|
393
|
|
|
return "{0}({1})".format(type(self).__name__, ", ".join(items)) |
|
394
|
|
|
|
|
395
|
|
|
def __eq__(self, other): |
|
396
|
|
|
return self.__dict__ == other.__dict__ |
|
397
|
|
|
|
|
398
|
|
|
|
|
399
|
|
|
def factory(sealer, **sealer_options): |
|
400
|
|
|
""" |
|
401
|
|
|
Create a factory that will produce a class using the given ``sealer``. |
|
402
|
|
|
|
|
403
|
|
|
Args: |
|
404
|
|
|
sealer (func): A function that takes ``fields, defaults`` as arguments, where: |
|
405
|
|
|
|
|
406
|
|
|
* fields (list): A list with all the field names in the declared order. |
|
407
|
|
|
* defaults (dict): A dict with all the defaults. |
|
408
|
|
|
sealer_options: Optional keyword arguments passed to ``sealer``. |
|
409
|
|
|
Return: |
|
410
|
|
|
A class on which you can do `.field1.field2.field3...`. When it's subclassed it "seals", and whatever the |
|
411
|
|
|
``sealer`` returned for the given fields is used as the baseclass. |
|
412
|
|
|
|
|
413
|
|
|
Example: |
|
414
|
|
|
|
|
415
|
|
|
.. sourcecode:: pycon |
|
416
|
|
|
|
|
417
|
|
|
|
|
418
|
|
|
>>> def sealer(fields, defaults): |
|
419
|
|
|
... print("Creating class with:") |
|
420
|
|
|
... print(" fields = {0}".format(fields)) |
|
421
|
|
|
... print(" defaults = {0}".format(defaults)) |
|
422
|
|
|
... return object |
|
423
|
|
|
... |
|
424
|
|
|
>>> Fields = factory(sealer) |
|
425
|
|
|
>>> class Foo(Fields.foo.bar.lorem[1].ipsum[2]): |
|
426
|
|
|
... pass |
|
427
|
|
|
... |
|
428
|
|
|
Creating class with: |
|
429
|
|
|
fields = ['foo', 'bar', 'lorem', 'ipsum'] |
|
430
|
|
|
defaults = OrderedDict([('lorem', 1), ('ipsum', 2)]) |
|
431
|
|
|
>>> Foo |
|
432
|
|
|
<class '...Foo'> |
|
433
|
|
|
>>> Foo.__bases__ |
|
434
|
|
|
(<... 'object'>,) |
|
435
|
|
|
""" |
|
436
|
|
|
return _Factory(sealer=_SealerWrapper(sealer, **sealer_options)) |
|
437
|
|
|
|
|
438
|
|
|
|
|
439
|
|
|
Fields = _Factory() |
|
440
|
|
|
ConvertibleFields = factory(class_sealer, convertible=True) |
|
441
|
|
|
SlotsFields = factory(slots_class_sealer) |
|
442
|
|
|
BareFields = factory(class_sealer, comparable=False, printable=False) |
|
443
|
|
|
InheritableFields = factory(class_sealer, base=object, pass_kwargs=True) |
|
444
|
|
|
|
|
445
|
|
|
Tuple = factory(tuple_sealer) |
|
446
|
|
|
|
|
447
|
|
|
PrintableMixin = factory(class_sealer, initializer=False, base=object, comparable=False) |
|
448
|
|
|
ComparableMixin = factory(class_sealer, initializer=False, base=object, printable=False) |
|
449
|
|
|
ConvertibleMixin = factory( |
|
450
|
|
|
class_sealer, initializer=False, base=object, printable=False, comparable=False, convertible=True |
|
451
|
|
|
) |
|
452
|
|
|
|