├── .coveragerc ├── .gitignore ├── LICENSE.txt ├── README.md ├── code_of_conduct.md ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── pyproject.toml ├── setup.py ├── src └── showcallstack │ ├── __init__.py │ └── __main__.py ├── tests └── test_showcallstack.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | 3 | # To run coverage tests, run `coverage run tests/test_showcallstack.py` then `coverage html`. The report will be in htmlconv/index.html 4 | 5 | 6 | 7 | [run] 8 | 9 | 10 | 11 | [report] 12 | # Regexes for lines to exclude from consideration 13 | exclude_lines = 14 | # Have to re-enable the standard pragma 15 | pragma: no cover 16 | 17 | # Don't complain if tests don't hit defensive assertion code: 18 | raise AssertionError 19 | raise NotImplementedError 20 | 21 | # Don't complain if non-runnable code isn't run: 22 | if 0: 23 | if __name__ == .__main__.: 24 | 25 | ignore_errors = True 26 | 27 | 28 | [html] 29 | directory = htmlcov 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Al Sweigart 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShowCallStack 2 | 3 | Shows a simplified view of the call stack. 4 | 5 | This module is similar to Python's built-in `traceback` and `inspect` modules, but is easier to use and displays more simple output. This module is useful for demonstrating what the call stack looks like during recursive function calls. Simply add a `from showcallstack import showCallStack` line and then call `showCallStack()` from wherever you wish to see the state of the call stack and the local variables in each call frame. 6 | 7 | Example Usage 8 | ============= 9 | 10 | This program... 11 | 12 | from showcallstack import showcallstack 13 | 14 | def a(): 15 | varA = 42 16 | b() 17 | 18 | def b(): 19 | varB = 86 20 | c() 21 | 22 | def c(): 23 | varC = 99 24 | showcallstack() 25 | 26 | spam = 'SPAM! 27 | a() 28 | 29 | ...outputs this: 30 | 31 | The call stack is 3 call(s) deep: 32 | (Here is the "bottom" of the call stack.) 33 | Local variables of call to a(): 34 | varA (type: int) == 42 35 | Local variables of call to b(): 36 | varB (type: int) == 86 37 | Local variables of call to c(): 38 | varC (type: int) == 99 39 | (Here is the "top" of the call stack.) 40 | 41 | Global variables: 42 | spam (type: str) == 'SPAM!' 43 | 44 | This recursive factorial program... 45 | 46 | from showcallstack import showcallstack 47 | 48 | def factorial(num): 49 | showcallstack() 50 | if num == 1: 51 | return 1 52 | else: 53 | return num * factorial(num - 1) 54 | 55 | factorial(4) 56 | 57 | ...outputs this: 58 | 59 | The call stack is 1 call(s) deep: 60 | (Here is the "bottom" of the call stack.) 61 | Local variables of call to factorial(): 62 | num (type: int) == 4 63 | (Here is the "top" of the call stack.) 64 | 65 | Global variables: 66 | No global variables. 67 | 68 | The call stack is 2 call(s) deep: 69 | (Here is the "bottom" of the call stack.) 70 | Local variables of call to factorial(): 71 | num (type: int) == 4 72 | Local variables of call to factorial(): 73 | num (type: int) == 3 74 | (Here is the "top" of the call stack.) 75 | 76 | Global variables: 77 | No global variables. 78 | 79 | The call stack is 3 call(s) deep: 80 | (Here is the "bottom" of the call stack.) 81 | Local variables of call to factorial(): 82 | num (type: int) == 4 83 | Local variables of call to factorial(): 84 | num (type: int) == 3 85 | Local variables of call to factorial(): 86 | num (type: int) == 2 87 | (Here is the "top" of the call stack.) 88 | 89 | Global variables: 90 | No global variables. 91 | 92 | The call stack is 4 call(s) deep: 93 | (Here is the "bottom" of the call stack.) 94 | Local variables of call to factorial(): 95 | num (type: int) == 4 96 | Local variables of call to factorial(): 97 | num (type: int) == 3 98 | Local variables of call to factorial(): 99 | num (type: int) == 2 100 | Local variables of call to factorial(): 101 | num (type: int) == 1 102 | (Here is the "top" of the call stack.) 103 | 104 | Global variables: 105 | No global variables. 106 | 107 | You can also call the `showcallstack.getcallstack()` function to get this output as a list of strings. 108 | 109 | Support 110 | ------- 111 | 112 | If you find this project helpful and would like to support its development, [consider donating to its creator on Patreon](https://www.patreon.com/AlSweigart). 113 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | 131 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'ShowCallStack' 23 | copyright = '2018, Al Sweigart' 24 | author = 'Al Sweigart' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.doctest', 44 | 'sphinx.ext.coverage', 45 | ] 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ['_templates'] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # 53 | # source_suffix = ['.rst', '.md'] 54 | source_suffix = '.rst' 55 | 56 | # The master toctree document. 57 | master_doc = 'index' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | # 62 | # This is also used if you do content translation via gettext catalogs. 63 | # Usually you set "language" from the command line for these cases. 64 | language = None 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | # This pattern also affects html_static_path and html_extra_path. 69 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 70 | 71 | # The name of the Pygments (syntax highlighting) style to use. 72 | pygments_style = None 73 | 74 | 75 | # -- Options for HTML output ------------------------------------------------- 76 | 77 | # The theme to use for HTML and HTML Help pages. See the documentation for 78 | # a list of builtin themes. 79 | # 80 | html_theme = 'alabaster' 81 | 82 | # Theme options are theme-specific and customize the look and feel of a theme 83 | # further. For a list of options available for each theme, see the 84 | # documentation. 85 | # 86 | # html_theme_options = {} 87 | 88 | # Add any paths that contain custom static files (such as style sheets) here, 89 | # relative to this directory. They are copied after the builtin static files, 90 | # so a file named "default.css" will overwrite the builtin "default.css". 91 | html_static_path = ['_static'] 92 | 93 | # Custom sidebar templates, must be a dictionary that maps document names 94 | # to template names. 95 | # 96 | # The default sidebars (for documents that don't match any pattern) are 97 | # defined by theme itself. Builtin themes are using these templates by 98 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 99 | # 'searchbox.html']``. 100 | # 101 | # html_sidebars = {} 102 | 103 | 104 | # -- Options for HTMLHelp output --------------------------------------------- 105 | 106 | # Output file base name for HTML help builder. 107 | htmlhelp_basename = 'showcallstackdoc' 108 | 109 | 110 | # -- Options for LaTeX output ------------------------------------------------ 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | 117 | # The font size ('10pt', '11pt' or '12pt'). 118 | # 119 | # 'pointsize': '10pt', 120 | 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | 125 | # Latex figure (float) alignment 126 | # 127 | # 'figure_align': 'htbp', 128 | } 129 | 130 | # Grouping the document tree into LaTeX files. List of tuples 131 | # (source start file, target name, title, 132 | # author, documentclass [howto, manual, or own class]). 133 | latex_documents = [ 134 | (master_doc, 'showcallstack.tex', 'ShowCallStack Documentation', 135 | 'Al Sweigart', 'manual'), 136 | ] 137 | 138 | 139 | # -- Options for manual page output ------------------------------------------ 140 | 141 | # One entry per manual page. List of tuples 142 | # (source start file, name, description, authors, manual section). 143 | man_pages = [ 144 | (master_doc, 'showcallstack', 'ShowCallStack Documentation', 145 | [author], 1) 146 | ] 147 | 148 | 149 | # -- Options for Texinfo output ---------------------------------------------- 150 | 151 | # Grouping the document tree into Texinfo files. List of tuples 152 | # (source start file, target name, title, author, 153 | # dir menu entry, description, category) 154 | texinfo_documents = [ 155 | (master_doc, 'showcallstack', 'ShowCallStack Documentation', 156 | author, 'ShowCallStack', 'Shows a simplified view of the call stack.', 157 | 'Miscellaneous'), 158 | ] 159 | 160 | 161 | # -- Options for Epub output ------------------------------------------------- 162 | 163 | # Bibliographic Dublin Core info. 164 | epub_title = project 165 | 166 | # The unique identifier of the text. This can be a ISBN number 167 | # or the project homepage. 168 | # 169 | # epub_identifier = '' 170 | 171 | # A unique identification for the text. 172 | # 173 | # epub_uid = '' 174 | 175 | # A list of files that should not be packed into the epub file. 176 | epub_exclude_files = ['search.html'] 177 | 178 | 179 | # -- Extension configuration ------------------------------------------------- -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. ShowCallStack documentation master file, created by 2 | sphinx-quickstart on Mon Nov 12 14:17:27 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to ShowCallStack's documentation! 7 | ======================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/showcallstack/7c96f946a226a43005473d7e0f63a0139b681a74/pyproject.toml -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | from setuptools import setup, find_packages 5 | 6 | scriptFolder = os.path.dirname(os.path.realpath(__file__)) 7 | os.chdir(scriptFolder) 8 | 9 | # Find version info from module (without importing the module): 10 | with open("src/showcallstack/__init__.py", "r") as fileObj: 11 | version = re.search( 12 | r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fileObj.read(), re.MULTILINE 13 | ).group(1) 14 | 15 | # Use the README.md content for the long description: 16 | with io.open("README.md", encoding="utf-8") as fileObj: 17 | long_description = fileObj.read() 18 | 19 | setup( 20 | name="ShowCallStack", 21 | version=version, 22 | url="https://github.com/asweigart/showcallstack", 23 | author="Al Sweigart", 24 | author_email="al@inventwithpython.com", 25 | description=("""Shows a simplified view of the call stack."""), 26 | long_description=long_description, 27 | long_description_content_type="text/markdown", 28 | license="MIT", 29 | packages=find_packages(where="src"), 30 | package_dir={"": "src"}, 31 | test_suite="tests", 32 | install_requires=[], 33 | keywords="", 34 | classifiers=[ 35 | "License :: OSI Approved :: MIT License", 36 | "Programming Language :: Python", 37 | "Programming Language :: Python :: 3", 38 | "Programming Language :: Python :: 3.5", 39 | "Programming Language :: Python :: 3.6", 40 | "Programming Language :: Python :: 3.7", 41 | ], 42 | ) 43 | -------------------------------------------------------------------------------- /src/showcallstack/__init__.py: -------------------------------------------------------------------------------- 1 | """showcallstack 2 | By Al Sweigart al@inventwithpython.com 3 | 4 | Shows a simplified view of the call stack.""" 5 | 6 | __version__ = '0.3.0' 7 | 8 | import traceback, inspect, pprint 9 | 10 | 11 | def showcallstack(): 12 | print('\n'.join(getcallstack())) 13 | 14 | 15 | def getcallstack(): 16 | outputStrings = [] 17 | 18 | tracebackInfo = list(reversed(traceback.extract_stack()[:-2])) 19 | localVars = [] 20 | frame = inspect.currentframe().f_back 21 | while frame is not None: 22 | currentLocalVars = dict(frame.f_locals) 23 | if 'showcallstack' in currentLocalVars: 24 | del currentLocalVars['showcallstack'] 25 | if 'getcallstack' in frame.f_locals: 26 | del currentLocalVars['getcallstack'] 27 | 28 | localVars.append(currentLocalVars) 29 | 30 | if frame.f_back is None: 31 | # Grab the global variables: 32 | globalVars = frame.f_globals 33 | frame = frame.f_back 34 | 35 | # Get rid of frame/global variables in the global scope. 36 | tracebackInfo.pop() 37 | localVars.pop() 38 | 39 | tracebackInfo.reverse() 40 | localVars.reverse() 41 | 42 | # Display the frame and local variable info: 43 | outputStrings.append('The call stack is %s call(s) deep:' % (len(tracebackInfo))) 44 | if len(tracebackInfo) == 0: 45 | # Show a special message explaining that we were called from the global scope. 46 | outputStrings.append(' showcallstack() was called in the global scope and not in a function call.') 47 | else: 48 | 49 | outputStrings.append('(Here is the "bottom" of the call stack.)') 50 | for i, v in enumerate(tracebackInfo): 51 | outputStrings.append('Local variables of call to %s():' % (v.name))# + pprint.pformat(localVars[i])) 52 | for localVarName, localVarValue in localVars[i].items(): 53 | outputStrings.append(' ' + localVarName + ' (type: ' + type(localVarValue).__qualname__ + ') == ' + repr(localVarValue)) 54 | 55 | outputStrings.append('(Here is the "top" of the call stack.)') 56 | outputStrings.append('') 57 | 58 | # Filter the global variables. 59 | reportedGlobalVars = {} 60 | for k, v in globalVars.items(): 61 | if k.startswith('__') or inspect.isfunction(v) or inspect.ismodule(v) or inspect.isclass(v): 62 | # Skip variables that are functions, modules, classes, etc: 63 | continue 64 | reportedGlobalVars[k] = (type(v).__qualname__, v) 65 | 66 | outputStrings.append('Global variables:') 67 | if len(reportedGlobalVars) == 0: 68 | outputStrings.append(' No global variables.') 69 | else: 70 | for k in sorted(list(reportedGlobalVars.keys())): 71 | varTypeAsStr = reportedGlobalVars[k][0] 72 | varValueAsStr = repr(reportedGlobalVars[k][1]) 73 | outputStrings.append(' ' + k + ' (type: ' + varTypeAsStr + ') == ' + varValueAsStr) 74 | 75 | outputStrings.append('') 76 | return outputStrings 77 | 78 | -------------------------------------------------------------------------------- /src/showcallstack/__main__.py: -------------------------------------------------------------------------------- 1 | import showcallstack 2 | 3 | if __name__ == '__main__': 4 | pass 5 | -------------------------------------------------------------------------------- /tests/test_showcallstack.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function 2 | import pytest 3 | import showcallstack 4 | 5 | 6 | def test_basic(): 7 | pass # TODO - add unit tests 8 | 9 | 10 | if __name__ == "__main__": 11 | pytest.main() 12 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py34, py35, py36, py37 8 | 9 | [testenv] 10 | deps = 11 | pytest 12 | commands = 13 | pytest 14 | --------------------------------------------------------------------------------