From 719eb2a7d7f40da55b4cc490d47c407d42d9ca13 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 29 Oct 2024 10:20:01 +0100 Subject: [PATCH] Some cleanup while looking into local imports --- README.rst | 18 ++++++++-------- lib/python/pyflyby/_autoimp.py | 38 ++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index e2223099..659fe5fe 100644 --- a/README.rst +++ b/README.rst @@ -92,7 +92,7 @@ configuration file: .. code:: python - + from pyflyby import add_import add_import("foo", "foo = 1") @@ -463,29 +463,29 @@ Emacs support saveframe: A utility for debugging / reproducing an issue ========================================================= -PyFlyBy provides a utility named **saveframe** which can be used to save +PyFlyBy provides a utility named **saveframe** which can be used to save information for debugging / reproducing an issue. -**Usage**: If you have a piece of code or a script that is failing due an issue -originating from upstream code, and you cannot share your private code as a reproducer, -use this utility to save relevant information to a file. Share the generated file with +**Usage**: If you have a piece of code or a script that is failing due an issue +originating from upstream code, and you cannot share your private code as a reproducer, +use this utility to save relevant information to a file. Share the generated file with the upstream team, enabling them to reproduce and diagnose the issue independently. -**Information saved in the file**: This utility captures and saves *error stack frames* -to a file. It includes the values of local variables from each stack frame, as well +**Information saved in the file**: This utility captures and saves *error stack frames* +to a file. It includes the values of local variables from each stack frame, as well as metadata about each frame and the exception raised by your code. This utility comes with 2 interfaces: 1. **A function**: For interactive usages such as IPython, Jupyter Notebook, or a - debugger (pdb/ipdb), use **pyflyby.saveframe** function. To know how to use this + debugger (pdb/ipdb), use **pyflyby.saveframe** function. To know how to use this function, checkout it's documentation: .. code:: In [1]: saveframe? -2. **A script**: For cli usages (like a failing script), use **pyflyby/bin/saveframe** +2. **A script**: For cli usages (like a failing script), use **pyflyby/bin/saveframe** script. To know how to use this script, checkout its documentation: .. code:: diff --git a/lib/python/pyflyby/_autoimp.py b/lib/python/pyflyby/_autoimp.py index 2223dab1..b0d5e6dc 100644 --- a/lib/python/pyflyby/_autoimp.py +++ b/lib/python/pyflyby/_autoimp.py @@ -24,7 +24,7 @@ from six import reraise import sys import types -from typing import Any, Set +from typing import Any, Set, Optional, List, Tuple if sys.version_info >= (3, 12): ATTRIBUTE_NAME = "value" @@ -353,7 +353,7 @@ def __init__(self, name, source, lineno): self.lineno = lineno -class _MissingImportFinder(object): +class _MissingImportFinder: """ A helper class to be used only by `_find_missing_imports_in_ast`. @@ -371,8 +371,12 @@ class _MissingImportFinder(object): """ - def __init__(self, scopestack, find_unused_imports=False, - parse_docstrings=False): + scopestack: ScopeStack + _lineno: Optional[int] + missing_imports: List[Tuple[Optional[int], DottedIdentifier]] + parse_docstrings: bool + + def __init__(self, scopestack, *, find_unused_imports:bool, parse_docstrings:bool): """ Construct the AST visitor. @@ -385,19 +389,26 @@ def __init__(self, scopestack, find_unused_imports=False, # includes the globals dictionary. ScopeStack() will make sure this # includes builtins. scopestack = ScopeStack(scopestack) + # Add an empty namespace to the stack. This facilitates adding stuff # to scopestack[-1] without ever modifying user globals. scopestack = scopestack.with_new_scope() + self.scopestack = scopestack + # Create data structure to hold the result. # missing_imports is a list of (lineno, DottedIdentifier) tuples. self.missing_imports = [] + # unused_imports is a list of (lineno, Import) tuples, if enabled. self.unused_imports = [] if find_unused_imports else None + self.parse_docstrings = parse_docstrings + # Function bodies that we need to check after defining names in this # function scope. self._deferred_load_checks = [] + # Whether we're currently in a FunctionDef. self._in_FunctionDef = False # Current lineno. @@ -419,7 +430,8 @@ def _scan_node(self, node): finally: self.scopestack = oldscopestack - def scan_for_import_issues(self, codeblock): + def scan_for_import_issues(self, codeblock: PythonBlock): + assert isinstance(codeblock, PythonBlock) # See global `scan_for_import_issues` if not isinstance(codeblock, PythonBlock): codeblock = PythonBlock(codeblock) @@ -998,10 +1010,11 @@ def visit_Delete(self, node): # Don't call generic_visit(node) here. Reason: We already visit the # parts above, if relevant. - def _visit_Load_defered_global(self, fullname): + def _visit_Load_defered_global(self, fullname:str): """ Some things will be resolved in global scope later. """ + assert isinstance(fullname, str), fullname logger.debug("_visit_Load_defered_global(%r)", fullname) if symbol_needs_import(fullname, self.scopestack): data = (fullname, self.scopestack, self._lineno) @@ -1090,7 +1103,11 @@ def _scan_unused_imports(self): unused_imports.sort() -def scan_for_import_issues(codeblock, find_unused_imports=True, parse_docstrings=False): +def scan_for_import_issues( + codeblock: PythonBlock, + find_unused_imports: bool = True, + parse_docstrings: bool = False, +): """ Find missing and unused imports, by lineno. @@ -1117,7 +1134,7 @@ def scan_for_import_issues(codeblock, find_unused_imports=True, parse_docstrings ([], [(1, Import('import baz'))]) """ - logger.debug("scan_for_import_issues()") + logger.debug("global scan_for_import_issues()") if not isinstance(codeblock, PythonBlock): codeblock = PythonBlock(codeblock) namespaces = ScopeStack([{}]) @@ -1148,7 +1165,10 @@ def _find_missing_imports_in_ast(node, namespaces): # Traverse the abstract syntax tree. if logger.debug_enabled: logger.debug("ast=%s", ast.dump(node)) - return _MissingImportFinder(namespaces).find_missing_imports(node) + return _MissingImportFinder( + namespaces, + find_unused_imports=False, + parse_docstrings=False).find_missing_imports(node) # TODO: maybe we should replace _find_missing_imports_in_ast with # _find_missing_imports_in_code(compile(node)). The method of parsing opcodes