Skip to content

Commit

Permalink
feat: Make dump for fns, classes more stable and helpful (openedx#36188)
Browse files Browse the repository at this point in the history
The `dump_settings` command currently prints out the raw `repr(...)`s for
defined functions, e.g.:

    "WIKI_CAN_ASSIGN": "<function CAN_ASSIGN at 0x74ce5e9b2020>",

In addition to being uninformative, these `at 0x74ce...` hashes change every
run, so they appear in the diff as having "changed" every time. With this
commit, here's what `dump_settings` will print out for a function instead:

    "WIKI_CAN_ASSIGN": {
        "module": "lms.djangoapps.course_wiki.settings",
        "qualname": "CAN_ASSIGN"
    },
  • Loading branch information
kdmccormick authored and leoaulasneo98 committed Jan 30, 2025
1 parent c7e4969 commit 76748c4
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 13 deletions.
25 changes: 16 additions & 9 deletions openedx/core/djangoapps/util/management/commands/dump_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
* "<lms.myapp.MyClass object at 0x704599fa2fd0>" # some random class instance
* "<_io.TextIOWrapper name='<stderr>' 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, "<lambda>"
"source_hint": "MY_SETTING = lambda: x + y", // For <lambda>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):
Expand Down Expand Up @@ -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 == "<lambda>":
# 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 == "<lambda>" else {}),
}
# For all other objects, print the repr
return repr(value)
8 changes: 4 additions & 4 deletions openedx/core/djangoapps/util/tests/test_dump_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<class 'xmodule.x_module.XModuleMixin'>" 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"
Expand All @@ -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 "<class 'xmodule.x_module.XModuleMixin'>" 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"
Expand Down

0 comments on commit 76748c4

Please sign in to comment.