[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static parsing in autoprofile cannot handle annotated assignments #279

Closed
TTsangSC opened this issue Jun 22, 2024 · 2 comments
Closed

Static parsing in autoprofile cannot handle annotated assignments #279

TTsangSC opened this issue Jun 22, 2024 · 2 comments

Comments

@TTsangSC
Copy link
Contributor

TL; DR

__editable___*_*finder.py may have type annotations (e.g. those created by setuptools > v70.0.0), which trips the static analysis code used by auto-profiling to locate modules. The fix is a low-hanging fruit though.

What happened

A package which I installed locally as editable in my virtual environment can only be profiled with auto-profiling when I am inside the project directory hierarchy. Outside of it, I get something like:

(my_venv)  $ python -m pdb -m kernprof --line --outfile <SOME_FILE> --prof-mod <SOME_EDITABLE_PACKAGE> <SOME_BENCKMARK_SCRIPT> -- <SOME_ARGS_FOR_THE_SCRIPT> [...]
> <MY_VENV>/lib/python3.8/site-packages/kernprof.py(2)<module>()
-> """
(Pdb) c
Wrote profile results to <SOME_FILE>
Inspect results with:
python -m line_profiler -rmt "<SOME_FILE>"  
Traceback (most recent call last):
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/util_static.py", line 187, in _static_parse
    value = visitor.static_value
AttributeError: 'StaticVisitor' object has no attribute 'static_value'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pdb.py", line 1703, in main
    pdb._runmodule(mainpyfile)
  File "/usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pdb.py", line 1547, in _runmodule
    self.run(code)
  File "/usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/lib/python3.8/bdb.py", line 580, in run
    exec(cmd, globals, locals)
  File "<MY_VENV>/lib/python3.8/site-packages/kernprof.py", line 2, in <module>
    """
  File "<MY_VENV>/lib/python3.8/site-packages/kernprof.py", line 379, in main
    autoprofile.run(script_file, ns, prof_mod=prof_mod, profile_imports=options.prof_imports)
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/autoprofile.py", line 91, in run
    tree_profiled = AstTreeProfiler(script_file, prof_mod, profile_imports).profile()
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/ast_tree_profiler.py", line 161, in profile
    tree_imports_to_profile_dict = self._profmod_extractor_class_handler(
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/profmod_extractor.py", line 231, in run
    modnames_to_profile = self._get_modnames_to_profile_from_prof_mod(self._script_file, self._prof_mod)
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/profmod_extractor.py", line 98, in _get_modnames_to_profile_from_prof_mod
    modpath = modname_to_modpath(mod, sys_path=new_sys_path)
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/util_static.py", line 420, in modname_to_modpath
    modpath = _syspath_modname_to_modpath(modname, sys_path)
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/util_static.py", line 334, in _syspath_modname_to_modpath
    mapping = _static_parse("MAPPING", finder_fpath)
  File "<MY_VENV>/lib/python3.8/site-packages/line_profiler/autoprofile/util_static.py", line 190, in _static_parse
    raise AttributeError(value)
AttributeError: Unknown MAPPING
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program

The cause

Editable modules are installed via __editable___<PACKAGE_NAME>_*finder.py scripts (among other hooks) in the site-packages. This is used by line_profiler.autoprofile.util_static._syspath_modname_to_modpath() to find (by glob.glob()-ing the file pattern) and process (by line_profiler.autoprofile.util_static._static_parse()) those finder scripts, thus statically resolving the --prof-module module names to the appropriate source where they can be loaded for AST manipulation.

Specifically, ._static_parse() is used to locate the definition of MAPPING in the finder script, like

from __future__ import annotations
import sys
from importlib.machinery import ModuleSpec, PathFinder
from importlib.machinery import all_suffixes as module_suffixes
from importlib.util import spec_from_file_location
from itertools import chain
from pathlib import Path

MAPPING: dict[str, str] = <SOME_DICT>
NAMESPACES: dict[str, list[str]] = {}
PATH_PLACEHOLDER = '__editable__.<...>.finder' + ".__path_hook__"

Note particularly how the assignment to MAPPING is type-annotated – this is a relatively new invention, the setuptools commit which added this was only about a month old. However, this has ramifications for ._static_parse(), since the .<locals>.StaticVisitor it uses only handles non-annotated assignments (parsed to ast.Assign nodes) but not annotated ones (parsed to ast.AnnAssign nodes):

    class StaticVisitor(ast.NodeVisitor):
        def visit_Assign(self, node):
            for target in node.targets:
                if getattr(target, "id", None) == varname:
                    self.static_value = _parse_static_node_value(node.value)

Hence the visitor misses the node, and the code falls through to the reporting of the AttributeError as seen in the traceback block.

A possible solution

Just extend StaticVisitor to handle annotated assignments:

    class StaticVisitor(ast.NodeVisitor):
        def visit_Assign(self, node):
            for target in node.targets:
                if getattr(target, "id", None) == varname:
                    self.static_value = _parse_static_node_value(node.value)

        def visit_AnnAssign(self, node):
            if getattr(node.target, "id", None) == varname:
                self.static_value = _parse_static_node_value(node.value)

This probably wouldn't cause performance issues since ._static_parse() is only invoked once (per finder script) in the entire lifetime of the program, and IDK if there are other edge cases not taken care of or whether it would miraculously break some tests. But the borderline is that it now works on my machine™ so I'm happy.

@Erotemic
Copy link
Member
Erotemic commented Jun 22, 2024

I've recently fixed this in ubelt and xdoctest. I forgot that the code was ported here too. I came up with the same fix. I'm under quite a bit of pressure at the moment, so a PR to add it here would be helpful. I will merge and release it once it is ready. Unfortunately this is not the easiest thing to write a test for, so just the fix is fine.

As an aside: I'm glad someone else ran into this. This was an issue in xdoctest and ubelt (and apparently line-profiler) for several months, and I was really surprised nobody else was submitting issues about MAPPING. I'm sorry you also had to go through the debugging to find this, but it's good to know we both came to the same conclusion and fix. The commits where I fixed this are here:

TTsangSC added a commit to TTsangSC/line_profiler that referenced this issue Jun 23, 2024
@TTsangSC
Copy link
Contributor Author

Hi @Erotemic, thanks for the prompt reply – and of course for being part of the team behind this invaluable development tool.

I've just put in the PR (#280), but since I'm not a project member there's now a workflow pending approval; please take a look when you have the time. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants