Message ID | 20200922210101.4081073-2-jsnow@redhat.com |
---|---|
State | Superseded |
Headers | show |
Series | qapi: static typing conversion, pt1 | expand |
On Tue, Sep 22, 2020 at 05:00:24PM -0400, John Snow wrote: > This adds some really childishly simple debugging tools. Maybe they're > interesting for someone else, too? > > Signed-off-by: John Snow <jsnow@redhat.com> > --- > scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 78 insertions(+) > create mode 100644 scripts/qapi/debug.py > > diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py > new file mode 100644 > index 0000000000..bacf5ee180 > --- /dev/null > +++ b/scripts/qapi/debug.py > @@ -0,0 +1,78 @@ > +""" > +Small debugging facilities for mypy static analysis work. > +(C) 2020 John Snow, for Red Hat, Inc. > +""" > + > +import inspect > +import json > +from typing import Dict, List, Any > +from types import FrameType > + > + > +OBSERVED_TYPES: Dict[str, List[str]] = {} > + > + > +# You have no idea how long it took to find this return type... > +def caller_frame() -> FrameType: > + """ > + Returns the stack frame of the caller's caller. > + e.g. foo() -> caller() -> caller_frame() return's foo's stack frame. > + """ > + stack = inspect.stack() > + caller = stack[2].frame > + if caller is None: > + msg = "Python interpreter does not support stack frame inspection" > + raise RuntimeError(msg) > + return caller > + > + > +def _add_type_record(name: str, typestr: str) -> None: > + seen = OBSERVED_TYPES.setdefault(name, []) > + if typestr not in seen: > + seen.append(typestr) > + > + > +def record_type(name: str, value: Any, dict_names: bool = False) -> None: > + """ > + Record the type of a variable. > + > + :param name: The name of the variable > + :param value: The value of the variable > + """ > + _add_type_record(name, str(type(value))) > + > + try: > + for key, subvalue in value.items(): > + subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]" > + _add_type_record(subname, str(type(subvalue))) > + return > + except AttributeError: > + # (Wasn't a dict or anything resembling one.) > + pass > + > + # str is iterable, but not in the way we want! > + if isinstance(value, str): > + return > + > + try: > + for elem in value: > + _add_type_record(f"{name}.[list_elem]", str(type(elem))) > + except TypeError: > + # (Wasn't a list or anything else iterable.) > + pass > + > + > +def show_types() -> None: > + """ > + Print all of the currently known variable types to stdout. > + """ > + print(json.dumps(OBSERVED_TYPES, indent=2)) > + Maybe the following will be cheaper (no json conversion): pprint.pprint(OBSERVED_TYPES, indent=2) Other than that, I'd vote for including this if there's a bit more documentation on how to use it, or an example script. Maybe there already is, and I did not get to it yet. - Cleber. > + > +def record_locals(show: bool = False, dict_names: bool = False) -> None: > + caller = caller_frame() > + name = caller.f_code.co_name > + for key, value in caller.f_locals.items(): > + record_type(f"{name}.{key}", value, dict_names=dict_names) > + if show: > + show_types() > -- > 2.26.2 >
On 9/22/20 7:43 PM, Cleber Rosa wrote: > On Tue, Sep 22, 2020 at 05:00:24PM -0400, John Snow wrote: >> This adds some really childishly simple debugging tools. Maybe they're >> interesting for someone else, too? >> >> Signed-off-by: John Snow <jsnow@redhat.com> >> --- >> scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++ >> 1 file changed, 78 insertions(+) >> create mode 100644 scripts/qapi/debug.py >> >> diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py >> new file mode 100644 >> index 0000000000..bacf5ee180 >> --- /dev/null >> +++ b/scripts/qapi/debug.py >> @@ -0,0 +1,78 @@ >> +""" >> +Small debugging facilities for mypy static analysis work. >> +(C) 2020 John Snow, for Red Hat, Inc. >> +""" >> + >> +import inspect >> +import json >> +from typing import Dict, List, Any >> +from types import FrameType >> + >> + >> +OBSERVED_TYPES: Dict[str, List[str]] = {} >> + >> + >> +# You have no idea how long it took to find this return type... >> +def caller_frame() -> FrameType: >> + """ >> + Returns the stack frame of the caller's caller. >> + e.g. foo() -> caller() -> caller_frame() return's foo's stack frame. >> + """ >> + stack = inspect.stack() >> + caller = stack[2].frame >> + if caller is None: >> + msg = "Python interpreter does not support stack frame inspection" >> + raise RuntimeError(msg) >> + return caller >> + >> + >> +def _add_type_record(name: str, typestr: str) -> None: >> + seen = OBSERVED_TYPES.setdefault(name, []) >> + if typestr not in seen: >> + seen.append(typestr) >> + >> + >> +def record_type(name: str, value: Any, dict_names: bool = False) -> None: >> + """ >> + Record the type of a variable. >> + >> + :param name: The name of the variable >> + :param value: The value of the variable >> + """ >> + _add_type_record(name, str(type(value))) >> + >> + try: >> + for key, subvalue in value.items(): >> + subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]" >> + _add_type_record(subname, str(type(subvalue))) >> + return >> + except AttributeError: >> + # (Wasn't a dict or anything resembling one.) >> + pass >> + >> + # str is iterable, but not in the way we want! >> + if isinstance(value, str): >> + return >> + >> + try: >> + for elem in value: >> + _add_type_record(f"{name}.[list_elem]", str(type(elem))) >> + except TypeError: >> + # (Wasn't a list or anything else iterable.) >> + pass >> + >> + >> +def show_types() -> None: >> + """ >> + Print all of the currently known variable types to stdout. >> + """ >> + print(json.dumps(OBSERVED_TYPES, indent=2)) >> + > > Maybe the following will be cheaper (no json conversion): > > pprint.pprint(OBSERVED_TYPES, indent=2) > > Other than that, I'd vote for including this if there's a bit more > documentation on how to use it, or an example script. Maybe there > already is, and I did not get to it yet. > > - Cleber. > Nope, this is just a dumb script I did to observe types in flight. There are apparently bigger, beefier tools that I don't know how to use yet: https://github.com/dropbox/pyannotate I just included my own little tool as a reference thing to be archived on list, I have no desire to spruce it up. I'd rather spend my time learning pyannotate. --js
diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py new file mode 100644 index 0000000000..bacf5ee180 --- /dev/null +++ b/scripts/qapi/debug.py @@ -0,0 +1,78 @@ +""" +Small debugging facilities for mypy static analysis work. +(C) 2020 John Snow, for Red Hat, Inc. +""" + +import inspect +import json +from typing import Dict, List, Any +from types import FrameType + + +OBSERVED_TYPES: Dict[str, List[str]] = {} + + +# You have no idea how long it took to find this return type... +def caller_frame() -> FrameType: + """ + Returns the stack frame of the caller's caller. + e.g. foo() -> caller() -> caller_frame() return's foo's stack frame. + """ + stack = inspect.stack() + caller = stack[2].frame + if caller is None: + msg = "Python interpreter does not support stack frame inspection" + raise RuntimeError(msg) + return caller + + +def _add_type_record(name: str, typestr: str) -> None: + seen = OBSERVED_TYPES.setdefault(name, []) + if typestr not in seen: + seen.append(typestr) + + +def record_type(name: str, value: Any, dict_names: bool = False) -> None: + """ + Record the type of a variable. + + :param name: The name of the variable + :param value: The value of the variable + """ + _add_type_record(name, str(type(value))) + + try: + for key, subvalue in value.items(): + subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]" + _add_type_record(subname, str(type(subvalue))) + return + except AttributeError: + # (Wasn't a dict or anything resembling one.) + pass + + # str is iterable, but not in the way we want! + if isinstance(value, str): + return + + try: + for elem in value: + _add_type_record(f"{name}.[list_elem]", str(type(elem))) + except TypeError: + # (Wasn't a list or anything else iterable.) + pass + + +def show_types() -> None: + """ + Print all of the currently known variable types to stdout. + """ + print(json.dumps(OBSERVED_TYPES, indent=2)) + + +def record_locals(show: bool = False, dict_names: bool = False) -> None: + caller = caller_frame() + name = caller.f_code.co_name + for key, value in caller.f_locals.items(): + record_type(f"{name}.{key}", value, dict_names=dict_names) + if show: + show_types()
This adds some really childishly simple debugging tools. Maybe they're interesting for someone else, too? Signed-off-by: John Snow <jsnow@redhat.com> --- scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 scripts/qapi/debug.py