From a2109b8793914d1d3660b65a7b7d6e9df3c847d6 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 29 Oct 2024 11:11:01 +0100 Subject: [PATCH 1/3] Some cleanup while looking into local imports --- README.rst | 18 +++++++-------- lib/python/pyflyby/_autoimp.py | 40 +++++++++++++++++++++++++-------- lib/python/pyflyby/_importdb.py | 8 ++++--- 3 files changed, 45 insertions(+), 21 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..81917a8b 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,14 @@ 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 + unused_imports: Optional[List[Tuple[int, str]]] + _deferred_load_checks: list[tuple[str, ScopeStack, Optional[int]]] + + def __init__(self, scopestack, *, find_unused_imports:bool, parse_docstrings:bool): """ Construct the AST visitor. @@ -385,19 +391,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 +432,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 +1012,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 +1105,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 +1136,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 +1167,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 diff --git a/lib/python/pyflyby/_importdb.py b/lib/python/pyflyby/_importdb.py index 818121f2..c0d3f07a 100644 --- a/lib/python/pyflyby/_importdb.py +++ b/lib/python/pyflyby/_importdb.py @@ -560,9 +560,11 @@ def _from_filenames(cls, filenames, _mandatory_filenames_deprecated=[]): filenames = [filenames] for f in filenames: assert isinstance(f, Filename) - logger.debug("ImportDB: loading [%s], mandatory=[%s]", - ', '.join(map(str, filenames)), - ', '.join(map(str, _mandatory_filenames_deprecated))) + logger.debug( + "ImportDB: loading %r, mandatory=%r", + [str(f) for f in filenames], + [str(f) for f in _mandatory_filenames_deprecated], + ) if SUPPORT_DEPRECATED_BEHAVIOR: # Before 2014-10, pyflyby read the following: # * known_imports from $PYFLYBY_PATH/known_imports/**/*.py or From 9b5f42a5264a7c0476e77b5a78aa974945c32deb Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 29 Oct 2024 11:24:27 +0100 Subject: [PATCH 2/3] drop 3.8, test on 3.13 --- .github/workflows/test.yml | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97a40004..a6cb15b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,14 +11,14 @@ jobs: matrix: os: [ubuntu-latest] python-version: - - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" + - "3.13" include: - os: macos-latest - python-version: "3.10" + python-version: "3.12" env: DEBUG_TEST_PYFLYBY: 1 diff --git a/setup.py b/setup.py index fa6e9959..ec8a7529 100755 --- a/setup.py +++ b/setup.py @@ -228,7 +228,7 @@ def make_distribution(self): "black", "typing_extensions>=4.6; python_version<'3.12'" ], - python_requires=">3.8, <4", + python_requires=">3.9, <4", tests_require=['pexpect>=3.3', 'pytest', 'epydoc', 'rlipython', 'requests'], cmdclass = { 'test' : PyTest, From d4665920bad52be8e20d0712bed4495064d2175c Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 29 Oct 2024 03:40:40 -0700 Subject: [PATCH 3/3] Apply suggestions from code review --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6cb15b1..376ba423 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,6 @@ jobs: - "3.10" - "3.11" - "3.12" - - "3.13" include: - os: macos-latest python-version: "3.12"