Skip to content

Instantly share code, notes, and snippets.

@alexbowe
Created January 7, 2025 22:40
Show Gist options
  • Save alexbowe/b42d14e6115baa7b04e34badf837e0c5 to your computer and use it in GitHub Desktop.
Save alexbowe/b42d14e6115baa7b04e34badf837e0c5 to your computer and use it in GitHub Desktop.
Diff Python objects by dotted path
import inspect
import argparse
from importlib import import_module
from difflib import unified_diff
# If Rich is available, use it to colorize the diff.
# NOTE: Users can also use other tools like `colordiff`
try:
from rich.console import Console
from rich.syntax import Syntax
_RICH_AVAILABLE = True
except ImportError:
_RICH_AVAILABLE = False
def import_object(dotted_str):
"""
Attempt to import the longest valid module prefix.
Then get the remaining attributes nested in that module.
Example:
dotted_str = "mypkg.mymodule.SomeClass.NestedClass.my_method"
"""
parts = dotted_str.split(".")
for i in reversed(range(1, len(parts) + 1)):
try:
obj = import_module(".".join(parts[:i]))
for attr in parts[i:]:
obj = getattr(obj, attr)
return obj
except ImportError:
pass
raise ImportError(f"Cannot import '{dotted_str}'")
def get_source(dotted_str):
obj = import_object(dotted_str)
return inspect.getsource(obj)
def parse_args():
parser = argparse.ArgumentParser(
description="Diff two Python objects via dotted paths"
)
parser.add_argument("obj1", help="e.g. mypkg.mymodule.SomeClass.my_method")
parser.add_argument("obj2", help="e.g. mypkg2.mymodule2.OtherClass.other_method")
return parser.parse_args()
def diff_objects(obj_path1, obj_path2):
source1 = get_source(obj_path1)
source2 = get_source(obj_path2)
diff_lines = unified_diff(
source1.splitlines(),
source2.splitlines(),
fromfile=obj_path1,
tofile=obj_path2,
)
return diff_lines
def print_diff(diff_lines):
diff_text = "\n".join(diff_lines)
if _RICH_AVAILABLE:
console = Console(force_terminal=True)
syntax = Syntax(diff_text, "diff", theme="ansi_dark", line_numbers=False)
console.print(syntax)
else:
print(diff_text)
def main():
args = parse_args()
obj_path1, obj_path2 = args.obj1, args.obj2
diff_lines = diff_objects(obj_path1, obj_path2)
print_diff(diff_lines)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment