]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
util: Enable multiple configurable bases in one hierarchy
authorBen Darnell <ben@bendarnell.com>
Sat, 27 May 2017 23:10:45 +0000 (19:10 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 27 May 2017 23:10:45 +0000 (19:10 -0400)
This allows a mid-level class like PollIOLoop to have its own
configurability.

tornado/test/util_test.py
tornado/util.py

index 459cb9c327164f20ad7e8b81436fc7b984aefe32..48924d44beddf6e00fd41d4d014442e21c29f462 100644 (file)
@@ -58,12 +58,35 @@ class TestConfig2(TestConfigurable):
         self.pos_arg = pos_arg
 
 
+class TestConfig3(TestConfigurable):
+    # TestConfig3 is a configuration option that is itself configurable.
+    @classmethod
+    def configurable_base(cls):
+        return TestConfig3
+
+    @classmethod
+    def configurable_default(cls):
+        return TestConfig3A
+
+
+class TestConfig3A(TestConfig3):
+    def initialize(self, a=None):
+        self.a = a
+
+
+class TestConfig3B(TestConfig3):
+    def initialize(self, b=None):
+        self.b = b
+
+
 class ConfigurableTest(unittest.TestCase):
     def setUp(self):
         self.saved = TestConfigurable._save_configuration()
+        self.saved3 = TestConfig3._save_configuration()
 
     def tearDown(self):
         TestConfigurable._restore_configuration(self.saved)
+        TestConfig3._restore_configuration(self.saved3)
 
     def checkSubclasses(self):
         # no matter how the class is configured, it should always be
@@ -131,6 +154,39 @@ class ConfigurableTest(unittest.TestCase):
         obj = TestConfig2()
         self.assertIs(obj.b, None)
 
+    def test_config_multi_level(self):
+        TestConfigurable.configure(TestConfig3, a=1)
+        obj = TestConfigurable()
+        self.assertIsInstance(obj, TestConfig3A)
+        self.assertEqual(obj.a, 1)
+
+        TestConfigurable.configure(TestConfig3)
+        TestConfig3.configure(TestConfig3B, b=2)
+        obj = TestConfigurable()
+        self.assertIsInstance(obj, TestConfig3B)
+        self.assertEqual(obj.b, 2)
+
+    def test_config_inner_level(self):
+        # The inner level can be used even when the outer level
+        # doesn't point to it.
+        obj = TestConfig3()
+        self.assertIsInstance(obj, TestConfig3A)
+
+        TestConfig3.configure(TestConfig3B)
+        obj = TestConfig3()
+        self.assertIsInstance(obj, TestConfig3B)
+
+        # Configuring the base doesn't configure the inner.
+        obj = TestConfigurable()
+        self.assertIsInstance(obj, TestConfig1)
+        TestConfigurable.configure(TestConfig2)
+
+        obj = TestConfigurable()
+        self.assertIsInstance(obj, TestConfig2)
+
+        obj = TestConfig3()
+        self.assertIsInstance(obj, TestConfig3B)
+
 
 class UnicodeLiteralTest(unittest.TestCase):
     def test_unicode_escapes(self):
index 981b94c8eaeba783f832e6c0480ec1f0485415d3..bfd80beb6fce111191e0c37e72c9d66b155d407a 100644 (file)
@@ -286,6 +286,9 @@ class Configurable(object):
         else:
             impl = cls
         init_kwargs.update(kwargs)
+        if impl.configurable_base() is not base:
+            # The impl class is itself configurable, so recurse.
+            return impl(*args, **init_kwargs)
         instance = super(Configurable, cls).__new__(impl)
         # initialize vs __init__ chosen for compatibility with AsyncHTTPClient
         # singleton magic.  If we get rid of that we can switch to __init__
@@ -343,7 +346,10 @@ class Configurable(object):
         # type: () -> type
         """Returns the currently configured class."""
         base = cls.configurable_base()
-        if cls.__impl_class is None:
+        # Manually mangle the private name to see whether this base
+        # has been configured (and not another base higher in the
+        # hierarchy).
+        if base.__dict__.get('_Configurable__impl_class') is None:
             base.__impl_class = cls.configurable_default()
         return base.__impl_class