From 511d541c9f2544ea07825d8d622ba4ecfcaec0ca Mon Sep 17 00:00:00 2001 From: "Kyle D. McCormick" Date: Tue, 28 Jan 2025 15:15:56 -0500 Subject: [PATCH] feat: Make dump for fns, classes more stable and helpful --- .../util/management/commands/dump_settings.py | 25 ++++++++++++------- .../util/tests/test_dump_settings.py | 8 +++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/openedx/core/djangoapps/util/management/commands/dump_settings.py b/openedx/core/djangoapps/util/management/commands/dump_settings.py index 1f9949ffbdaf..965ea16c6d27 100644 --- a/openedx/core/djangoapps/util/management/commands/dump_settings.py +++ b/openedx/core/djangoapps/util/management/commands/dump_settings.py @@ -33,13 +33,14 @@ class Command(BaseCommand): * _("hello") # <-- this will become just "hello" * "hello" - Furthermore, objects which are not easily JSON-ifiable will stringified using their `repr(...)`, e.g.: - * "Path('my/path')" # a Path object - * "" # some random class instance - * "<_io.TextIOWrapper name='' mode='w' encoding='utf-8'>" # sys.stderr + Furthermore, functions and classes are printed as JSON objects like: + { + "module": "path.to.module", + "qualname": "MyClass.MyInnerClass.my_method", // Or, "" + "source_hint": "MY_SETTING = lambda: x + y", // For s only + } - and lambdas are printed by *roughly* printing out their source lines (it's impossible in Python to get the *exact* - source code, as it's been compiled into bytecode). + And everything else will be stringified as its `repr(...)`. """ def handle(self, *args, **kwargs): @@ -82,12 +83,18 @@ def _to_json_friendly_repr(value: object) -> object: # Print gettext_lazy as simply the wrapped string return proxy_args[0] try: + module = value.__module__ qualname = value.__qualname__ except AttributeError: pass else: - if qualname == "": - # Handle lambdas by printing the source lines - return "lambda defined with line(s): " + inspect.getsource(value).strip() + # Handle functions and classes by printing their location (plus approximate source, for lambdas) + return { + "module": module, + "qualname": qualname, + **({ + "source_hint": inspect.getsource(value).strip(), + } if qualname == "" else {}), + } # For all other objects, print the repr return repr(value) diff --git a/openedx/core/djangoapps/util/tests/test_dump_settings.py b/openedx/core/djangoapps/util/tests/test_dump_settings.py index b8712e9aed1c..90171eb48c95 100644 --- a/openedx/core/djangoapps/util/tests/test_dump_settings.py +++ b/openedx/core/djangoapps/util/tests/test_dump_settings.py @@ -26,8 +26,8 @@ def test_for_lms_settings(capsys): # Check: tuples are converted to lists assert isinstance(dump['XBLOCK_MIXINS'], list) - # Check: objects (like classes) are repr'd - assert "" in dump['XBLOCK_MIXINS'] + # Check: classes are converted to dicts of info on the class location + assert {"module": "xmodule.x_module", "qualname": "XModuleMixin"} in dump['XBLOCK_MIXINS'] # Check: nested dictionaries come through OK, and int'l strings are just strings assert dump['COURSE_ENROLLMENT_MODES']['audit']['display_name'] == "Audit" @@ -46,8 +46,8 @@ def test_for_cms_settings(capsys): # Check: tuples are converted to lists assert isinstance(dump['XBLOCK_MIXINS'], list) - # Check: objects (like classes) are repr'd - assert "" in dump['XBLOCK_MIXINS'] + # Check: classes are converted to dicts of info on the class location + assert {"module": "xmodule.x_module", "qualname": "XModuleMixin"} in dump['XBLOCK_MIXINS'] # Check: nested dictionaries come through OK, and int'l strings are just strings assert dump['COURSE_ENROLLMENT_MODES']['audit']['display_name'] == "Audit"