From 25462f27d368c76e92fc82b0a64da3e917ebe398 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Sat, 23 Oct 2021 19:53:47 +0200 Subject: [PATCH] first version --- .gitignore | 129 +++++++++++++++++++++++++++++++++++++++++++++++ example.ipynb | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++ inspector.py | 98 ++++++++++++++++++++++++++++++++++++ 3 files changed, 363 insertions(+) create mode 100644 .gitignore create mode 100644 example.ipynb create mode 100644 inspector.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/example.ipynb b/example.ipynb new file mode 100644 index 0000000..dfc3e95 --- /dev/null +++ b/example.ipynb @@ -0,0 +1,136 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "QK02kUNCW63R" + }, + "outputs": [], + "source": [ + "from inspector import inspector" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c4f3180b10df4882b0f997beb81bedf3", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Box(children=(HTML(value='
Name…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "inspector" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wVqNvnUmW63Z" + }, + "source": [ + "### Change values below" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "eDNtIZIWW63Z" + }, + "outputs": [], + "source": [ + "a = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "rXgxHVioW63a" + }, + "outputs": [], + "source": [ + "a = b = 3.5\n", + "A = B = 123" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "btIFCR7PW63c" + }, + "outputs": [], + "source": [ + "ccccccccccccc = a * b" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "AFDITWxlW63c" + }, + "outputs": [], + "source": [ + "d = \"String\" * 3" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "lCf_CGRYW63d" + }, + "outputs": [], + "source": [ + "del b" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "colab": { + "name": "Variable Inspector.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/inspector.py b/inspector.py new file mode 100644 index 0000000..6dec32f --- /dev/null +++ b/inspector.py @@ -0,0 +1,98 @@ +import inspect +import re +import weakref + +import ipywidgets + + + +HEADER = '' +SEP = '
' +LINE = '{0}{1}{2}' + +IGNORE = ["In", "Out", "exit", "quit", "get_ipython"] + +RE_DIGITS = re.compile("([0-9]+)") + + + +class Singleton(type): + + def __init__(cls, name, bases, namespace): + super().__init__(name, bases, namespace) # creates the class + cls.__signature__ = inspect.signature(cls.__init__) # restore the constructor signature (instead of that of __call__ below) + cls.__instance__ = lambda: None # fake dead weakref + + def __call__(cls, *args, **kwargs): + inst = cls.__instance__() + if not inst: + inst = super().__call__(*args, **kwargs) # creates the instance (calls __new__ and __init__ methods) + cls.__instance__ = weakref.ref(inst) + return inst + + + +class VariableInspector(object, metaclass=Singleton): + + def __init__(self, ipython=None): + self._box = ipywidgets.Box() + self._box.layout.overflow_y = "scroll" + self._table = ipywidgets.HTML(value="Loading...") + self._box.children = [self._table] + + self._ipython = ipython or get_ipython() + self.start() + + + def start(self): + """Add update callback if not already registered""" + if self.is_running: + raise RuntimeError("Cannot start. Update callback is already registered") + self._ipython.events.register("post_run_cell", self._update) + + def stop(self): + """Remove update callback if registered""" + if not self.is_running: + raise RuntimeError("Cannot stop. Update callback is not registered") + self._ipython.events.unregister("post_run_cell", self._update) + + @property + def is_running(self): + """Test if update callback is registered""" + return self._update in self._ipython.events.callbacks["post_run_cell"] + + + def _update(self, _): + """Fill table with variable information""" + namespace = self._ipython.user_ns.items() + lines = (format_line(k, v) for k, v in sorted_naturally(namespace) if is_good_entry(k, v)) + self._table.value = HEADER + SEP.join(lines) + FOOTER + + def _ipython_display_(self): + """Called when display() or pyout is used to display the Variable Inspector""" + try: + self._box._ipython_display_() + except: + pass + + + +def format_line(k, v): + return LINE.format(k, typename(v), v) + +def sorted_naturally(iterable, reverse=False): + natural = lambda item: [int(c) if c.isdigit() else c.casefold() for c in RE_DIGITS.split(str(item))] + return sorted(iterable, key=natural, reverse=reverse) + +def is_good_entry(k, v): + return not k.startswith("_") and k not in IGNORE and not isinstance(v, VariableInspector) + +def typename(obj): + return type(obj).__name__ + + + +inspector = VariableInspector() + +