source: wokkel/compat.py @ 160:33ef849a77d8

Last change on this file since 160:33ef849a77d8 was 160:33ef849a77d8, checked in by Ralph Meijer <ralphm@…>, 9 years ago

Use symbolic constants instead of integers MUC status code.

Instead of using normal constant values for representing MUC status codes,
UserPresence now uses twisted.python.constants to define these. This
makes code better to understand and helps debugging.

If a user presence includes one or more status codes, they are stored in the
mucStatuses attribute, as an instance of Statuses. This replaces the
former statusCodes attribute.

  • Property exe set to *
File size: 11.2 KB
Line 
1# -*- test-case-name: wokkel.test.test_compat -*-
2#
3# Copyright (c) Twisted Matrix Laboratories.
4# See LICENSE for details.
5
6"""
7Compatibility module to provide backwards compatibility with Twisted features.
8"""
9
10__all__ = ['BootstrapMixin', 'XmlStreamServerFactory', 'IQ',
11           'NamedConstant', 'ValueConstant', 'Names', 'Values']
12
13from itertools import count
14
15from twisted.internet import protocol
16from twisted.words.protocols.jabber import xmlstream
17
18class BootstrapMixin(object):
19    """
20    XmlStream factory mixin to install bootstrap event observers.
21
22    This mixin is for factories providing
23    L{IProtocolFactory<twisted.internet.interfaces.IProtocolFactory>} to make
24    sure bootstrap event observers are set up on protocols, before incoming
25    data is processed. Such protocols typically derive from
26    L{utility.EventDispatcher}, like L{XmlStream}.
27
28    You can set up bootstrap event observers using C{addBootstrap}. The
29    C{event} and C{fn} parameters correspond with the C{event} and
30    C{observerfn} arguments to L{utility.EventDispatcher.addObserver}.
31
32    @since: 8.2.
33    @ivar bootstraps: The list of registered bootstrap event observers.
34    @type bootstrap: C{list}
35    """
36
37    def __init__(self):
38        self.bootstraps = []
39
40
41    def installBootstraps(self, dispatcher):
42        """
43        Install registered bootstrap observers.
44
45        @param dispatcher: Event dispatcher to add the observers to.
46        @type dispatcher: L{utility.EventDispatcher}
47        """
48        for event, fn in self.bootstraps:
49            dispatcher.addObserver(event, fn)
50
51
52    def addBootstrap(self, event, fn):
53        """
54        Add a bootstrap event handler.
55
56        @param event: The event to register an observer for.
57        @type event: C{str} or L{xpath.XPathQuery}
58        @param fn: The observer callable to be registered.
59        """
60        self.bootstraps.append((event, fn))
61
62
63    def removeBootstrap(self, event, fn):
64        """
65        Remove a bootstrap event handler.
66
67        @param event: The event the observer is registered for.
68        @type event: C{str} or L{xpath.XPathQuery}
69        @param fn: The registered observer callable.
70        """
71        self.bootstraps.remove((event, fn))
72
73
74
75class XmlStreamServerFactory(BootstrapMixin,
76                             protocol.ServerFactory):
77    """
78    Factory for Jabber XmlStream objects as a server.
79
80    @since: 8.2.
81    @ivar authenticatorFactory: Factory callable that takes no arguments, to
82                                create a fresh authenticator to be associated
83                                with the XmlStream.
84    """
85
86    protocol = xmlstream.XmlStream
87
88    def __init__(self, authenticatorFactory):
89        BootstrapMixin.__init__(self)
90        self.authenticatorFactory = authenticatorFactory
91
92
93    def buildProtocol(self, addr):
94        """
95        Create an instance of XmlStream.
96
97        A new authenticator instance will be created and passed to the new
98        XmlStream. Registered bootstrap event observers are installed as well.
99        """
100        authenticator = self.authenticatorFactory()
101        xs = self.protocol(authenticator)
102        xs.factory = self
103        self.installBootstraps(xs)
104        return xs
105
106
107
108class IQ(xmlstream.IQ):
109    def __init__(self, *args, **kwargs):
110        # Make sure we have a reactor parameter
111        try:
112            reactor = kwargs['reactor']
113        except KeyError:
114            from twisted.internet import reactor
115        kwargs['reactor'] = reactor
116
117        # Check if IQ's init accepts the reactor parameter
118        try:
119            xmlstream.IQ.__init__(self, *args, **kwargs)
120        except TypeError:
121            # Guess not. Remove the reactor parameter and try again.
122            del kwargs['reactor']
123            xmlstream.IQ.__init__(self, *args, **kwargs)
124
125            # Patch the XmlStream instance so that it has a _callLater
126            self._xmlstream._callLater = reactor.callLater
127
128
129
130_unspecified = object()
131_constantOrder = count().next
132
133
134class _Constant(object):
135    """
136    @ivar _index: A C{int} allocated from a shared counter in order to keep
137        track of the order in which L{_Constant}s are instantiated.
138
139    @ivar name: A C{str} giving the name of this constant; only set once the
140        constant is initialized by L{_ConstantsContainer}.
141
142    @ivar _container: The L{_ConstantsContainer} subclass this constant belongs
143        to; only set once the constant is initialized by that subclass.
144    """
145    def __init__(self):
146        self._index = _constantOrder()
147
148
149    def __get__(self, oself, cls):
150        """
151        Ensure this constant has been initialized before returning it.
152        """
153        cls._initializeEnumerants()
154        return self
155
156
157    def __repr__(self):
158        """
159        Return text identifying both which constant this is and which collection
160        it belongs to.
161        """
162        return "<%s=%s>" % (self._container.__name__, self.name)
163
164
165    def _realize(self, container, name, value):
166        """
167        Complete the initialization of this L{_Constant}.
168
169        @param container: The L{_ConstantsContainer} subclass this constant is
170            part of.
171
172        @param name: The name of this constant in its container.
173
174        @param value: The value of this constant; not used, as named constants
175            have no value apart from their identity.
176        """
177        self._container = container
178        self.name = name
179
180
181
182class _EnumerantsInitializer(object):
183    """
184    L{_EnumerantsInitializer} is a descriptor used to initialize a cache of
185    objects representing named constants for a particular L{_ConstantsContainer}
186    subclass.
187    """
188    def __get__(self, oself, cls):
189        """
190        Trigger the initialization of the enumerants cache on C{cls} and then
191        return it.
192        """
193        cls._initializeEnumerants()
194        return cls._enumerants
195
196
197
198class _ConstantsContainer(object):
199    """
200    L{_ConstantsContainer} is a class with attributes used as symbolic
201    constants.  It is up to subclasses to specify what kind of constants are
202    allowed.
203
204    @cvar _constantType: Specified by a L{_ConstantsContainer} subclass to
205        specify the type of constants allowed by that subclass.
206
207    @cvar _enumerantsInitialized: A C{bool} tracking whether C{_enumerants} has
208        been initialized yet or not.
209
210    @cvar _enumerants: A C{dict} mapping the names of constants (eg
211        L{NamedConstant} instances) found in the class definition to those
212        instances.  This is initialized via the L{_EnumerantsInitializer}
213        descriptor the first time it is accessed.
214    """
215    _constantType = None
216
217    _enumerantsInitialized = False
218    _enumerants = _EnumerantsInitializer()
219
220    def __new__(cls):
221        """
222        Classes representing constants containers are not intended to be
223        instantiated.
224
225        The class object itself is used directly.
226        """
227        raise TypeError("%s may not be instantiated." % (cls.__name__,))
228
229
230    def _initializeEnumerants(cls):
231        """
232        Find all of the L{NamedConstant} instances in the definition of C{cls},
233        initialize them with constant values, and build a mapping from their
234        names to them to attach to C{cls}.
235        """
236        if not cls._enumerantsInitialized:
237            constants = []
238            for (name, descriptor) in cls.__dict__.iteritems():
239                if isinstance(descriptor, cls._constantType):
240                    constants.append((descriptor._index, name, descriptor))
241            enumerants = {}
242            for (index, enumerant, descriptor) in constants:
243                value = cls._constantFactory(enumerant)
244                descriptor._realize(cls, enumerant, value)
245                enumerants[enumerant] = descriptor
246            # Replace the _enumerants descriptor with the result so future
247            # access will go directly to the values.  The _enumerantsInitialized
248            # flag is still necessary because NamedConstant.__get__ may also
249            # call this method.
250            cls._enumerants = enumerants
251            cls._enumerantsInitialized = True
252    _initializeEnumerants = classmethod(_initializeEnumerants)
253
254
255    def _constantFactory(cls, name):
256        """
257        Construct the value for a new constant to add to this container.
258
259        @param name: The name of the constant to create.
260
261        @return: L{NamedConstant} instances have no value apart from identity,
262            so return a meaningless dummy value.
263        """
264        return _unspecified
265    _constantFactory = classmethod(_constantFactory)
266
267
268    def lookupByName(cls, name):
269        """
270        Retrieve a constant by its name or raise a C{ValueError} if there is no
271        constant associated with that name.
272
273        @param name: A C{str} giving the name of one of the constants defined by
274            C{cls}.
275
276        @raise ValueError: If C{name} is not the name of one of the constants
277            defined by C{cls}.
278
279        @return: The L{NamedConstant} associated with C{name}.
280        """
281        if name in cls._enumerants:
282            return getattr(cls, name)
283        raise ValueError(name)
284    lookupByName = classmethod(lookupByName)
285
286
287    def iterconstants(cls):
288        """
289        Iteration over a L{Names} subclass results in all of the constants it
290        contains.
291
292        @return: an iterator the elements of which are the L{NamedConstant}
293            instances defined in the body of this L{Names} subclass.
294        """
295        constants = cls._enumerants.values()
296        constants.sort(key=lambda descriptor: descriptor._index)
297        return iter(constants)
298    iterconstants = classmethod(iterconstants)
299
300
301
302class NamedConstant(_Constant):
303    """
304    L{NamedConstant} defines an attribute to be a named constant within a
305    collection defined by a L{Names} subclass.
306
307    L{NamedConstant} is only for use in the definition of L{Names}
308    subclasses.  Do not instantiate L{NamedConstant} elsewhere and do not
309    subclass it.
310    """
311
312
313
314class Names(_ConstantsContainer):
315    """
316    A L{Names} subclass contains constants which differ only in their names and
317    identities.
318    """
319    _constantType = NamedConstant
320
321
322
323class ValueConstant(_Constant):
324    """
325    L{ValueConstant} defines an attribute to be a named constant within a
326    collection defined by a L{Values} subclass.
327
328    L{ValueConstant} is only for use in the definition of L{Values} subclasses.
329    Do not instantiate L{ValueConstant} elsewhere and do not subclass it.
330    """
331    def __init__(self, value):
332        _Constant.__init__(self)
333        self.value = value
334
335
336
337class Values(_ConstantsContainer):
338    """
339    A L{Values} subclass contains constants which are associated with arbitrary
340    values.
341    """
342    _constantType = ValueConstant
343
344    def lookupByValue(cls, value):
345        """
346        Retrieve a constant by its value or raise a C{ValueError} if there is no
347        constant associated with that value.
348
349        @param value: The value of one of the constants defined by C{cls}.
350
351        @raise ValueError: If C{value} is not the value of one of the constants
352            defined by C{cls}.
353
354        @return: The L{ValueConstant} associated with C{value}.
355        """
356        for constant in cls.iterconstants():
357            if constant.value == value:
358                return constant
359        raise ValueError(value)
360    lookupByValue = classmethod(lookupByValue)
Note: See TracBrowser for help on using the repository browser.