1
# -*- test-case-name: epsilon.test.test_structlike -*-
4
This module implements convenience objects for classes which have initializers
5
and repr()s that describe a fixed set of attributes.
8
from twisted.python import context
10
_NOT_SPECIFIED = object()
13
class _RecursiveReprer(object):
15
This object maintains state so that repr()s can tell when they are
16
recursing and not do so.
21
def recursiveRepr(self, stuff, thunk=repr):
27
return '%s(...)' % (stuff.__class__.__name__,)
30
self.active[ID] = stuff
36
def _contextualize(contextFactory, contextReceiver):
38
Invoke a callable with an argument derived from the current execution
39
context (L{twisted.python.context}), or automatically created if none is
40
yet present in the current context.
42
This function, with a better name and documentation, should probably be
43
somewhere in L{twisted.python.context}. Calling context.get() and
44
context.call() individually is perilous because you always have to handle
45
the case where the value you're looking for isn't present; this idiom
46
forces you to supply some behavior for that case.
48
@param contextFactory: An object which is both a 0-arg callable and
49
hashable; used to look up the value in the context, set the value in the
50
context, and create the value (by being called).
52
@param contextReceiver: A function that receives the value created or
53
identified by contextFactory. It is a 1-arg callable object, called with
54
the result of calling the contextFactory, or retrieving the contextFactory
57
value = context.get(contextFactory, _NOT_SPECIFIED)
58
if value is not _NOT_SPECIFIED:
59
return contextReceiver(value)
61
return context.call({contextFactory: contextFactory()},
62
_contextualize, contextFactory, contextReceiver)
66
class StructBehavior(object):
70
def __init__(self, *args, **kw):
71
super(StructBehavior, self).__init__()
73
# Turn all the args into kwargs
74
if len(args) > len(self.__names__):
76
"Got %d positional arguments but expected no more than %d" %
77
(len(args), len(self.__names__)))
79
for n, v in zip(self.__names__, args):
81
raise TypeError("Got multiple values for argument " + n)
85
for n, v in zip(self.__names__[::-1], self.__defaults__[::-1]):
89
for n in self.__names__:
91
raise TypeError('Specify a value for %r' % (n,))
92
setattr(self, n, kw.pop(n))
95
raise TypeError('Got unexpected arguments: ' + ', '.join(kw))
100
Generate a string representation.
104
def _recordrepr(self2):
106
Internal implementation of repr() for this record.
109
self.__class__.__name__,
111
(n, repr(getattr(self, n, None)))
112
for n in self.__names__]))
113
return rr.recursiveRepr(self, _recordrepr)
114
return _contextualize(_RecursiveReprer, doit)
117
def record(*a, **kw):
119
Are you tired of typing class declarations that look like this::
122
def __init__(self, a=None, b=None, c=None, d=None, e=None,
123
f=None, g=None, h=None, i=None, j=None):
130
Epsilon can help! That's right - for a limited time only, this function
131
returns a class which provides a shortcut. The above can be simplified
134
StuffInfo = record(a=None, b=None, c=None, d=None, e=None,
135
f=None, g=None, h=None, i=None, j=None)
137
if the arguments are required, rather than having defaults, it could be
140
StuffInfo = record('a b c d e f g h i j')
142
Put more formally: C{record} optionally takes one positional argument, a
143
L{str} representing attribute names as whitespace-separated identifiers; it
144
also takes an arbitrary number of keyword arguments, which map attribute
145
names to their default values. If no positional argument is provided, the
146
names of attributes will be inferred from the names of the defaults
150
attributeNames = a[0].split()
153
raise TypeError("Attempted to define a record with no attributes.")
154
attributeNames = kw.keys()
155
attributeNames.sort()
158
"record must be called with zero or one positional arguments")
160
# Work like Python: allow defaults specified backwards from the end
162
for attributeName in attributeNames:
163
default = kw.pop(attributeName, _NOT_SPECIFIED)
165
if default is _NOT_SPECIFIED:
167
"You must specify default values like in Python; "
168
"backwards from the end of the argument list, "
171
defaults.append(default)
172
elif default is not _NOT_SPECIFIED:
173
defaults.append(default)
175
# This space left intentionally blank.
178
raise TypeError("The following defaults did not apply: %r" % (kw,))
180
return type('Record<%s>' % (' '.join(attributeNames),),
182
dict(__names__=attributeNames,
183
__defaults__=defaults))