source:
ralphm-patches/py3-compat.patch
@
78:361e2111a663
Last change on this file since 78:361e2111a663 was 78:361e2111a663, checked in by Ralph Meijer <ralphm@…>, 6 years ago | |
---|---|
File size: 26.0 KB |
-
wokkel/compat.py
# HG changeset patch # Parent 159b460dc1f08608eff6df1e474761cddca4c2d0 diff --git a/wokkel/compat.py b/wokkel/compat.py
a b 7 7 Compatibility module to provide backwards compatibility with Twisted features. 8 8 """ 9 9 10 __all__ = ['BootstrapMixin', 'XmlStreamServerFactory', 'IQ', 11 'NamedConstant', 'ValueConstant', 'Names', 'Values'] 12 13 from itertools import count 10 from __future__ import division, absolute_import 14 11 15 12 from twisted.python.deprecate import deprecatedModuleAttribute 16 13 from twisted.python.versions import Version 17 14 from twisted.words.protocols.jabber import xmlstream 18 from twisted.words.protocols.jabber.xmlstream import XmlStreamServerFactory19 from twisted.words.xish.xmlstream import BootstrapMixin20 21 deprecatedModuleAttribute(22 Version("Wokkel", 0, 7, 0),23 "Use twisted.words.xish.xmlstream.BootstrapMixin instead.",24 __name__,25 "BootstrapMixin")26 27 deprecatedModuleAttribute(28 Version("Wokkel", 0, 7, 0),29 "Use twisted.words.protocols.jabber.xmlstream.XmlStreamServerFactory "30 "instead.",31 __name__,32 "XmlStreamServerFactory")33 15 34 16 class IQ(xmlstream.IQ): 35 17 def __init__(self, *args, **kwargs): … … 53 35 54 36 55 37 56 _unspecified = object() 57 _constantOrder = count().next 58 59 60 class _Constant(object): 61 """ 62 @ivar _index: A C{int} allocated from a shared counter in order to keep 63 track of the order in which L{_Constant}s are instantiated. 64 65 @ivar name: A C{str} giving the name of this constant; only set once the 66 constant is initialized by L{_ConstantsContainer}. 67 68 @ivar _container: The L{_ConstantsContainer} subclass this constant belongs 69 to; only set once the constant is initialized by that subclass. 70 71 @since: Twisted 12.0.0. 72 """ 73 def __init__(self): 74 self._index = _constantOrder() 75 76 77 def __get__(self, oself, cls): 78 """ 79 Ensure this constant has been initialized before returning it. 80 """ 81 cls._initializeEnumerants() 82 return self 83 84 85 def __repr__(self): 86 """ 87 Return text identifying both which constant this is and which collection 88 it belongs to. 89 """ 90 return "<%s=%s>" % (self._container.__name__, self.name) 91 92 93 def _realize(self, container, name, value): 94 """ 95 Complete the initialization of this L{_Constant}. 96 97 @param container: The L{_ConstantsContainer} subclass this constant is 98 part of. 99 100 @param name: The name of this constant in its container. 101 102 @param value: The value of this constant; not used, as named constants 103 have no value apart from their identity. 104 """ 105 self._container = container 106 self.name = name 107 108 109 110 class _EnumerantsInitializer(object): 111 """ 112 L{_EnumerantsInitializer} is a descriptor used to initialize a cache of 113 objects representing named constants for a particular L{_ConstantsContainer} 114 subclass. 115 116 @since: Twisted 12.0.0. 117 """ 118 def __get__(self, oself, cls): 119 """ 120 Trigger the initialization of the enumerants cache on C{cls} and then 121 return it. 122 """ 123 cls._initializeEnumerants() 124 return cls._enumerants 125 126 127 128 class _ConstantsContainer(object): 129 """ 130 L{_ConstantsContainer} is a class with attributes used as symbolic 131 constants. It is up to subclasses to specify what kind of constants are 132 allowed. 133 134 @cvar _constantType: Specified by a L{_ConstantsContainer} subclass to 135 specify the type of constants allowed by that subclass. 136 137 @cvar _enumerantsInitialized: A C{bool} tracking whether C{_enumerants} has 138 been initialized yet or not. 139 140 @cvar _enumerants: A C{dict} mapping the names of constants (eg 141 L{NamedConstant} instances) found in the class definition to those 142 instances. This is initialized via the L{_EnumerantsInitializer} 143 descriptor the first time it is accessed. 144 145 @since: Twisted 12.0.0. 146 """ 147 _constantType = None 148 149 _enumerantsInitialized = False 150 _enumerants = _EnumerantsInitializer() 151 152 def __new__(cls): 153 """ 154 Classes representing constants containers are not intended to be 155 instantiated. 156 157 The class object itself is used directly. 158 """ 159 raise TypeError("%s may not be instantiated." % (cls.__name__,)) 160 161 162 def _initializeEnumerants(cls): 163 """ 164 Find all of the L{NamedConstant} instances in the definition of C{cls}, 165 initialize them with constant values, and build a mapping from their 166 names to them to attach to C{cls}. 167 """ 168 if not cls._enumerantsInitialized: 169 constants = [] 170 for (name, descriptor) in cls.__dict__.iteritems(): 171 if isinstance(descriptor, cls._constantType): 172 constants.append((descriptor._index, name, descriptor)) 173 enumerants = {} 174 for (index, enumerant, descriptor) in constants: 175 value = cls._constantFactory(enumerant) 176 descriptor._realize(cls, enumerant, value) 177 enumerants[enumerant] = descriptor 178 # Replace the _enumerants descriptor with the result so future 179 # access will go directly to the values. The _enumerantsInitialized 180 # flag is still necessary because NamedConstant.__get__ may also 181 # call this method. 182 cls._enumerants = enumerants 183 cls._enumerantsInitialized = True 184 _initializeEnumerants = classmethod(_initializeEnumerants) 185 186 187 def _constantFactory(cls, name): 188 """ 189 Construct the value for a new constant to add to this container. 190 191 @param name: The name of the constant to create. 192 193 @return: L{NamedConstant} instances have no value apart from identity, 194 so return a meaningless dummy value. 195 """ 196 return _unspecified 197 _constantFactory = classmethod(_constantFactory) 198 199 200 def lookupByName(cls, name): 201 """ 202 Retrieve a constant by its name or raise a C{ValueError} if there is no 203 constant associated with that name. 204 205 @param name: A C{str} giving the name of one of the constants defined by 206 C{cls}. 207 208 @raise ValueError: If C{name} is not the name of one of the constants 209 defined by C{cls}. 210 211 @return: The L{NamedConstant} associated with C{name}. 212 """ 213 if name in cls._enumerants: 214 return getattr(cls, name) 215 raise ValueError(name) 216 lookupByName = classmethod(lookupByName) 217 218 219 def iterconstants(cls): 220 """ 221 Iteration over a L{Names} subclass results in all of the constants it 222 contains. 223 224 @return: an iterator the elements of which are the L{NamedConstant} 225 instances defined in the body of this L{Names} subclass. 226 """ 227 constants = cls._enumerants.values() 228 constants.sort(key=lambda descriptor: descriptor._index) 229 return iter(constants) 230 iterconstants = classmethod(iterconstants) 231 232 233 234 class NamedConstant(_Constant): 235 """ 236 L{NamedConstant} defines an attribute to be a named constant within a 237 collection defined by a L{Names} subclass. 238 239 L{NamedConstant} is only for use in the definition of L{Names} 240 subclasses. Do not instantiate L{NamedConstant} elsewhere and do not 241 subclass it. 242 243 @since: Twisted 12.0.0. 244 """ 245 246 247 248 class Names(_ConstantsContainer): 249 """ 250 A L{Names} subclass contains constants which differ only in their names and 251 identities. 252 253 @since: Twisted 12.0.0. 254 """ 255 _constantType = NamedConstant 256 257 258 259 class ValueConstant(_Constant): 260 """ 261 L{ValueConstant} defines an attribute to be a named constant within a 262 collection defined by a L{Values} subclass. 263 264 L{ValueConstant} is only for use in the definition of L{Values} subclasses. 265 Do not instantiate L{ValueConstant} elsewhere and do not subclass it. 266 267 @since: Twisted 12.0.0. 268 """ 269 def __init__(self, value): 270 _Constant.__init__(self) 271 self.value = value 272 273 274 275 class Values(_ConstantsContainer): 276 """ 277 A L{Values} subclass contains constants which are associated with arbitrary 278 values. 279 280 @since: Twisted 12.0.0. 281 """ 282 _constantType = ValueConstant 283 284 def lookupByValue(cls, value): 285 """ 286 Retrieve a constant by its value or raise a C{ValueError} if there is no 287 constant associated with that value. 288 289 @param value: The value of one of the constants defined by C{cls}. 290 291 @raise ValueError: If C{value} is not the value of one of the constants 292 defined by C{cls}. 293 294 @return: The L{ValueConstant} associated with C{value}. 295 """ 296 for constant in cls.iterconstants(): 297 if constant.value == value: 298 return constant 299 raise ValueError(value) 300 lookupByValue = classmethod(lookupByValue) 38 __all__ = ['IQ'] -
wokkel/muc.py
diff --git a/wokkel/muc.py b/wokkel/muc.py
a b 11 11 """ 12 12 from dateutil.tz import tzutc 13 13 14 from zope.interface import implement s14 from zope.interface import implementer 15 15 16 16 from twisted.internet import defer 17 from twisted.python.constants import Values, ValueConstant 17 18 from twisted.words.protocols.jabber import jid, error, xmlstream 18 19 from twisted.words.xish import domish 19 20 20 21 from wokkel import data_form, generic, iwokkel, xmppim 21 from wokkel.compat import Values, ValueConstant22 22 from wokkel.delay import Delay, DelayMixin 23 23 from wokkel.subprotocols import XMPPHandler 24 24 from wokkel.iwokkel import IMUCClient … … 64 64 REMOVED_SHUTDOWN = ValueConstant(332) 65 65 66 66 67 @implementer(iwokkel.IMUCStatuses) 67 68 class Statuses(set): 68 69 """ 69 70 Container of MUC status conditions. … … 75 76 cater for extensible status conditions, as defined in 76 77 U{XEP-0306<http://xmpp.org/extensions/xep-0306.html>}. 77 78 """ 78 implements(iwokkel.IMUCStatuses)79 79 80 80 81 81 … … 1203 1203 1204 1204 1205 1205 1206 @implementer(IMUCClient) 1206 1207 class MUCClient(MUCClientProtocol): 1207 1208 """ 1208 1209 Multi-User Chat client protocol. … … 1215 1216 @type _rooms: C{dict} 1216 1217 """ 1217 1218 1218 implements(IMUCClient)1219 1220 1219 def __init__(self, reactor=None): 1221 1220 MUCClientProtocol.__init__(self, reactor) 1222 1221 -
wokkel/test/test_compat.py
diff --git a/wokkel/test/test_compat.py b/wokkel/test/test_compat.py
a b 6 6 Tests for L{wokkel.compat}. 7 7 """ 8 8 9 from zope.interface import implements 9 from __future__ import division, absolute_import 10 11 from zope.interface import implementer 10 12 from twisted.internet import task 11 13 from twisted.internet.interfaces import IReactorTime 12 14 from twisted.trial import unittest 13 15 from twisted.words.protocols.jabber import xmlstream 14 16 15 17 from wokkel.compat import IQ 16 from wokkel.compat import NamedConstant, Names, ValueConstant, Values17 18 18 class DeprecationTest(unittest.TestCase): 19 """ 20 Deprecation tests for L{wokkel.compat}. 21 """ 22 23 def lookForDeprecationWarning(self, testmethod, attributeName, newName): 24 """ 25 Importing C{testmethod} emits a deprecation warning. 26 """ 27 warningsShown = self.flushWarnings([testmethod]) 28 self.assertEqual(len(warningsShown), 1) 29 self.assertIdentical(warningsShown[0]['category'], DeprecationWarning) 30 self.assertEqual( 31 warningsShown[0]['message'], 32 "wokkel.compat." + attributeName + " " 33 "was deprecated in Wokkel 0.7.0: Use " + newName + " instead.") 34 35 36 def test_bootstrapMixinTest(self): 37 """ 38 L{compat.BootstrapMixin} is deprecated. 39 """ 40 from wokkel.compat import BootstrapMixin 41 BootstrapMixin 42 self.lookForDeprecationWarning( 43 self.test_bootstrapMixinTest, 44 "BootstrapMixin", 45 "twisted.words.xish.xmlstream.BootstrapMixin") 46 47 48 def test_xmlStreamServerFactory(self): 49 """ 50 L{compat.XmlStreamServerFactory} is deprecated. 51 """ 52 from wokkel.compat import XmlStreamServerFactory 53 XmlStreamServerFactory 54 self.lookForDeprecationWarning( 55 self.test_xmlStreamServerFactory, 56 "XmlStreamServerFactory", 57 "twisted.words.protocols.jabber.xmlstream." 58 "XmlStreamServerFactory") 59 60 61 19 @implementer(IReactorTime) 62 20 class FakeReactor(object): 63 21 64 implements(IReactorTime)65 22 def __init__(self): 66 23 self.clock = task.Clock() 67 24 self.callLater = self.clock.callLater … … 97 54 self.assertFalse(self.reactor.getDelayedCalls()) 98 55 self.assertFalse(xs.iqDeferreds) 99 56 return d 100 101 102 103 class NamedConstantTests(unittest.TestCase):104 """105 Tests for the L{twisted.python.constants.NamedConstant} class which is used106 to represent individual values.107 """108 def setUp(self):109 """110 Create a dummy container into which constants can be placed.111 """112 class foo(Names):113 pass114 self.container = foo115 116 117 def test_name(self):118 """119 The C{name} attribute of a L{NamedConstant} refers to the value passed120 for the C{name} parameter to C{_realize}.121 """122 name = NamedConstant()123 name._realize(self.container, "bar", None)124 self.assertEqual("bar", name.name)125 126 127 def test_representation(self):128 """129 The string representation of an instance of L{NamedConstant} includes130 the container the instances belongs to as well as the instance's name.131 """132 name = NamedConstant()133 name._realize(self.container, "bar", None)134 self.assertEqual("<foo=bar>", repr(name))135 136 137 def test_equality(self):138 """139 A L{NamedConstant} instance compares equal to itself.140 """141 name = NamedConstant()142 name._realize(self.container, "bar", None)143 self.assertTrue(name == name)144 self.assertFalse(name != name)145 146 147 def test_nonequality(self):148 """149 Two different L{NamedConstant} instances do not compare equal to each150 other.151 """152 first = NamedConstant()153 first._realize(self.container, "bar", None)154 second = NamedConstant()155 second._realize(self.container, "bar", None)156 self.assertFalse(first == second)157 self.assertTrue(first != second)158 159 160 def test_hash(self):161 """162 Because two different L{NamedConstant} instances do not compare as equal163 to each other, they also have different hashes to avoid collisions when164 added to a C{dict} or C{set}.165 """166 first = NamedConstant()167 first._realize(self.container, "bar", None)168 second = NamedConstant()169 second._realize(self.container, "bar", None)170 self.assertNotEqual(hash(first), hash(second))171 172 173 174 class _ConstantsTestsMixin(object):175 """176 Mixin defining test helpers common to multiple types of constants177 collections.178 """179 def _notInstantiableTest(self, name, cls):180 """181 Assert that an attempt to instantiate the constants class raises182 C{TypeError}.183 184 @param name: A C{str} giving the name of the constants collection.185 @param cls: The constants class to test.186 """187 exc = self.assertRaises(TypeError, cls)188 self.assertEqual(name + " may not be instantiated.", str(exc))189 190 191 192 class NamesTests(unittest.TestCase, _ConstantsTestsMixin):193 """194 Tests for L{twisted.python.constants.Names}, a base class for containers of195 related constaints.196 """197 def setUp(self):198 """199 Create a fresh new L{Names} subclass for each unit test to use. Since200 L{Names} is stateful, re-using the same subclass across test methods201 makes exercising all of the implementation code paths difficult.202 """203 class METHOD(Names):204 """205 A container for some named constants to use in unit tests for206 L{Names}.207 """208 GET = NamedConstant()209 PUT = NamedConstant()210 POST = NamedConstant()211 DELETE = NamedConstant()212 213 self.METHOD = METHOD214 215 216 def test_notInstantiable(self):217 """218 A subclass of L{Names} raises C{TypeError} if an attempt is made to219 instantiate it.220 """221 self._notInstantiableTest("METHOD", self.METHOD)222 223 224 def test_symbolicAttributes(self):225 """226 Each name associated with a L{NamedConstant} instance in the definition227 of a L{Names} subclass is available as an attribute on the resulting228 class.229 """230 self.assertTrue(hasattr(self.METHOD, "GET"))231 self.assertTrue(hasattr(self.METHOD, "PUT"))232 self.assertTrue(hasattr(self.METHOD, "POST"))233 self.assertTrue(hasattr(self.METHOD, "DELETE"))234 235 236 def test_withoutOtherAttributes(self):237 """238 As usual, names not defined in the class scope of a L{Names}239 subclass are not available as attributes on the resulting class.240 """241 self.assertFalse(hasattr(self.METHOD, "foo"))242 243 244 def test_representation(self):245 """246 The string representation of a constant on a L{Names} subclass includes247 the name of the L{Names} subclass and the name of the constant itself.248 """249 self.assertEqual("<METHOD=GET>", repr(self.METHOD.GET))250 251 252 def test_lookupByName(self):253 """254 Constants can be looked up by name using L{Names.lookupByName}.255 """256 method = self.METHOD.lookupByName("GET")257 self.assertIdentical(self.METHOD.GET, method)258 259 260 def test_notLookupMissingByName(self):261 """262 Names not defined with a L{NamedConstant} instance cannot be looked up263 using L{Names.lookupByName}.264 """265 self.assertRaises(ValueError, self.METHOD.lookupByName, "lookupByName")266 self.assertRaises(ValueError, self.METHOD.lookupByName, "__init__")267 self.assertRaises(ValueError, self.METHOD.lookupByName, "foo")268 269 270 def test_name(self):271 """272 The C{name} attribute of one of the named constants gives that273 constant's name.274 """275 self.assertEqual("GET", self.METHOD.GET.name)276 277 278 def test_attributeIdentity(self):279 """280 Repeated access of an attribute associated with a L{NamedConstant} value281 in a L{Names} subclass results in the same object.282 """283 self.assertIdentical(self.METHOD.GET, self.METHOD.GET)284 285 286 def test_iterconstants(self):287 """288 L{Names.iterconstants} returns an iterator over all of the constants289 defined in the class, in the order they were defined.290 """291 constants = list(self.METHOD.iterconstants())292 self.assertEqual(293 [self.METHOD.GET, self.METHOD.PUT,294 self.METHOD.POST, self.METHOD.DELETE],295 constants)296 297 298 def test_attributeIterconstantsIdentity(self):299 """300 The constants returned from L{Names.iterconstants} are identical to the301 constants accessible using attributes.302 """303 constants = list(self.METHOD.iterconstants())304 self.assertIdentical(self.METHOD.GET, constants[0])305 self.assertIdentical(self.METHOD.PUT, constants[1])306 self.assertIdentical(self.METHOD.POST, constants[2])307 self.assertIdentical(self.METHOD.DELETE, constants[3])308 309 310 def test_iterconstantsIdentity(self):311 """312 The constants returned from L{Names.iterconstants} are identical on each313 call to that method.314 """315 constants = list(self.METHOD.iterconstants())316 again = list(self.METHOD.iterconstants())317 self.assertIdentical(again[0], constants[0])318 self.assertIdentical(again[1], constants[1])319 self.assertIdentical(again[2], constants[2])320 self.assertIdentical(again[3], constants[3])321 322 323 def test_initializedOnce(self):324 """325 L{Names._enumerants} is initialized once and its value re-used on326 subsequent access.327 """328 first = self.METHOD._enumerants329 self.METHOD.GET # Side-effects!330 second = self.METHOD._enumerants331 self.assertIdentical(first, second)332 333 334 335 class ValuesTests(unittest.TestCase, _ConstantsTestsMixin):336 """337 Tests for L{twisted.python.constants.Names}, a base class for containers of338 related constaints with arbitrary values.339 """340 def setUp(self):341 """342 Create a fresh new L{Values} subclass for each unit test to use. Since343 L{Values} is stateful, re-using the same subclass across test methods344 makes exercising all of the implementation code paths difficult.345 """346 class STATUS(Values):347 OK = ValueConstant("200")348 NOT_FOUND = ValueConstant("404")349 350 self.STATUS = STATUS351 352 353 def test_notInstantiable(self):354 """355 A subclass of L{Values} raises C{TypeError} if an attempt is made to356 instantiate it.357 """358 self._notInstantiableTest("STATUS", self.STATUS)359 360 361 def test_symbolicAttributes(self):362 """363 Each name associated with a L{ValueConstant} instance in the definition364 of a L{Values} subclass is available as an attribute on the resulting365 class.366 """367 self.assertTrue(hasattr(self.STATUS, "OK"))368 self.assertTrue(hasattr(self.STATUS, "NOT_FOUND"))369 370 371 def test_withoutOtherAttributes(self):372 """373 As usual, names not defined in the class scope of a L{Values}374 subclass are not available as attributes on the resulting class.375 """376 self.assertFalse(hasattr(self.STATUS, "foo"))377 378 379 def test_representation(self):380 """381 The string representation of a constant on a L{Values} subclass includes382 the name of the L{Values} subclass and the name of the constant itself.383 """384 self.assertEqual("<STATUS=OK>", repr(self.STATUS.OK))385 386 387 def test_lookupByName(self):388 """389 Constants can be looked up by name using L{Values.lookupByName}.390 """391 method = self.STATUS.lookupByName("OK")392 self.assertIdentical(self.STATUS.OK, method)393 394 395 def test_notLookupMissingByName(self):396 """397 Names not defined with a L{ValueConstant} instance cannot be looked up398 using L{Values.lookupByName}.399 """400 self.assertRaises(ValueError, self.STATUS.lookupByName, "lookupByName")401 self.assertRaises(ValueError, self.STATUS.lookupByName, "__init__")402 self.assertRaises(ValueError, self.STATUS.lookupByName, "foo")403 404 405 def test_lookupByValue(self):406 """407 Constants can be looked up by their associated value, defined by the408 argument passed to L{ValueConstant}, using L{Values.lookupByValue}.409 """410 status = self.STATUS.lookupByValue("200")411 self.assertIdentical(self.STATUS.OK, status)412 413 414 def test_lookupDuplicateByValue(self):415 """416 If more than one constant is associated with a particular value,417 L{Values.lookupByValue} returns whichever of them is defined first.418 """419 class TRANSPORT_MESSAGE(Values):420 """421 Message types supported by an SSH transport.422 """423 KEX_DH_GEX_REQUEST_OLD = ValueConstant(30)424 KEXDH_INIT = ValueConstant(30)425 426 self.assertIdentical(427 TRANSPORT_MESSAGE.lookupByValue(30),428 TRANSPORT_MESSAGE.KEX_DH_GEX_REQUEST_OLD)429 430 431 def test_notLookupMissingByValue(self):432 """433 L{Values.lookupByValue} raises L{ValueError} when called with a value434 with which no constant is associated.435 """436 self.assertRaises(ValueError, self.STATUS.lookupByValue, "OK")437 self.assertRaises(ValueError, self.STATUS.lookupByValue, 200)438 self.assertRaises(ValueError, self.STATUS.lookupByValue, "200.1")439 440 441 def test_name(self):442 """443 The C{name} attribute of one of the constants gives that constant's444 name.445 """446 self.assertEqual("OK", self.STATUS.OK.name)447 448 449 def test_attributeIdentity(self):450 """451 Repeated access of an attribute associated with a L{ValueConstant} value452 in a L{Values} subclass results in the same object.453 """454 self.assertIdentical(self.STATUS.OK, self.STATUS.OK)455 456 457 def test_iterconstants(self):458 """459 L{Values.iterconstants} returns an iterator over all of the constants460 defined in the class, in the order they were defined.461 """462 constants = list(self.STATUS.iterconstants())463 self.assertEqual(464 [self.STATUS.OK, self.STATUS.NOT_FOUND],465 constants)466 467 468 def test_attributeIterconstantsIdentity(self):469 """470 The constants returned from L{Values.iterconstants} are identical to the471 constants accessible using attributes.472 """473 constants = list(self.STATUS.iterconstants())474 self.assertIdentical(self.STATUS.OK, constants[0])475 self.assertIdentical(self.STATUS.NOT_FOUND, constants[1])476 477 478 def test_iterconstantsIdentity(self):479 """480 The constants returned from L{Values.iterconstants} are identical on481 each call to that method.482 """483 constants = list(self.STATUS.iterconstants())484 again = list(self.STATUS.iterconstants())485 self.assertIdentical(again[0], constants[0])486 self.assertIdentical(again[1], constants[1])487 488 489 def test_initializedOnce(self):490 """491 L{Values._enumerants} is initialized once and its value re-used on492 subsequent access.493 """494 first = self.STATUS._enumerants495 self.STATUS.OK # Side-effects!496 second = self.STATUS._enumerants497 self.assertIdentical(first, second)
Note: See TracBrowser
for help on using the repository browser.