From 07adcf651b38aa6156cd81809dd13987cdd4ad76 Mon Sep 17 00:00:00 2001 From: Juan Altmayer Pizzorno Date: Wed, 28 Aug 2024 15:57:25 -0400 Subject: [PATCH] - fixed various cases get_info was causing errors while running on Flask; --- src/coverup/codeinfo.py | 12 ++++++--- tests/test_codeinfo.py | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/coverup/codeinfo.py b/src/coverup/codeinfo.py index 7683206..18b90ec 100644 --- a/src/coverup/codeinfo.py +++ b/src/coverup/codeinfo.py @@ -55,7 +55,7 @@ def _resolve_from_import(file: Path, imp: ast.ImportFrom) -> str: def _load_module(module_name: str) -> ast.Module | None: try: - if (spec := importlib.util.find_spec(module_name)) and spec.origin: + if (spec := importlib.util.find_spec(module_name)) and spec.origin and spec.origin != 'frozen': return parse_file(Path(spec.origin)) except ModuleNotFoundError: @@ -68,7 +68,7 @@ def _auto_stack(func): """Decorator that adds a stack of the first argument of the function being called.""" def helper(*args): helper.stack.append(args[0]) - _debug(f"{'.'.join(getattr(n, 'name', '?') for n in helper.stack)}") + _debug(f"{'.'.join((n.name if getattr(n, 'name', None) else '?') for n in helper.stack)}") retval = func(*args) helper.stack.pop() return retval @@ -124,6 +124,7 @@ def _find_name_path(module: ast.Module, name: T.List[str], *, paths_seen: T.Set[ crossed to find it. """ if not module: return None + if not name: return None # TODO return module? _debug(f"looking up {name} in {module.path}") @@ -146,12 +147,13 @@ def find_name(node: ast.AST, name: T.List[str]) -> T.List[ast.AST]: return [node, *path] for base in node.bases: + base_name = ast.unparse(base).split('.') if (len(find_name.stack) > 1 and isinstance(context := find_name.stack[-2], ast.ClassDef)): - if (base_path := find_name(context, [context.name, base.id, *name[1:]])): + if (base_path := find_name(context, [context.name, *base_name, *name[1:]])): return base_path[1:] - if (path := find_name(module, [base.id, *name[1:]])): + if (path := find_name(module, [*base_name, *name[1:]])): return path elif isinstance(module, (ast.Function, ast.AsyncFunction)): @@ -171,6 +173,8 @@ def find_name(node: ast.AST, name: T.List[str]) -> T.List[ast.AST]: if (path := _handle_import(module, node, name, paths_seen=paths_seen)): return path + return [] + elif not isinstance(node, (ast.Expression, ast.Expr, ast.Name)): for c in ast.iter_child_nodes(node): if (path := find_name(c, name)): diff --git a/tests/test_codeinfo.py b/tests/test_codeinfo.py index 52db77a..349e522 100644 --- a/tests/test_codeinfo.py +++ b/tests/test_codeinfo.py @@ -248,6 +248,36 @@ def a(self): ) +def test_get_info_method_from_parent_is_attribute(import_fixture): + tmp_path = import_fixture + + code = tmp_path / "foo.py" + code.write_text(textwrap.dedent("""\ + class A: + class B: + def a(self): + return 42 + + class C(A.B): + pass + """ + )) + + tree = codeinfo.parse_file(code) + assert codeinfo.get_info(tree, 'C.a') == textwrap.dedent("""\ + ```python + class A: + ... + + class B: + ... + + def a(self): + return 42 + ```""" + ) + + def test_get_info_method_from_parent_parent_missing(import_fixture): tmp_path = import_fixture @@ -1020,3 +1050,31 @@ def find_node(tree, name): ] +def test_get_info_module(import_fixture): + tmp_path = import_fixture + + code = tmp_path / "foo.py" + code.write_text(textwrap.dedent("""\ + import bar + """ + )) + (tmp_path / "bar.py").write_text(textwrap.dedent("""\ + answer = 42 + """ + )) + + tree = codeinfo.parse_file(code) + assert codeinfo.get_info(tree, 'bar') == None + + +def test_get_info_frozen_module(import_fixture): + tmp_path = import_fixture + + code = tmp_path / "foo.py" + code.write_text(textwrap.dedent("""\ + import os + """ + )) + + tree = codeinfo.parse_file(code) + assert codeinfo.get_info(tree, 'os.path.join') == None