From 9a2e25c370fff0470cb7c9b6ebaf507aeeb97514 Mon Sep 17 00:00:00 2001 From: John Eskew Date: Mon, 30 Oct 2017 14:33:25 -0400 Subject: [PATCH] Add derived/derived_dict_entry/derive_settings and tests. - Enables a method of deriving Django settings from other Django settings after all other Django settings are stable. --- openedx/core/lib/derived.py | 69 ++++++++++++++++++++++++++ openedx/core/lib/tests/test_derived.py | 44 ++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 openedx/core/lib/derived.py create mode 100644 openedx/core/lib/tests/test_derived.py diff --git a/openedx/core/lib/derived.py b/openedx/core/lib/derived.py new file mode 100644 index 0000000000..63497e6756 --- /dev/null +++ b/openedx/core/lib/derived.py @@ -0,0 +1,69 @@ +""" +Allows the registration of Django/Python settings that are derived from other settings +via callable methods/lambdas. The derivation time can be controlled to happen after all +other settings have been set. The derived setting can also be overridden by setting the +derived setting to an actual value. +""" +import six +import sys + +# Global list holding all settings which will be derived. +__DERIVED = [] + + +def derived(*settings): + """ + Registers settings which are derived from other settings. + Can be called multiple times to add more derived settings. + + Args: + settings (list): List of setting names to register. + """ + __DERIVED.extend(settings) + + +def derived_dict_entry(setting_dict, key): + """ + Registers a setting which is a dictionary and needs a derived value for a particular key. + Can be called multiple times to add more derived settings. + + Args: + setting_dict (str): Name of setting which contains a dictionary. + key (str): Name of key in the setting dictionary which will be derived. + """ + __DERIVED.append((setting_dict, key)) + + +def derive_settings(module_name): + """ + Derives all registered settings and sets them onto a particular module. + Skips deriving settings that are set to a value. + + Args: + module_name (str): Name of module to which the derived settings will be added. + """ + module = sys.modules[module_name] + for derived in __DERIVED: + if isinstance(derived, six.string_types): + setting = getattr(module, derived) + if callable(setting): + setting_val = setting(module) + setattr(module, derived, setting_val) + elif isinstance(derived, tuple): + # If a tuple, two elements are expected - else ignore. + if len(derived) == 2: + # Both elements are expected to be strings. + # The first string is the attribute which is expected to be a dictionary. + # The second string is a key in that dictionary containing a derived setting. + setting = getattr(module, derived[0])[derived[1]] + if callable(setting): + setting_val = setting(module) + getattr(module, derived[0]).update({derived[1]: setting_val}) + + +def clear_for_tests(): + """ + Clears all settings to be derived. For tests only. + """ + global __DERIVED + __DERIVED = [] diff --git a/openedx/core/lib/tests/test_derived.py b/openedx/core/lib/tests/test_derived.py new file mode 100644 index 0000000000..c42a20bdee --- /dev/null +++ b/openedx/core/lib/tests/test_derived.py @@ -0,0 +1,44 @@ +""" +Tests for derived.py +""" + +import sys +from unittest import TestCase +from openedx.core.lib.derived import derived, derive_settings, clear_for_tests + + +class TestDerivedSettings(TestCase): + """ + Test settings that are derived from other settings. + """ + def setUp(self): + super(TestDerivedSettings, self).setUp() + clear_for_tests() + self.module = sys.modules[__name__] + self.module.SIMPLE_VALUE = 'paneer' + self.module.DERIVED_VALUE = lambda settings: 'mutter ' + settings.SIMPLE_VALUE + self.module.ANOTHER_DERIVED_VALUE = lambda settings: settings.DERIVED_VALUE + ' with naan' + self.module.UNREGISTERED_DERIVED_VALUE = lambda settings: settings.SIMPLE_VALUE + ' is cheese' + derived('DERIVED_VALUE', 'ANOTHER_DERIVED_VALUE') + self.module.DICT_VALUE = {} + self.module.DICT_VALUE['test_key'] = lambda settings: settings.DERIVED_VALUE * 3 + derived(('DICT_VALUE', 'test_key')) + + def test_derived_settings_are_derived(self): + derive_settings(__name__) + self.assertEqual(self.module.DERIVED_VALUE, 'mutter paneer') + self.assertEqual(self.module.ANOTHER_DERIVED_VALUE, 'mutter paneer with naan') + + def test_unregistered_derived_settings(self): + derive_settings(__name__) + self.assertTrue(callable(self.module.UNREGISTERED_DERIVED_VALUE)) + + def test_derived_settings_overridden(self): + self.module.DERIVED_VALUE = 'aloo gobi' + derive_settings(__name__) + self.assertEqual(self.module.DERIVED_VALUE, 'aloo gobi') + self.assertEqual(self.module.ANOTHER_DERIVED_VALUE, 'aloo gobi with naan') + + def test_derived_dict_settings(self): + derive_settings(__name__) + self.assertEqual(self.module.DICT_VALUE['test_key'], 'mutter paneermutter paneermutter paneer')