├── .codecov.yml
├── .coveragerc
├── .flake8
├── .github
├── dependabot.yml
└── workflows
│ ├── auto-merge.yml
│ ├── ci.yml
│ └── codeql.yml
├── .gitignore
├── .mypy.ini
├── .pre-commit-config.yaml
├── CHANGES.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── aiohttp_debugtoolbar
├── __init__.py
├── main.py
├── middlewares.py
├── panels
│ ├── __init__.py
│ ├── base.py
│ ├── headers.py
│ ├── logger.py
│ ├── middlewares.py
│ ├── performance.py
│ ├── request_vars.py
│ ├── routes.py
│ ├── settings.py
│ ├── templates
│ │ ├── headers.jinja2
│ │ ├── logger.jinja2
│ │ ├── middlewares.jinja2
│ │ ├── performance.jinja2
│ │ ├── request_vars.jinja2
│ │ ├── routes.jinja2
│ │ ├── settings.jinja2
│ │ ├── sqlalchemy_explain.jinja2
│ │ ├── sqlalchemy_select.jinja2
│ │ ├── traceback.jinja2
│ │ └── versions.jinja2
│ ├── traceback.py
│ └── versions.py
├── py.typed
├── static
│ ├── css
│ │ ├── bootstrap.min.css
│ │ ├── dashboard.css
│ │ ├── debugger.css
│ │ ├── highlightjs_default.min.css
│ │ ├── prism.css
│ │ ├── toolbar.css
│ │ └── toolbar_button.css
│ ├── font
│ │ ├── FONT_LICENSE
│ │ └── ubuntu.ttf
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ ├── img
│ │ ├── aiohttp.svg
│ │ ├── asc.gif
│ │ ├── back.png
│ │ ├── back_hover.png
│ │ ├── bg.gif
│ │ ├── close.png
│ │ ├── close_hover.png
│ │ ├── console.png
│ │ ├── desc.gif
│ │ ├── headerbg.png
│ │ ├── indicator.png
│ │ ├── less.png
│ │ ├── more.png
│ │ ├── panel_bg.png
│ │ ├── source.png
│ │ ├── tick-red.png
│ │ └── tick.png
│ └── js
│ │ ├── README.rst
│ │ ├── bootstrap.min.js
│ │ ├── debugger.js
│ │ ├── highlight.min.js
│ │ ├── jquery-1.10.2.min.js
│ │ ├── jquery-1.10.2.min.map
│ │ ├── jquery.cookie.js
│ │ ├── jquery.tablesorter.min.js
│ │ ├── prism.js
│ │ ├── r.js
│ │ ├── require.js
│ │ ├── tests.html
│ │ └── toolbar.js
├── tbtools
│ ├── __init__.py
│ ├── console.py
│ ├── repr.py
│ └── tbtools.py
├── templates
│ ├── console.jinja2
│ ├── exception.jinja2
│ ├── exception_summary.jinja2
│ ├── global_tab.jinja2
│ ├── history_tab.jinja2
│ ├── redirect.jinja2
│ ├── settings_tab.jinja2
│ └── toolbar.jinja2
├── toolbar.py
├── utils.py
└── views.py
├── demo
├── README.rst
├── aiohttp_debugtoolba_sceenshot.png
├── demo.py
├── static
│ ├── jquery-1.7.2.min.js
│ ├── main.js
│ └── require-1.0.6.js
└── templates
│ ├── ajax.jinja2
│ ├── error.jinja2
│ └── index.jinja2
├── examples
├── extra_panels
│ ├── extra_pgsql.py
│ ├── extra_redis.py
│ ├── extra_tpl
│ │ ├── request_pgsql.jinja2
│ │ └── request_redis.jinja2
│ └── server.py
└── simple.py
├── pytest.ini
├── requirements-dev.txt
├── requirements.txt
├── setup.py
└── tests
├── conftest.py
├── pep492
└── test_await.py
├── test_debug.py
├── test_exception_views.py
├── test_imports.py
├── test_middleware.py
├── test_panel.py
├── test_panels_versions.py
├── test_server_push.py
├── test_utils.py
└── tpl
└── test.jinja2
/.codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | branch: master
3 | notify:
4 | after_n_builds: 10
5 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = aiohttp_debugtoolbar, tests
4 | omit = site-packages, .tox
5 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | enable-extensions = G
3 | max-doc-length = 90
4 | max-line-length = 90
5 | select = A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,B901,B902,B903,B950
6 | # E226: Missing whitespace around arithmetic operators can help group things together.
7 | # E501: Superseeded by B950 (from Bugbear)
8 | # E722: Superseeded by B001 (from Bugbear)
9 | # W503: Mutually exclusive with W504.
10 | ignore = E226,E501,E722,W503
11 | per-file-ignores =
12 | # I900: Requirements not needed for examples
13 | examples/*:I900
14 | # S101: Pytest uses assert
15 | tests/*:S101
16 |
17 | # flake8-import-order
18 | application-import-names = aiohttp_debugtoolbar
19 | import-order-style = pycharm
20 |
21 | # flake8-requirements
22 | known-modules = :[aiohttp_debugtoolbar]
23 | requirements-file = requirements-dev.txt
24 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: pip
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 |
8 | - package-ecosystem: "github-actions"
9 | directory: "/"
10 | schedule:
11 | interval: "monthly"
12 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: Dependabot auto-merge
2 | on: pull_request_target
3 |
4 | permissions:
5 | pull-requests: write
6 | contents: write
7 |
8 | jobs:
9 | dependabot:
10 | runs-on: ubuntu-latest
11 | if: ${{ github.actor == 'dependabot[bot]' }}
12 | steps:
13 | - name: Dependabot metadata
14 | id: metadata
15 | uses: dependabot/fetch-metadata@v2.4.0
16 | with:
17 | github-token: "${{ secrets.GITHUB_TOKEN }}"
18 | - name: Enable auto-merge for Dependabot PRs
19 | run: gh pr merge --auto --squash "$PR_URL"
20 | env:
21 | PR_URL: ${{github.event.pull_request.html_url}}
22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
23 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6
8 | tags: [ 'v*' ]
9 | pull_request:
10 | branches:
11 | - master
12 | - '[0-9].[0-9]+'
13 |
14 |
15 | jobs:
16 | lint:
17 | name: Linter
18 | runs-on: ubuntu-latest
19 | timeout-minutes: 5
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v4
23 | - name: Setup Python
24 | uses: actions/setup-python@v5
25 | with:
26 | python-version: 3.9
27 | cache: 'pip'
28 | cache-dependency-path: '**/requirements*.txt'
29 | - name: Pre-Commit hooks
30 | uses: pre-commit/action@v3.0.1
31 | - name: Install dependencies
32 | uses: py-actions/py-dependency-install@v4
33 | with:
34 | path: requirements-dev.txt
35 | - name: Install itself
36 | run: |
37 | pip install .
38 | - name: Mypy
39 | run: mypy
40 | - name: Prepare twine checker
41 | run: |
42 | pip install -U build twine wheel
43 | python -m build
44 | - name: Run twine checker
45 | run: |
46 | twine check dist/*
47 |
48 | test:
49 | name: Test
50 | strategy:
51 | matrix:
52 | pyver: ['3.9', '3.10', '3.11', '3.12', '3.13']
53 | os: [ubuntu, macos, windows]
54 | include:
55 | - pyver: pypy-3.9
56 | os: ubuntu
57 | runs-on: ${{ matrix.os }}-latest
58 | timeout-minutes: 15
59 | steps:
60 | - name: Checkout
61 | uses: actions/checkout@v4
62 | - name: Setup Python ${{ matrix.pyver }}
63 | uses: actions/setup-python@v5
64 | with:
65 | python-version: ${{ matrix.pyver }}
66 | cache: 'pip'
67 | cache-dependency-path: '**/requirements*.txt'
68 | - name: Install dependencies
69 | uses: py-actions/py-dependency-install@v4
70 | with:
71 | path: requirements.txt
72 | - name: Run unittests
73 | run: pytest --cov-report=xml tests
74 | env:
75 | COLOR: 'yes'
76 | - run: python -m coverage xml
77 | - name: Upload coverage
78 | uses: codecov/codecov-action@v5
79 | with:
80 | file: ./coverage.xml
81 | flags: unit
82 |
83 | check: # This job does nothing and is only used for the branch protection
84 | if: always()
85 |
86 | needs: [lint, test]
87 |
88 | runs-on: ubuntu-latest
89 |
90 | steps:
91 | - name: Decide whether the needed jobs succeeded or failed
92 | uses: re-actors/alls-green@release/v1
93 | with:
94 | jobs: ${{ toJSON(needs) }}
95 |
96 | deploy:
97 | name: Deploy
98 | environment: release
99 | runs-on: ubuntu-latest
100 | needs: check
101 | # Run only on pushing a tag
102 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
103 | steps:
104 | - name: Checkout
105 | uses: actions/checkout@v4
106 | - name: Setup Python
107 | uses: actions/setup-python@v5
108 | with:
109 | python-version: 3.13
110 | - name: Install dependencies
111 | run:
112 | python -m pip install -U build setuptools wheel twine
113 | - name: Make dists
114 | run:
115 | python -m build
116 | - name: Make Release
117 | uses: aio-libs/create-release@v1.6.6
118 | with:
119 | changes_file: CHANGES.rst
120 | name: aiohttp-debugtoolbar
121 | version_file: aiohttp_debugtoolbar/__init__.py
122 | github_token: ${{ secrets.GITHUB_TOKEN }}
123 | pypi_token: ${{ secrets.PYPI_API_TOKEN }}
124 | dist_dir: dist
125 | fix_issue_regex: "`#(\\d+) `"
126 | fix_issue_repl: "(#\\1)"
127 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 | schedule:
9 | - cron: "33 2 * * 2"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ python ]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v4
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v3
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v3
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v3
40 | with:
41 | category: "/language:${{ matrix.language }}"
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ># Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | venv/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .cache
41 | nosetests.xml
42 | coverage.xml
43 | cover
44 |
45 | # Translations
46 | *.mo
47 | *.pot
48 |
49 | # Django stuff:
50 | *.log
51 |
52 | # Sphinx documentation
53 | docs/_build/
54 |
55 | # PyBuilder
56 | target/
57 |
58 | # PyCharm
59 | .idea
60 |
61 | # vim
62 | *.swp
63 |
64 | .pytest_cache
65 | <<<<<<< HEAD
66 | .python-version
67 | =======
68 | >>>>>>> b32a82ee01dcc2d96a18cf00e5a6a614d880a724
69 |
--------------------------------------------------------------------------------
/.mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | files = aiohttp_debugtoolbar, demo, examples, tests
3 | #check_untyped_defs = True
4 | follow_imports_for_stubs = True
5 | #disallow_any_decorated = True
6 | #disallow_any_generics = True
7 | #disallow_any_unimported = True
8 | disallow_incomplete_defs = True
9 | disallow_subclassing_any = True
10 | #disallow_untyped_calls = True
11 | #disallow_untyped_decorators = True
12 | #disallow_untyped_defs = True
13 | implicit_reexport = False
14 | no_implicit_optional = True
15 | show_error_codes = True
16 | strict_equality = True
17 | warn_incomplete_stub = True
18 | warn_redundant_casts = True
19 | warn_unreachable = True
20 | warn_unused_ignores = True
21 | #warn_return_any = True
22 |
23 |
24 | [mypy-aiopg.*]
25 | ignore_missing_imports = True
26 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: 'v4.4.0'
4 | hooks:
5 | - id: check-merge-conflict
6 | - repo: https://github.com/asottile/yesqa
7 | rev: v1.5.0
8 | hooks:
9 | - id: yesqa
10 | additional_dependencies:
11 | - flake8-bandit==4.1.1
12 | - flake8-bugbear==23.7.10
13 | - flake8-import-order==0.18.2
14 | - flake8-requirements==1.7.8
15 | - repo: https://github.com/psf/black
16 | rev: '23.7.0'
17 | hooks:
18 | - id: black
19 | language_version: python3 # Should be a command that runs python
20 | - repo: https://github.com/pre-commit/pre-commit-hooks
21 | rev: 'v4.4.0'
22 | hooks:
23 | - id: end-of-file-fixer
24 | exclude: >-
25 | ^docs/[^/]*\.svg$
26 | - id: requirements-txt-fixer
27 | - id: trailing-whitespace
28 | - id: file-contents-sorter
29 | files: |
30 | CONTRIBUTORS.txt|
31 | docs/spelling_wordlist.txt|
32 | .gitignore|
33 | .gitattributes
34 | - id: check-case-conflict
35 | - id: check-json
36 | - id: check-xml
37 | - id: check-executables-have-shebangs
38 | - id: check-toml
39 | - id: check-xml
40 | - id: check-yaml
41 | - id: debug-statements
42 | - id: check-added-large-files
43 | - id: check-symlinks
44 | - id: debug-statements
45 | - id: detect-aws-credentials
46 | args: ['--allow-missing-credentials']
47 | - id: detect-private-key
48 | exclude: ^examples/
49 | - repo: https://github.com/PyCQA/flake8
50 | rev: '6.1.0'
51 | hooks:
52 | - id: flake8
53 | exclude: "^docs/"
54 | additional_dependencies:
55 | - flake8-bandit==4.1.1
56 | - flake8-bugbear==23.7.10
57 | - flake8-import-order==0.18.2
58 | - flake8-requirements==1.7.8
59 | - repo: https://github.com/asottile/pyupgrade
60 | rev: 'v3.10.1'
61 | hooks:
62 | - id: pyupgrade
63 | args: ['--py36-plus']
64 | - repo: https://github.com/Lucas-C/pre-commit-hooks-markup
65 | rev: v1.0.1
66 | hooks:
67 | - id: rst-linter
68 | files: >-
69 | ^[^/]+[.]rst$
70 |
--------------------------------------------------------------------------------
/CHANGES.rst:
--------------------------------------------------------------------------------
1 | =======
2 | CHANGES
3 | =======
4 |
5 | .. towncrier release notes start
6 |
7 | 0.6.1 (2023-11-19)
8 | ==================
9 |
10 | - Filtered out requests to debugtoolbar itself from the requests history.
11 | - Improved import time by delaying loading of package data.
12 | - Fixed static URLs when using yarl 1.9+.
13 | - Fixed a warning in the ``re`` module.
14 | - Switched to ``aiohttp.web.AppKey`` for aiohttp 3.9.
15 | - Dropped Python 3.7 and added Python 3.11.
16 |
17 | 0.6.0 (2020-01-25)
18 | ==================
19 |
20 | - Fixed ClassBasedView support. #207
21 | - Dropped aiohttp<3.3 support.
22 | - Dropped Python 3.4 support.
23 | - Dropped ``yield from`` and ``@asyncio.coroutine`` support.
24 |
25 | 0.5.0 (2018-02-14)
26 | ==================
27 |
28 | - Added safe filter to render_content. #195
29 | - Added support for aiohtp 3.
30 |
31 | 0.4.1 (2017-08-30)
32 | ==================
33 |
34 | - Fixed issue with redirects without location header. #174
35 |
36 | 0.4.0 (2017-05-04)
37 | ==================
38 |
39 | - Added asyncio trove classifier.
40 | - Addes support for aiohttp 2.0.7+.
41 |
42 | 0.3.0 (2016-11-18)
43 | ==================
44 |
45 | - Fixed middleware route finding when using sub-apps. #65
46 | - Added examples for extra panels: pgsql & redis monitor. #59
47 |
48 | 0.2.0 (2016-11-08)
49 | ==================
50 |
51 | - Refactored test suite.
52 |
53 | 0.1.4 (2016-11-07)
54 | ==================
55 |
56 | - Renamed to aiohttp-debugtoolbar.
57 | - Fixed imcompatibility with aiohttp 1.1.
58 |
59 | 0.1.3 (2016-10-27)
60 | ==================
61 |
62 | - Fixed a link to request info page, sort request information alphabetically. #52
63 |
64 | 0.1.2 (2016-09-27)
65 | ==================
66 |
67 | - Fixed empty functions names in performance panel. #43 (Thanks @kammala!)
68 | - Fixed flashing message during page rendering issue. #46
69 |
70 | 0.1.1 (2016-02-21)
71 | ==================
72 |
73 | - Fixed a demo.
74 | - Added syntax highlight in traceback view, switched highlighter from
75 | highlight.js to prism.js. #31
76 |
77 | 0.1.0 (2016-02-13)
78 | ==================
79 |
80 | - Added Python 3.5 support. (Thanks @stormandco!)
81 | - Added view source button in RoutesDebugPanel. (Thanks @stormandco!)
82 | - Dropped support for Python 3.3. (Thanks @sloria!)
83 | - Added middleware in setup method. (Thanks @sloria!)
84 | - Fixed bug with interactive console.
85 | - Fixed support for aiohttp>=0.21.1.
86 |
87 | 0.0.5 (2015-09-13)
88 | ==================
89 |
90 | - Fixed IPv6 socket family error. (Thanks @stormandco!)
91 |
92 | 0.0.4 (2015-09-05)
93 | ==================
94 |
95 | - Fixed support for aiohttp>=0.17. (Thanks @himikof!)
96 |
97 | 0.0.3 (2015-07-03)
98 | ==================
99 |
100 | - Switched template engine from mako to jinja2. (Thanks @iho!)
101 | - Added custom *yield from* to track context switches inside coroutine.
102 | - Implemented panel for collecting request log messages.
103 | - Disable toolbar code injecting for non web.Response answers
104 | (StreamResponse or WebSocketResponse for example). #12
105 |
106 | 0.0.2 (2015-05-26)
107 | ==================
108 |
109 | - Redesigned UI look-and-feel.
110 | - Renamed `toolbar_middleware_factory` to just `middleware`.
111 |
112 | 0.0.1 (2015-05-18)
113 | ==================
114 |
115 | - Initial release.
116 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include CHANGES.rst
3 | include README.rst
4 | graft aiohttp_debugtoolbar
5 | global-exclude *.pyc *.swp
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Some simple testing tasks (sorry, UNIX only).
2 |
3 | flake:
4 | flake8 --exclude=pep492 aiohttp_debugtoolbar tests
5 |
6 | test: flake
7 | pytest -s ./tests/
8 |
9 | vtest:
10 | pytest -s -v ./tests/
11 |
12 | cov cover coverage: flake
13 | pytest -s --cov-report term --cov-report html --cov aiohttp_debugtoolbar
14 | @echo "open file://`pwd`/htmlcov/index.html"
15 |
16 | clean:
17 | rm -rf `find . -name __pycache__`
18 | rm -f `find . -type f -name '*.py[co]' `
19 | rm -f `find . -type f -name '*~' `
20 | rm -f `find . -type f -name '.*~' `
21 | rm -f `find . -type f -name '@*' `
22 | rm -f `find . -type f -name '#*#' `
23 | rm -f `find . -type f -name '*.orig' `
24 | rm -f `find . -type f -name '*.rej' `
25 | rm -f .coverage
26 | rm -rf coverage
27 | rm -rf build
28 | rm -rf cover
29 | rm -rf htmlcov
30 |
31 | doc:
32 | make -C docs html
33 | @echo "open file://`pwd`/docs/_build/html/index.html"
34 |
35 | .PHONY: all build venv flake test vtest testloop cov clean doc
36 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | aiohttp-debugtoolbar
2 | ====================
3 | .. image:: https://travis-ci.org/aio-libs/aiohttp-debugtoolbar.svg?branch=master
4 | :target: https://travis-ci.org/aio-libs/aiohttp-debugtoolbar
5 | :alt: |Build status|
6 | .. image:: https://codecov.io/gh/aio-libs/aiohttp-debugtoolbar/branch/master/graph/badge.svg
7 | :target: https://codecov.io/gh/aio-libs/aiohttp-debugtoolbar
8 | :alt: |Coverage status|
9 | .. image:: https://img.shields.io/pypi/v/aiohttp-debugtoolbar.svg
10 | :target: https://pypi.python.org/pypi/aiohttp-debugtoolbar
11 | :alt: PyPI
12 | .. image:: https://badges.gitter.im/Join%20Chat.svg
13 | :target: https://gitter.im/aio-libs/Lobby
14 | :alt: Chat on Gitter
15 |
16 | **aiohttp_debugtoolbar** provides a debug toolbar for your aiohttp_
17 | web application. Library is port of pyramid_debugtoolbar_ and
18 | still in early development stages. Basic functionality has been
19 | ported:
20 |
21 | * basic panels
22 | * intercept redirects
23 | * intercept and pretty print exception
24 | * interactive python console
25 | * show source code
26 |
27 | .. image:: https://raw.githubusercontent.com/aio-libs/aiohttp_debugtoolbar/master/demo/aiohttp_debugtoolba_sceenshot.png
28 |
29 |
30 | Ported Panels
31 | -------------
32 | ``HeaderDebugPanel``, ``PerformanceDebugPanel``, ``TracebackPanel``,
33 | ``SettingsDebugPanel``, ``MiddlewaresDebugPanel``, ``VersionDebugPanel``,
34 | ``RoutesDebugPanel``, ``RequestVarsDebugPanel``, ``LoggingPanel``
35 |
36 |
37 | Help Needed
38 | -----------
39 | Are you coder looking for a project to contribute to
40 | python/asyncio libraries? This is the project for you!
41 |
42 |
43 | Install and Configuration
44 | -------------------------
45 | ::
46 |
47 | $ pip install aiohttp_debugtoolbar
48 |
49 |
50 | In order to plug in ``aiohttp_debugtoolbar``, call
51 | ``aiohttp_debugtoolbar.setup`` on your app.
52 |
53 | .. code:: python
54 |
55 | import aiohttp_debugtoolbar
56 | app = web.Application(loop=loop)
57 | aiohttp_debugtoolbar.setup(app)
58 |
59 |
60 | Full Example
61 | ------------
62 |
63 | .. code:: python
64 |
65 | import asyncio
66 | import jinja2
67 | import aiohttp_debugtoolbar
68 | import aiohttp_jinja2
69 |
70 | from aiohttp import web
71 |
72 |
73 | @aiohttp_jinja2.template('index.html')
74 | async def basic_handler(request):
75 | return {'title': 'example aiohttp_debugtoolbar!',
76 | 'text': 'Hello aiohttp_debugtoolbar!',
77 | 'app': request.app}
78 |
79 |
80 | async def exception_handler(request):
81 | raise NotImplementedError
82 |
83 |
84 | async def init(loop):
85 | # add aiohttp_debugtoolbar middleware to you application
86 | app = web.Application(loop=loop)
87 | # install aiohttp_debugtoolbar
88 | aiohttp_debugtoolbar.setup(app)
89 |
90 | template = """
91 |
92 |
93 | {{ title }}
94 |
95 |
96 | {{ text }}
97 |
98 |
99 | Exception example
100 |
101 |
102 |
103 | """
104 | # install jinja2 templates
105 | loader = jinja2.DictLoader({'index.html': template})
106 | aiohttp_jinja2.setup(app, loader=loader)
107 |
108 | # init routes for index page, and page with error
109 | app.router.add_route('GET', '/', basic_handler, name='index')
110 | app.router.add_route('GET', '/exc', exception_handler,
111 | name='exc_example')
112 | return app
113 |
114 |
115 | loop = asyncio.get_event_loop()
116 | app = loop.run_until_complete(init(loop))
117 | web.run_app(app, host='127.0.0.1', port=9000)
118 |
119 | Settings
120 | --------
121 | .. code:: python
122 |
123 | aiohttp_debugtoolbar.setup(app, hosts=['172.19.0.1', ])
124 |
125 | Supported options
126 |
127 |
128 | - enabled: The debugtoolbar is disabled if False. By default is set to True.
129 | - intercept_redirects: If True, intercept redirect and display an intermediate page with a link to the redirect page. By default is set to True.
130 | - hosts: The list of allow hosts. By default is set to ['127.0.0.1', '::1'].
131 | - exclude_prefixes: The list of forbidden hosts. By default is set to [].
132 | - check_host: If False, disable the host check and display debugtoolbar for any host. By default is set to True.
133 | - max_request_history: The max value for storing requests. By default is set to 100.
134 | - max_visible_requests: The max value of display requests. By default is set to 10.
135 | - path_prefix: The prefix of path to debugtoolbar. By default is set to '/_debugtoolbar'.
136 |
137 |
138 | Thanks!
139 | -------
140 |
141 | I've borrowed a lot of code from following projects. I highly
142 | recommend to check them out:
143 |
144 | * pyramid_debugtoolbar_
145 | * django-debug-toolbar_
146 | * flask-debugtoolbar_
147 |
148 | Play With Demo
149 | --------------
150 |
151 | https://github.com/aio-libs/aiohttp_debugtoolbar/tree/master/demo
152 |
153 | Requirements
154 | ------------
155 |
156 | * aiohttp_
157 | * aiohttp_jinja2_
158 |
159 |
160 | .. _Python: https://www.python.org
161 | .. _asyncio: http://docs.python.org/3/library/asyncio.html
162 | .. _aiohttp: https://github.com/KeepSafe/aiohttp
163 | .. _aiopg: https://github.com/aio-libs/aiopg
164 | .. _aiomysql: https://github.com/aio-libs/aiomysql
165 | .. _aiohttp_jinja2: https://github.com/aio-libs/aiohttp_jinja2
166 | .. _pyramid_debugtoolbar: https://github.com/Pylons/pyramid_debugtoolbar
167 | .. _django-debug-toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar
168 | .. _flask-debugtoolbar: https://github.com/mgood/flask-debugtoolbar
169 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/__init__.py:
--------------------------------------------------------------------------------
1 | from .main import setup
2 | from .middlewares import middleware
3 | from .utils import APP_KEY
4 |
5 | __version__ = "0.6.1"
6 |
7 | __all__ = ("setup", "middleware", "APP_KEY")
8 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/main.py:
--------------------------------------------------------------------------------
1 | import secrets
2 | from pathlib import Path
3 | from typing import Iterable, Literal, Sequence, Type, TypedDict, Union
4 |
5 | import aiohttp_jinja2
6 | import jinja2
7 | from aiohttp import web
8 |
9 | from . import panels, views
10 | from .middlewares import middleware
11 | from .panels.base import DebugPanel
12 | from .utils import (
13 | APP_KEY,
14 | AppState,
15 | ExceptionHistory,
16 | STATIC_ROUTE_NAME,
17 | TEMPLATE_KEY,
18 | ToolbarStorage,
19 | _Config,
20 | )
21 | from .views import ExceptionDebugView
22 |
23 | default_panel_names = (
24 | panels.HeaderDebugPanel,
25 | panels.PerformanceDebugPanel,
26 | panels.RequestVarsDebugPanel,
27 | panels.TracebackPanel,
28 | panels.LoggingPanel,
29 | )
30 |
31 |
32 | default_global_panel_names = (
33 | panels.RoutesDebugPanel,
34 | panels.SettingsDebugPanel,
35 | panels.MiddlewaresDebugPanel,
36 | panels.VersionDebugPanel,
37 | )
38 |
39 |
40 | class _AppDetails(TypedDict):
41 | exc_history: ExceptionHistory
42 | pdtb_token: str
43 | request_history: ToolbarStorage
44 | settings: _Config
45 |
46 |
47 | def setup(
48 | app: web.Application,
49 | *,
50 | enabled: bool = True,
51 | intercept_exc: Literal["debug", "display", False] = "debug",
52 | intercept_redirects: bool = True,
53 | panels: Iterable[Type[DebugPanel]] = default_panel_names,
54 | extra_panels: Iterable[Type[DebugPanel]] = (),
55 | extra_templates: Union[str, Path, Iterable[Union[str, Path]]] = (),
56 | global_panels: Iterable[Type[DebugPanel]] = default_global_panel_names,
57 | hosts: Sequence[str] = ("127.0.0.1", "::1"),
58 | exclude_prefixes: Iterable[str] = (),
59 | check_host: bool = True, # disable host check
60 | button_style: str = "",
61 | max_request_history: int = 100,
62 | max_visible_requests: int = 10,
63 | path_prefix: str = "/_debugtoolbar",
64 | ) -> None:
65 | config = _Config(
66 | enabled=enabled,
67 | intercept_exc=intercept_exc,
68 | intercept_redirects=intercept_redirects,
69 | panels=tuple(panels),
70 | extra_panels=tuple(extra_panels),
71 | global_panels=tuple(global_panels),
72 | hosts=hosts,
73 | exclude_prefixes=tuple(exclude_prefixes),
74 | check_host=check_host,
75 | button_style=button_style,
76 | max_visible_requests=max_visible_requests,
77 | path_prefix=path_prefix,
78 | )
79 |
80 | if middleware not in app.middlewares:
81 | app.middlewares.append(middleware)
82 |
83 | APP_ROOT = Path(__file__).parent
84 | templates_app = APP_ROOT / "templates"
85 | templates_panels = APP_ROOT / "panels/templates"
86 |
87 | if isinstance(extra_templates, (str, Path)):
88 | templ: Iterable[Union[str, Path]] = (extra_templates,)
89 | else:
90 | templ = extra_templates
91 | loader = jinja2.FileSystemLoader((templates_app, templates_panels, *templ))
92 | aiohttp_jinja2.setup(app, loader=loader, app_key=TEMPLATE_KEY)
93 |
94 | static_location = APP_ROOT / "static"
95 |
96 | exc_handlers = ExceptionDebugView()
97 |
98 | app.router.add_static(
99 | path_prefix + "/static", static_location, name=STATIC_ROUTE_NAME
100 | )
101 |
102 | app.router.add_route(
103 | "GET", path_prefix + "/source", exc_handlers.source, name="debugtoolbar.source"
104 | )
105 | app.router.add_route(
106 | "GET",
107 | path_prefix + "/execute",
108 | exc_handlers.execute,
109 | name="debugtoolbar.execute",
110 | )
111 | # app.router.add_route('GET', path_prefix + '/console',
112 | # exc_handlers.console,
113 | # name='debugtoolbar.console')
114 | app.router.add_route(
115 | "GET",
116 | path_prefix + "/exception",
117 | exc_handlers.exception,
118 | name="debugtoolbar.exception",
119 | )
120 | # TODO: fix when sql will be ported
121 | # app.router.add_route('GET', path_prefix + '/sqlalchemy/sql_select',
122 | # name='debugtoolbar.sql_select')
123 | # app.router.add_route('GET', path_prefix + '/sqlalchemy/sql_explain',
124 | # name='debugtoolbar.sql_explain')
125 |
126 | app.router.add_route(
127 | "GET", path_prefix + "/sse", views.sse, name="debugtoolbar.sse"
128 | )
129 |
130 | app.router.add_route(
131 | "GET",
132 | path_prefix + "/{request_id}",
133 | views.request_view,
134 | name="debugtoolbar.request",
135 | )
136 | app.router.add_route(
137 | "GET", path_prefix, views.request_view, name="debugtoolbar.main"
138 | )
139 |
140 | app[APP_KEY] = AppState(
141 | {
142 | "exc_history": ExceptionHistory(),
143 | "pdtb_token": secrets.token_hex(10),
144 | "request_history": ToolbarStorage(max_request_history),
145 | "settings": config,
146 | }
147 | )
148 | if intercept_exc:
149 | app[APP_KEY]["exc_history"].eval_exc = intercept_exc == "debug"
150 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/middlewares.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import aiohttp_jinja2
4 | from aiohttp import web
5 | from aiohttp.typedefs import Handler
6 | from aiohttp.web_exceptions import HTTPMove
7 |
8 | from .tbtools.tbtools import get_traceback
9 | from .toolbar import DebugToolbar
10 | from .utils import (
11 | APP_KEY,
12 | ContextSwitcher,
13 | REDIRECT_CODES,
14 | TEMPLATE_KEY,
15 | addr_in,
16 | hexlify,
17 | )
18 |
19 | __all__ = ("middleware",)
20 | HTML_TYPES = ("text/html", "application/xhtml+xml")
21 |
22 |
23 | @web.middleware
24 | async def middleware(request: web.Request, handler: Handler) -> web.StreamResponse:
25 | app = request.app
26 |
27 | if APP_KEY not in app:
28 | raise RuntimeError(
29 | "Please setup debug toolbar with " "aiohttp_debugtoolbar.setup method"
30 | )
31 |
32 | # just create namespace for handler
33 | settings = app[APP_KEY]["settings"]
34 | request_history = app[APP_KEY]["request_history"]
35 | exc_history = app[APP_KEY]["exc_history"]
36 | intercept_exc = app[APP_KEY]["settings"]["intercept_exc"]
37 |
38 | if not app[APP_KEY]["settings"]["enabled"]:
39 | return await handler(request)
40 |
41 | # request['exc_history'] = exc_history
42 | panel_classes = settings.get("panels", ()) + settings.get("extra_panels", ())
43 | global_panel_classes = settings.get("global_panels", ())
44 | hosts = settings.get("hosts", [])
45 |
46 | show_on_exc_only = settings.get("show_on_exc_only")
47 | intercept_redirects = settings["intercept_redirects"]
48 |
49 | root_url = app.router["debugtoolbar.main"].url_for().raw_path
50 | exclude_prefixes = settings.get("exclude_prefixes", ())
51 | exclude = (root_url,) + exclude_prefixes
52 |
53 | p = request.raw_path
54 | starts_with_excluded = list(filter(None, map(p.startswith, exclude)))
55 |
56 | # FIXME: error when run trough unixsocket
57 | if request.transport:
58 | peername = request.transport.get_extra_info("peername")
59 | last_proxy_addr = peername[0]
60 | else:
61 | last_proxy_addr = ""
62 |
63 | # TODO: rethink access policy by host
64 | if settings.get("check_host"):
65 | if starts_with_excluded or not addr_in(last_proxy_addr, hosts):
66 | return await handler(request)
67 |
68 | toolbar = DebugToolbar(request, panel_classes, global_panel_classes)
69 | _handler = handler
70 |
71 | context_switcher = ContextSwitcher()
72 | for panel in toolbar.panels:
73 | _handler = panel.wrap_handler(_handler, context_switcher)
74 |
75 | try:
76 | response = await context_switcher(_handler(request))
77 | except HTTPMove as exc:
78 | if not intercept_redirects:
79 | raise
80 | # Intercept http redirect codes and display an html page with a
81 | # link to the target.
82 | if not getattr(exc, "location", None):
83 | raise
84 | response = web.Response(
85 | status=exc.status, reason=exc.reason, text=exc.text, headers=exc.headers
86 | )
87 |
88 | context = {"redirect_to": exc.location, "redirect_code": exc.status}
89 |
90 | _response = aiohttp_jinja2.render_template(
91 | "redirect.jinja2", request, context, app_key=TEMPLATE_KEY
92 | )
93 | response = _response
94 | except web.HTTPException:
95 | raise
96 | except Exception as e:
97 | if intercept_exc:
98 | tb = get_traceback(
99 | info=sys.exc_info(),
100 | skip=1,
101 | show_hidden_frames=False,
102 | ignore_system_exceptions=True,
103 | exc=e,
104 | app=request.app,
105 | )
106 | for frame in tb.frames:
107 | exc_history.frames[frame.id] = frame
108 | exc_history.tracebacks[tb.id] = tb
109 | request["pdbt_tb"] = tb
110 |
111 | # TODO: find out how to port following to aiohttp
112 | # or just remove it
113 | # token = request.app[APP_KEY]['pdtb_token']
114 | # qs = {'token': token, 'tb': str(tb.id)}
115 | # msg = 'Exception at %s\ntraceback url: %s'
116 | #
117 | # exc_url = request.app.router['debugtoolbar.exception']\
118 | # .url(query=qs)
119 | # assert exc_url, msg
120 | # exc_msg = msg % (request.path, exc_url)
121 | # logger.exception(exc_msg)
122 |
123 | # subenviron = request.environ.copy()
124 | # del subenviron['PATH_INFO']
125 | # del subenviron['QUERY_STRING']
126 | # subrequest = type(request).blank(exc_url, subenviron)
127 | # subrequest.script_name = request.script_name
128 | # subrequest.path_info = \
129 | # subrequest.path_info[len(request.script_name):]
130 | #
131 | # response = request.invoke_subrequest(subrequest)
132 | body = tb.render_full(request).encode("utf-8", "replace")
133 | response = web.Response(body=body, status=500, content_type="text/html")
134 |
135 | await toolbar.process_response(request, response)
136 |
137 | request["id"] = str(id(request))
138 | toolbar.status = response.status
139 |
140 | request_history.put(request["id"], toolbar)
141 | toolbar.inject(request, response)
142 | return response
143 | else:
144 | # logger.exception('Exception at %s' % request.path)
145 | raise e
146 | toolbar.status = response.status
147 | if intercept_redirects:
148 | # Intercept http redirect codes and display an html page with a
149 | # link to the target.
150 | if response.status in REDIRECT_CODES and getattr(response, "location", None):
151 | context = {
152 | "redirect_to": response.location,
153 | "redirect_code": response.status,
154 | }
155 |
156 | _response = aiohttp_jinja2.render_template(
157 | "redirect.jinja2", request, context, app_key=TEMPLATE_KEY
158 | )
159 | response = _response
160 |
161 | await toolbar.process_response(request, response)
162 | request["id"] = hexlify(id(request))
163 |
164 | # Don't store the favicon.ico request
165 | # it's requested by the browser automatically
166 | # Also ignore requests for debugtoolbar itself.
167 | tb_request = request.path.startswith(settings["path_prefix"])
168 | if not tb_request and request.path != "/favicon.ico":
169 | request_history.put(request["id"], toolbar)
170 |
171 | if not show_on_exc_only and response.content_type in HTML_TYPES:
172 | toolbar.inject(request, response)
173 |
174 | return response
175 |
176 |
177 | toolbar_html_template = """\
178 |
185 |
186 |
193 | """
194 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/__init__.py:
--------------------------------------------------------------------------------
1 | from .headers import HeaderDebugPanel as HeaderDebugPanel
2 | from .logger import LoggingPanel as LoggingPanel
3 | from .middlewares import MiddlewaresDebugPanel as MiddlewaresDebugPanel
4 | from .performance import PerformanceDebugPanel as PerformanceDebugPanel
5 | from .request_vars import RequestVarsDebugPanel as RequestVarsDebugPanel
6 | from .routes import RoutesDebugPanel as RoutesDebugPanel
7 | from .settings import SettingsDebugPanel as SettingsDebugPanel
8 | from .traceback import TracebackPanel as TracebackPanel
9 | from .versions import VersionDebugPanel as VersionDebugPanel
10 |
11 | __all__ = (
12 | "HeaderDebugPanel",
13 | "LoggingPanel",
14 | "MiddlewaresDebugPanel",
15 | "PerformanceDebugPanel",
16 | "RequestVarsDebugPanel",
17 | "RoutesDebugPanel",
18 | "SettingsDebugPanel",
19 | "TracebackPanel",
20 | "VersionDebugPanel",
21 | )
22 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/base.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 | from ..utils import render
4 |
5 |
6 | class DebugPanel(ABC):
7 | """Base class for debug panels.
8 |
9 | A new instance of this class is created for every request.
10 |
11 | A panel is notified of events throughout the request lifecycle. It
12 | is then persisted and used later by the toolbar to render its results
13 | as a tab on the interface. The lifecycle hooks are available in the
14 | following order:
15 |
16 | - :meth:`.__init__`
17 | - :meth:`.wrap_handler`
18 | - :meth:`.process_beforerender`
19 | - :meth:`.process_response`
20 |
21 | Each of these hooks is overridable by a subclass to gleen information
22 | from the request and other events for later display.
23 |
24 | The panel is later used to render its results. This is done on-demand
25 | and in the lifecycle of a request to the debug toolbar by invoking
26 | :meth:`.render_content`. Any data stored within :attr:`.data` is
27 | injected into the template prior to rendering and is thus a common
28 | location to store the contents of previous events.
29 | """
30 |
31 | #: A unique identifier for the name of the panel. This **must** be
32 | #: defined by a subclass.
33 | name: str
34 |
35 | #: If ``False`` then the panel's tab will be disabled and
36 | #: :meth:`.render_content` will not be invoked. Most subclasses will
37 | #: want to set this to ``True``.
38 | has_content = False
39 |
40 | #: If the client is able to activate/de-activate the panel then this
41 | #: should be ``True``.
42 | user_activate = False
43 |
44 | #: This property will be set by the toolbar, indicating the user's
45 | #: decision to activate or deactivate the panel. If ``user_activate``
46 | #: is ``False`` then ``is_active`` will always be set to ``True``.
47 | is_active = False
48 |
49 | #: Must be overridden by subclasses that are using the default
50 | #: implementation of ``render_content``. This is an
51 | #: :term:`asset specification` pointing at the template to be rendered
52 | #: for the panel. Usually of the format
53 | #: ``'mylib:templates/panel.jinja2'``.
54 | template: str
55 |
56 | #: Title showing in toolbar. Must be overridden.
57 | nav_title: str
58 |
59 | #: Subtitle showing until title in toolbar.
60 | nav_subtitle = ""
61 |
62 | #: Title showing in panel. Must be overridden.
63 | title: str
64 |
65 | #: The URL invoked when the panel's tab is cliked. May be overridden to
66 | #: define an arbitrary URL for the panel or do some other custom action
67 | #: when the user clicks on the panel's tab in the toolbar.
68 | url = ""
69 |
70 | @property
71 | def request(self):
72 | return self._request
73 |
74 | # Panel methods
75 | def __init__(self, request):
76 | """Configure the panel for a request.
77 |
78 | :param request: The instance of :class:`aiohttp.web.Request` that
79 | this object is wrapping.
80 | """
81 | self._request = request
82 | self.data = {}
83 |
84 | def render_content(self, request):
85 | """Return a string containing the HTML to be rendered for the panel.
86 |
87 | By default this will render the template defined by the
88 | :attr:`.template` attribute with a rendering context defined by
89 | :attr:`.data` combined with the ``dict`` returned from
90 | :meth:`.render_vars`.
91 |
92 | The ``request`` here is the active request in the toolbar. Not the
93 | original request that this panel represents.
94 | """
95 | context = self.data.copy()
96 | context.update(self.render_vars(request))
97 | return render(self.template, request.app, context, request=request)
98 |
99 | @property
100 | def dom_id(self):
101 | """The ``id`` tag of the panel's tab. May be used by CSS and
102 | Javascript to implement custom styles and actions."""
103 | return "pDebug%sPanel" % (self.name.replace(" ", ""))
104 |
105 | async def process_response(self, response): # noqa: B027
106 | """Receives the response generated by the request.
107 |
108 | Override this method to track properties of the response.
109 | """
110 |
111 | def wrap_handler(self, handler, context_switcher):
112 | """May be overridden to monitor the entire lifecycle of the request.
113 |
114 | A handler receives a request and returns a response. An example
115 | implementation may be:
116 |
117 | .. code-block:: python
118 |
119 | def wrap_handler(self, handler, context_switcher):
120 | async def wrapper(request):
121 | start_time = time.time()
122 | response = await handler(request)
123 | end_time = time.time()
124 | self.data['duration'] = end_time - start_time
125 | return response
126 | return wrapper
127 |
128 | context_switcher can be used for context switch tracking, you can
129 | add your callback right before context switch in or out.
130 | """
131 | return handler
132 |
133 | def render_vars(self, request):
134 | """Invoked by the default implementation of :meth:`.render_content`
135 | and should return a ``dict`` of values to use when rendering the
136 | panel's HTML content. This value is usually injected into templates
137 | as the rendering context.
138 |
139 | The ``request`` here is the active request in the toolbar. Not the
140 | original request that this panel represents.
141 | """
142 | return {}
143 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/headers.py:
--------------------------------------------------------------------------------
1 | from .base import DebugPanel
2 |
3 | __all__ = ["HeaderDebugPanel"]
4 |
5 |
6 | class HeaderDebugPanel(DebugPanel):
7 | """
8 | A panel to display HTTP request and response headers.
9 | """
10 |
11 | name = "Header"
12 | has_content = True
13 | template = "headers.jinja2"
14 | title = "HTTP Headers"
15 | nav_title = title
16 |
17 | def __init__(self, request):
18 | super().__init__(request)
19 | self._request_headers = [(k, v) for k, v in sorted(request.headers.items())]
20 |
21 | async def process_response(self, response):
22 | response_headers = [(k, v) for k, v in sorted(response.headers.items())]
23 | self.data = {
24 | "request_headers": self._request_headers,
25 | "response_headers": response_headers,
26 | }
27 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/logger.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import logging
3 | from collections import deque
4 |
5 | from .base import DebugPanel
6 | from ..utils import format_fname
7 |
8 |
9 | class RequestTrackingHandler(logging.Handler):
10 | def __init__(self, *args, **kwargs):
11 | super().__init__(*args, **kwargs)
12 | self._records = deque(maxlen=1000)
13 |
14 | @property
15 | def records(self):
16 | return self._records
17 |
18 | def emit(self, record):
19 | self._records.append(record)
20 |
21 |
22 | class LoggingPanel(DebugPanel):
23 | name = "logging"
24 | template = "logger.jinja2"
25 | title = "Log Messages"
26 | nav_title = "Logging"
27 |
28 | def __init__(self, request):
29 | super().__init__(request)
30 | self._log_handler = RequestTrackingHandler()
31 |
32 | def _install_handler(self):
33 | logging.root.addHandler(self._log_handler)
34 |
35 | def _uninstall_handler(self):
36 | logging.root.removeHandler(self._log_handler)
37 |
38 | def wrap_handler(self, handler, context_switcher):
39 | context_switcher.add_context_in(self._install_handler)
40 | context_switcher.add_context_out(self._uninstall_handler)
41 | return handler
42 |
43 | async def process_response(self, response):
44 | records = []
45 | for record in self._log_handler.records:
46 | records.append(
47 | {
48 | "message": record.getMessage(),
49 | "time": datetime.datetime.fromtimestamp(record.created),
50 | "level": record.levelname,
51 | "file": format_fname(record.pathname),
52 | "file_long": record.pathname,
53 | "line": record.lineno,
54 | }
55 | )
56 | self.data = {"records": records}
57 |
58 | @property
59 | def has_content(self):
60 | if self.data.get("records"):
61 | return True
62 | return False
63 |
64 | @property
65 | def nav_subtitle(self):
66 | if self.data:
67 | return "%d" % len(self.data.get("records"))
68 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/middlewares.py:
--------------------------------------------------------------------------------
1 | from .base import DebugPanel
2 | from ..utils import STATIC_ROUTE_NAME
3 |
4 | __all__ = ["MiddlewaresDebugPanel"]
5 |
6 |
7 | class MiddlewaresDebugPanel(DebugPanel):
8 | """
9 | A panel to display the middlewares used by your aiohttp application.
10 | """
11 |
12 | name = "Middlewares"
13 | has_content = True
14 | template = "middlewares.jinja2"
15 | title = "Middlewares"
16 | nav_title = title
17 |
18 | def __init__(self, request):
19 | super().__init__(request)
20 | if not request.app.middlewares:
21 | self.has_content = False
22 | self.is_active = False
23 | else:
24 | self.populate(request)
25 |
26 | def populate(self, request):
27 | middleware_names = []
28 | for m in request.app.middlewares:
29 | if hasattr(m, "__name__"):
30 | # name for regular functions
31 | middleware_names.append(m.__name__)
32 | else:
33 | middleware_names.append(m.__repr__())
34 | self.data = {"middlewares": middleware_names}
35 |
36 | def render_vars(self, request):
37 | static_path = self._request.app.router[STATIC_ROUTE_NAME].canonical
38 | return {"static_path": static_path}
39 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/performance.py:
--------------------------------------------------------------------------------
1 | import cProfile as profile
2 | import pstats
3 | import time
4 |
5 | try:
6 | import resource
7 | except ImportError: # Fails on Windows
8 | resource = None # type: ignore[assignment]
9 |
10 | from .base import DebugPanel
11 | from ..utils import format_fname
12 |
13 | __all__ = ["PerformanceDebugPanel"]
14 |
15 |
16 | class PerformanceDebugPanel(DebugPanel):
17 | """
18 | Panel that looks at the performance of a request.
19 |
20 | It will display the time a request took and, optionally, the
21 | cProfile output.
22 | """
23 |
24 | name = "Performance"
25 | user_activate = True
26 | stats = None
27 | function_calls = None
28 | has_content = True
29 | has_resource = bool(resource)
30 | template = "performance.jinja2"
31 | title = "Performance"
32 | nav_title = title
33 |
34 | def __init__(self, request):
35 | super().__init__(request)
36 | self.profiler = profile.Profile()
37 |
38 | def _wrap_timer_handler(self, handler):
39 | if self.has_resource:
40 |
41 | async def resource_timer_handler(request):
42 | _start_time = time.time()
43 | self._start_rusage = resource.getrusage(resource.RUSAGE_SELF)
44 | try:
45 | result = await handler(request)
46 | except BaseException:
47 | raise
48 | finally:
49 | self._end_rusage = resource.getrusage(resource.RUSAGE_SELF)
50 | self.total_time = (time.time() - _start_time) * 1000
51 |
52 | return result
53 |
54 | return resource_timer_handler
55 |
56 | async def noresource_timer_handler(request):
57 | _start_time = time.time()
58 | try:
59 | result = await handler(request)
60 | except BaseException:
61 | raise
62 | finally:
63 | self.total_time = (time.time() - _start_time) * 1000
64 | return result
65 |
66 | return noresource_timer_handler
67 |
68 | def _wrap_profile_handler(self, handler):
69 | if not self.is_active:
70 | return handler
71 |
72 | async def profile_handler(request):
73 | try:
74 | self.profiler.enable()
75 | try:
76 | result = await handler(request)
77 | finally:
78 | self.profiler.disable()
79 | except BaseException:
80 | raise
81 | finally:
82 | stats = pstats.Stats(self.profiler)
83 | function_calls = []
84 | flist = stats.sort_stats("cumulative").fcn_list
85 | for func in flist:
86 | current = {}
87 | info = stats.stats[func]
88 |
89 | # Number of calls
90 | if info[0] != info[1]:
91 | current["ncalls"] = "%d/%d" % (info[1], info[0])
92 | else:
93 | current["ncalls"] = info[1]
94 |
95 | # Total time
96 | current["tottime"] = info[2] * 1000
97 |
98 | # Quotient of total time divided by number of calls
99 | if info[1]:
100 | current["percall"] = info[2] * 1000 / info[1]
101 | else:
102 | current["percall"] = 0
103 |
104 | # Cumulative time
105 | current["cumtime"] = info[3] * 1000
106 |
107 | # Quotient of the cumulative time divided by the number
108 | # of primitive calls.
109 | if info[0]:
110 | current["percall_cum"] = info[3] * 1000 / info[0]
111 | else:
112 | current["percall_cum"] = 0
113 |
114 | # Filename
115 | filename = pstats.func_std_string(func)
116 | current["filename_long"] = filename
117 | current["filename"] = format_fname(filename)
118 | function_calls.append(current)
119 |
120 | self.stats = stats
121 | self.function_calls = function_calls
122 |
123 | return result
124 |
125 | return profile_handler
126 |
127 | def wrap_handler(self, handler, context_switcher):
128 | handler = self._wrap_profile_handler(handler)
129 | handler = self._wrap_timer_handler(handler)
130 | return handler
131 |
132 | @property
133 | def nav_subtitle(self):
134 | return "%0.2fms" % (self.total_time)
135 |
136 | def _elapsed_ru(self, name):
137 | return getattr(self._end_rusage, name) - getattr(self._start_rusage, name)
138 |
139 | async def process_response(self, response):
140 | vars = {"timing_rows": None, "stats": None, "function_calls": []}
141 | if self.has_resource:
142 | utime = 1000 * self._elapsed_ru("ru_utime")
143 | stime = 1000 * self._elapsed_ru("ru_stime")
144 | vcsw = self._elapsed_ru("ru_nvcsw")
145 | ivcsw = self._elapsed_ru("ru_nivcsw")
146 | # minflt = self._elapsed_ru('ru_minflt')
147 | # majflt = self._elapsed_ru('ru_majflt')
148 |
149 | # these are documented as not meaningful under Linux. If you're
150 | # running BSD # feel free to enable them, and add any others that
151 | # I hadn't gotten to before I noticed that I was getting nothing
152 | # but zeroes and that the docs agreed. :-(
153 | #
154 | # blkin = self._elapsed_ru('ru_inblock')
155 | # blkout = self._elapsed_ru('ru_oublock')
156 | # swap = self._elapsed_ru('ru_nswap')
157 | # rss = self._end_rusage.ru_maxrss
158 | # srss = self._end_rusage.ru_ixrss
159 | # urss = self._end_rusage.ru_idrss
160 | # usrss = self._end_rusage.ru_isrss
161 |
162 | # TODO l10n on values
163 | rows = (
164 | ("User CPU time", "%0.3f msec" % utime),
165 | ("System CPU time", "%0.3f msec" % stime),
166 | ("Total CPU time", "%0.3f msec" % (utime + stime)),
167 | ("Elapsed time", "%0.3f msec" % self.total_time),
168 | ("Context switches", "%d voluntary, %d involuntary" % (vcsw, ivcsw)),
169 | # (_('Memory use'), '%d max RSS, %d shared, %d unshared' % (
170 | # rss, srss, urss + usrss)),
171 | # (_('Page faults'), '%d no i/o, %d requiring i/o' % (
172 | # minflt, majflt)),
173 | # (_('Disk operations'), '%d in, %d out, %d swapout' % (
174 | # blkin, blkout, swap)),
175 | )
176 | vars["timing_rows"] = rows
177 | if self.is_active:
178 | vars["stats"] = self.stats
179 | vars["function_calls"] = self.function_calls
180 | self.data = vars
181 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/request_vars.py:
--------------------------------------------------------------------------------
1 | from pprint import saferepr
2 |
3 | from .base import DebugPanel
4 |
5 | __all__ = ["RequestVarsDebugPanel"]
6 |
7 |
8 | class RequestVarsDebugPanel(DebugPanel):
9 | """
10 | A panel to display request variables (POST/GET, session, cookies, and
11 | ad-hoc request attributes).
12 | """
13 |
14 | name = "RequestVars"
15 | has_content = True
16 | template = "request_vars.jinja2"
17 | title = "Request Vars"
18 | nav_title = title
19 |
20 | def __init__(self, request):
21 | super().__init__(request)
22 |
23 | async def process_response(self, response):
24 | self.data = data = {}
25 | request = self.request
26 | post_data = await request.post()
27 | data.update(
28 | {
29 | "get": [(k, request.query.getall(k)) for k in sorted(request.query)],
30 | "post": [(k, saferepr(post_data.getall(k))) for k in sorted(post_data)],
31 | "cookies": [(k, v) for k, v in sorted(request.cookies.items())],
32 | "attrs": [(k, v) for k, v in sorted(request.items())],
33 | }
34 | )
35 |
36 | # TODO: think about aiohttp_security
37 |
38 | # session to separate table
39 | session = request.get("aiohttp_session")
40 | if session and not session.empty:
41 | data.update(
42 | {
43 | "session": [(k, v) for k, v in sorted(session.items())],
44 | }
45 | )
46 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/routes.py:
--------------------------------------------------------------------------------
1 | import inspect
2 |
3 | from .base import DebugPanel
4 |
5 | __all__ = ["RoutesDebugPanel"]
6 |
7 |
8 | class RoutesDebugPanel(DebugPanel):
9 | """
10 | A panel to display the routes used by your aiohttp application.
11 | """
12 |
13 | name = "Routes"
14 | has_content = True
15 | template = "routes.jinja2"
16 | title = "Routes"
17 | nav_title = title
18 |
19 | def __init__(self, request):
20 | super().__init__(request)
21 | self.populate(request)
22 |
23 | def populate(self, request):
24 | info = []
25 | router = request.app.router
26 |
27 | for route in router.routes():
28 | info.append(
29 | {
30 | "name": route.name or "",
31 | "method": route.method,
32 | "info": sorted(route.get_info().items()),
33 | "handler": repr(route.handler),
34 | "source": inspect.getsource(route.handler),
35 | }
36 | )
37 |
38 | self.data = {"routes": info}
39 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/settings.py:
--------------------------------------------------------------------------------
1 | from operator import itemgetter
2 |
3 | from .base import DebugPanel
4 | from ..utils import APP_KEY
5 |
6 | __all__ = ["SettingsDebugPanel"]
7 |
8 |
9 | class SettingsDebugPanel(DebugPanel):
10 | """
11 | A panel to display debug toolbar setting for now.
12 | """
13 |
14 | name = "Settings"
15 | has_content = True
16 | template = "settings.jinja2"
17 | title = "Settings"
18 | nav_title = title
19 |
20 | def __init__(self, request):
21 | super().__init__(request)
22 | # TODO: show application setting here
23 | # always repr this stuff before it's sent to the template to appease
24 | # dumbass stuff like MongoDB's __getattr__ that always returns a
25 | # Collection, which fails when Jinja tries to look up __html__ on it.
26 | settings = request.app[APP_KEY]["settings"]
27 | # filter out non-pyramid prefixed settings to avoid duplication
28 | reprs = [(k, repr(v)) for k, v in settings.items()]
29 | self.data = {"settings": sorted(reprs, key=itemgetter(0))}
30 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/headers.jinja2:
--------------------------------------------------------------------------------
1 | Request Headers
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Key
10 | Value
11 |
12 |
13 |
14 | {% for key, value in request_headers %}
15 |
16 | {{ key }}
17 | {{ value }}
18 |
19 | {% endfor %}
20 |
21 |
22 |
23 | Response Headers
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Key
32 | Value
33 |
34 |
35 |
36 | {% for key, value in response_headers %}
37 |
38 | {{ key }}
39 | {{ value }}
40 |
41 | {% endfor %}
42 |
43 |
44 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/logger.jinja2:
--------------------------------------------------------------------------------
1 | {% if records %}:
2 |
3 |
4 |
5 | Level
6 | Time
7 | Message
8 | Location
9 |
10 |
11 |
12 | {% for record in records %}
13 |
14 | {{ record['level'] }}
15 | {{ record['time'] }}
16 | {{ record['message'] }}
17 | {{record['file']}}:{{record['line']}}
18 |
19 | {% endfor %}
20 |
21 |
22 | {% else %}:
23 | No messages logged.
24 | {% endif %}
25 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/middlewares.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Order (from server to application)
5 | Middleware
6 |
7 |
8 |
9 | {% for middleware in middlewares %}
10 |
11 | {{ i }}
12 | {{ middleware }}
13 |
14 | {% endfor %}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/performance.jinja2:
--------------------------------------------------------------------------------
1 | {% if timing_rows %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Resource
10 | Value
11 |
12 |
13 |
14 | {% for key, value in timing_rows %}
15 |
16 | {{ key }}
17 | {{ value }}
18 |
19 | {% endfor %}
20 |
21 |
22 | {% else %}
23 | Resource statistics have been disabled. This is because the 'resource'
24 | module could not be found. This module is not supported under Windows.
25 | {% endif %}
26 |
27 | Profile
28 | {% if stats %}
29 | Times in milliseconds
30 |
31 |
32 |
33 | Calls
34 | Total
35 | Percall
36 | Cumu
37 | CumuPer
38 | Func
39 |
40 |
41 |
42 | {% for row in function_calls %}
43 |
44 | {{ row['ncalls'] }}
45 | {{ '%.4f' % row['tottime'] }}
46 | {{ '%.4f' % row['percall'] }}
47 | {{ '%.4f' % row['cumtime'] }}
48 | {{ '%.4f' % row['percall_cum'] }}
49 | {{ row['filename']|e }}
50 |
51 | {% endfor %}
52 |
53 |
54 | {% else %}
55 | The profiler is not activated. Activate the checkbox in the toolbar to use it.
56 | {% endif %}
57 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/request_vars.jinja2:
--------------------------------------------------------------------------------
1 | Cookie Variables
2 | {% if cookies %}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Variable
11 | Value
12 |
13 |
14 |
15 | {% for key, value in cookies %}
16 |
17 | {{ key }}
18 | {{ value }}
19 |
20 | {% endfor %}
21 |
22 |
23 | {% else %}
24 | No cookie data
25 | {% endif %}
26 |
27 | Session Variables
28 | {% if session %}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Variable
37 | Value
38 |
39 |
40 |
41 | {% for key, value in session %}
42 |
43 | {{ key }}
44 | {{ value }}
45 |
46 | {% endfor %}
47 |
48 |
49 | {% else %}
50 | No session data
51 | {% endif %}
52 |
53 | GET Variables
54 | {% if get %}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Variable
63 | Value
64 |
65 |
66 |
67 | {% for key, value in get %}
68 |
69 | {{ key }}
70 | {{ ', '.join(value) }}
71 |
72 | {% endfor %}
73 |
74 |
75 | {% else %}
76 | No GET data
77 | {% endif %}
78 |
79 | POST Variables
80 | {% if post %}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Variable
89 | Value
90 |
91 |
92 |
93 | {% for key, value in post %}
94 |
95 | {{ key }}
96 | {{ value }}
97 |
98 | {% endfor %}
99 |
100 |
101 | {% else %}
102 | No POST data
103 | {% endif %}
104 |
105 | Request attributes
106 | {% if attrs %}
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Attribute
115 | Value
116 |
117 |
118 |
119 | {% for key, value in attrs %}
120 |
121 | {{ key }}
122 | {{ value }}
123 |
124 | {% endfor %}
125 |
126 |
127 | {% else %}
128 | No request attributes
129 | {% endif %}
130 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/routes.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Route Name
5 | Method
6 | Route Info
7 | View Callable
8 |
9 |
10 |
11 | {% for route_info in routes %}
12 |
13 | {{ route_info['name'] }}
14 | {{ route_info['method'] }}
15 |
16 |
17 | {% for name, value in route_info['info'] %}
18 | {{ name }}: {{ value }}
19 | {% endfor %}
20 |
21 |
22 |
23 |
24 |
{{ route_info['handler']|e }}
25 |
26 |
31 | Source
32 |
33 |
34 |
35 |
36 |
{{ route_info['source'] }}
37 |
38 |
39 |
40 | {% endfor %}
41 |
42 |
43 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/settings.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Key
5 | Value
6 |
7 |
8 |
9 | {% for key, value in settings %}
10 |
11 | {{ key }}
12 | {{ value|e }}
13 |
14 | {% endfor %}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/sqlalchemy_explain.jinja2:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
SQL Explained
9 |
10 |
39 |
40 |
41 |
44 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/sqlalchemy_select.jinja2:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
SQL Select
9 |
10 |
43 |
44 |
45 |
48 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/traceback.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ exception_type }}
4 |
7 |
Traceback (most recent call last)
8 | {{ summary }}
9 |
10 |
11 |
12 | This is the Copy/Paste friendly version of the traceback.
13 |
14 |
16 |
17 |
18 |
19 |
20 | Warning: this feature should not be enabled on production
21 | systems.
22 |
23 |
24 | {% if evalex %}
25 |
26 |
27 | Hover over any gray area in the traceback and click on the
28 |
29 |
30 |
31 | button on the right hand side of that gray area to show an interactive
32 | console for the associated frame. Type arbitrary Python into the
33 | console; it will be evaluated in the context of the associated frame. In
34 | the interactive console there are helpers available for introspection:
35 |
36 |
37 | dump()
shows all variables in the frame
38 | dump(obj)
dumps all that's known about the object
39 |
40 |
41 | {% endif %}
42 |
43 |
44 | Hover over any gray area in the traceback and click on
45 |
46 |
47 |
48 | on the right hand side of that gray area to show the source of the file
49 | associated with the frame.
50 |
51 |
52 |
53 | Click on the traceback header to switch back and forth between the
54 | rendered version of the traceback and a plaintext copy-paste-friendly
55 | version of the traceback.
56 |
57 |
58 |
59 | URL to recover this traceback page: {{ url }}
60 |
61 |
62 |
63 |
67 |
68 |
73 |
74 |
82 |
84 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/templates/versions.jinja2:
--------------------------------------------------------------------------------
1 | Platform
2 | {{ platform }}
3 | aiohttp
4 | {{ aiohttp_version }}
5 |
6 | Packages
7 |
8 |
9 |
10 |
11 | Package Name
12 | Version
13 | Dependencies
14 |
15 |
16 |
17 | {% for package in packages %}
18 |
19 | {{ package['name'] }}
20 | {{ package['version'] }}
21 |
22 | {{ package['dependencies']|join(', ') }}
23 |
24 |
25 | {% endfor %}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/traceback.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from .base import DebugPanel
4 | from ..tbtools.tbtools import Traceback
5 | from ..utils import APP_KEY, ROOT_ROUTE_NAME, STATIC_ROUTE_NAME, escape
6 |
7 | __all__ = ["TracebackPanel"]
8 |
9 |
10 | class TracebackPanel(DebugPanel):
11 | name = "Traceback"
12 | template = "traceback.jinja2"
13 | title = "Traceback"
14 | nav_title = title
15 |
16 | def __init__(self, request):
17 | super().__init__(request)
18 | self.exc_history = request.app[APP_KEY]["exc_history"]
19 |
20 | @property
21 | def has_content(self):
22 | if self._request.get("pdbt_tb"):
23 | return True
24 | return False
25 |
26 | async def process_response(self, response):
27 | if not self.has_content:
28 | return
29 | traceback = self._request["pdbt_tb"]
30 |
31 | exc = escape(traceback.exception)
32 | summary = Traceback.render_summary(
33 | traceback, self._request.app, include_title=False
34 | )
35 | token = self.request.app[APP_KEY]["pdtb_token"]
36 | url = "" # self.request.route_url(EXC_ROUTE_NAME, _query=qs)
37 | evalex = self.exc_history.eval_exc
38 |
39 | self.data = {
40 | "evalex": evalex and "true" or "false",
41 | "console": "false",
42 | "lodgeit_url": None,
43 | "title": exc,
44 | "exception": exc,
45 | "exception_type": escape(traceback.exception_type),
46 | "summary": summary,
47 | "plaintext": traceback.plaintext,
48 | "plaintext_cs": re.sub("-{2,}", "-", traceback.plaintext),
49 | "traceback_id": traceback.id,
50 | "token": token,
51 | "url": url,
52 | }
53 |
54 | def render_content(self, request):
55 | return super().render_content(request)
56 |
57 | def render_vars(self, request):
58 | static_path = self._request.app.router[STATIC_ROUTE_NAME].canonical
59 | root_path = self.request.app.router[ROOT_ROUTE_NAME].url_for()
60 | return {"static_path": static_path, "root_path": root_path}
61 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/panels/versions.py:
--------------------------------------------------------------------------------
1 | import platform
2 | import sys
3 | from importlib.metadata import Distribution, version
4 | from operator import itemgetter
5 | from typing import ClassVar, Dict, List, Optional, TypedDict
6 |
7 | from .base import DebugPanel
8 |
9 |
10 | __all__ = ("VersionDebugPanel",)
11 | aiohttp_version = version("aiohttp")
12 |
13 |
14 | class _Package(TypedDict):
15 | version: str
16 | lowername: str
17 | name: str
18 | dependencies: List[str]
19 | url: Optional[str]
20 |
21 |
22 | class VersionDebugPanel(DebugPanel):
23 | """
24 | Panel that displays the Python version, the aiohttp version, and the
25 | versions of other software on your PYTHONPATH.
26 | """
27 |
28 | name = "Version"
29 | has_content = True
30 | template = "versions.jinja2"
31 | title = "Versions"
32 | nav_title = title
33 | packages: ClassVar[Optional[List[Dict[str, str]]]] = None
34 |
35 | def __init__(self, request):
36 | super().__init__(request)
37 | self.data = {
38 | "platform": self.get_platform(),
39 | "packages": self.get_packages(),
40 | "aiohttp_version": aiohttp_version,
41 | }
42 |
43 | @classmethod
44 | def get_packages(cls) -> List[Dict[str, str]]:
45 | if VersionDebugPanel.packages:
46 | return VersionDebugPanel.packages
47 |
48 | packages: List[_Package] = []
49 | for distribution in Distribution.discover():
50 | name = distribution.metadata["Name"]
51 | dependencies = [d for d in distribution.requires or ()]
52 | url = distribution.metadata.get("Home-page")
53 |
54 | packages.append(
55 | {
56 | "version": distribution.version,
57 | "lowername": name.lower(),
58 | "name": name,
59 | "dependencies": dependencies,
60 | "url": url,
61 | }
62 | )
63 |
64 | VersionDebugPanel.packages = sorted(packages, key=itemgetter("lowername")) # type: ignore[arg-type]
65 | return VersionDebugPanel.packages
66 |
67 | def _get_platform_name(self):
68 | return platform.platform()
69 |
70 | def get_platform(self):
71 | return f"Python {sys.version} on {self._get_platform_name()}"
72 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/py.typed
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/css/dashboard.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Base structure
3 | */
4 |
5 | /* Move down content because we have a fixed navbar that is 50px tall */
6 | body {
7 | padding-top: 70px;
8 | }
9 |
10 | a {
11 | color: #e28d29;
12 | }
13 |
14 | a:hover {
15 | color: #c56b00;
16 | }
17 |
18 | .navbar-collapse {
19 | border-color: #e28d29;
20 | }
21 |
22 | /*
23 | .navbar-inner {
24 | background-color: #dd7c0d;
25 | background-image: linear-gradient(left, #dd7c0d, #dbc1a4;
26 | background-image: -moz-linear-gradient(left, #dd7c0d, #dbc1a4;
27 | background-image: -ms-linear-gradient(left, #dd7c0d, #dbc1a4;
28 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#dd7c0d), to(#dbc1a4);
29 | background-image: -webkit-linear-gradient(left, #dd7c0d, #dbc1a4;
30 | background-image: -o-linear-gradient(left, #dd7c0d, #dbc1a4;
31 | }
32 | */
33 |
34 | .navbar-inverse {
35 | background: #dd7c0d; /* Old browsers */
36 | background: -moz-linear-gradient(left, #dd7c0d 0%, #c99662 100%); /* FF3.6+ */
37 | background: -webkit-gradient(linear, left top, right top, color-stop(0%,#dd7c0d), color-stop(100%,#c99662)); /* Chrome,Safari4+ */
38 | background: -webkit-linear-gradient(left, #dd7c0d 0%,#c99662 100%); /* Chrome10+,Safari5.1+ */
39 | background: -o-linear-gradient(left, #dd7c0d 0%,#c99662 100%); /* Opera 11.10+ */
40 | background: -ms-linear-gradient(left, #dd7c0d 0%,#c99662 100%); /* IE10+ */
41 | background: linear-gradient(to right, #dd7c0d 0%,#c99662 100%); /* W3C */
42 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#dd7c0d', endColorstr='#c99662',GradientType=1 ); /* IE6-9 */
43 | }
44 |
45 | .navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav > li > a {
46 | color: #fff;
47 | }
48 |
49 | .navbar-inverse .navbar-nav > .active > a,
50 | .navbar-inverse .navbar-nav > .active > a:hover,
51 | .navbar-inverse .navbar-nav > .active > a:focus,
52 |
53 | .navbar-inverse .navbar-nav > .open > a,
54 | .navbar-inverse .navbar-nav > .open > a:hover,
55 | .navbar-inverse .navbar-nav > .open > a:focus,
56 |
57 | .navbar-inverse .navbar-nav > li.active > a,
58 | .navbar-inverse .navbar-nav > li.active:hover > a {
59 | background-color: #c56b00;
60 | }
61 |
62 | .navbar-brand > img {
63 | position: absolute;
64 | top: 10px;
65 | left: 10px;
66 | width: 35px;
67 | height: 35px;
68 | }
69 |
70 | .navbar-brand {
71 | padding-left: 50px;
72 | }
73 |
74 | /*
75 | * Global add-ons
76 | */
77 |
78 | .sub-header {
79 | padding-bottom: 10px;
80 | border-bottom: 1px solid #eee;
81 | }
82 |
83 |
84 | /*
85 | * Sidebar
86 | */
87 |
88 | /* Hide for mobile, show later */
89 | .sidebar {
90 | display: none;
91 | }
92 | @media (min-width: 768px) {
93 | .navbar-nav {
94 | float: right!important;
95 | }
96 | .sidebar {
97 | position: fixed;
98 | top: 0;
99 | left: 0;
100 | bottom: 0;
101 | z-index: 1000;
102 | display: block;
103 | padding: 70px 20px 20px;
104 | background-color: #eee;
105 | border-right: 1px solid #eee;
106 | overflow-y: scroll;
107 | }
108 | }
109 |
110 | /* Sidebar navigation */
111 | .nav-sidebar {
112 | margin-left: -20px;
113 | margin-right: -21px; /* 20px padding + 1px border */
114 | margin-bottom: 20px;
115 | }
116 | .nav-sidebar > li > h4, .nav-sidebar > li > a {
117 | padding-left: 20px;
118 | padding-right: 20px;
119 | width: 100%;
120 | }
121 | .nav-sidebar > li > a {
122 | color: #555;
123 | }
124 | .nav-sidebar > .active > a {
125 | color: #fff;
126 | background: #999;
127 | }
128 | .nav-sidebar > .active:hover > a {
129 | background: #999;
130 | }
131 |
132 | /*
133 | * Main content
134 | */
135 |
136 | .main {
137 | padding: 0px 20px 20px 20px;
138 | }
139 | @media (min-width: 768px) {
140 | .main {
141 | padding-left: 40px;
142 | padding-right: 40px;
143 | }
144 | }
145 | .main .page-header {
146 | margin-top: 0;
147 | }
148 |
149 |
150 | /*
151 | * Placeholder dashboard ideas
152 | */
153 |
154 | .placeholders {
155 | margin-bottom: 30px;
156 | text-align: center;
157 | }
158 | .placeholders h4 {
159 | margin-bottom: 0;
160 | }
161 | .placeholder {
162 | margin-bottom: 20px;
163 | }
164 | .placeholder img {
165 | border-radius: 50%;
166 | }
167 | ul#requests li a {
168 | overflow: hidden;
169 | white-space: nowrap;
170 | text-overflow: ellipsis;
171 | display: inline-block;
172 | max-width: 300px;
173 | }
174 | /* override twitter bootstrap's tooltip styles */
175 | .tooltip, .tooltip-inner {
176 | max-width: 100%;
177 | }
178 | .tooltip {
179 | word-wrap: break-word;
180 | word-break: break-all;
181 | }
182 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/css/debugger.css:
--------------------------------------------------------------------------------
1 | /* TODO: cleanup unused styles */
2 | input { background-color: #fff; margin: 0; text-align: left;
3 | outline: none !important; }
4 | pre, code, table.source,
5 | textarea { font-family: Consolas, "andale mono", "lucida console", monospace; font-size: 14px; }
6 |
7 | div.debugger { text-align: left; padding: 12px; margin: auto;
8 | background-color: white; }
9 | h1 { margin: 0 0 0.3em 0; }
10 | div.detail p { margin: 0 0 8px 13px; font-size: 14px; }
11 | div.detail pre.errormsg { word-wrap: break-word; }
12 | div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
13 | div.footer { font-size: 13px; text-align: right; margin: 30px 0;
14 | color: #999999; }
15 |
16 | h2 { margin: 1.3em 0 0.0 0; padding: 9px;
17 | background: #e28d29; color: #000000; }
18 | h2.traceback, h3.traceback {
19 | color: #ffffff;
20 | border-top-right-radius: 4px;
21 | border-top-left-radius: 4px;
22 | }
23 | h2 small, h3 small { font-style: normal; color: #ffffff; font-weight: normal; }
24 |
25 | div.traceback {
26 | border-bottom-left-radius: 4px;
27 | border-bottom-right-radius: 1px;
28 | }
29 | div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; }
30 | div.plain p { margin: 0; }
31 | div.plain textarea,
32 | div.plain pre { margin: 10px 0 0 0; padding: 4px;
33 | background-color: #eeeeee; border: 1px solid #dddddd; }
34 | div.plain textarea { width: 99%; height: 300px; }
35 | div.traceback h3 { margin: 0; }
36 | div.traceback ul { list-style: none; margin: 0; padding: 0; }
37 | div.traceback h4 { font-size: 13px; font-weight: normal; margin: 5px 0; }
38 | div.traceback pre { margin: 0; padding: 5px 0 3px 5px;
39 | background-color: #f5f5f5;}
40 | div.traceback pre,
41 | div.box table.source { white-space: pre-wrap; /* css-3 should we be so lucky... */
42 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
43 | white-space: -pre-wrap; /* Opera 4-6 ?? */
44 | white-space: -o-pre-wrap; /* Opera 7 ?? */
45 | word-wrap: break-word; /* Internet Explorer 5.5+ */
46 | _white-space: pre; /* IE only hack to re-specify in
47 | addition to word-wrap */ }
48 | div.box h2 { color: #ffffff; }
49 | div.traceback pre:hover { background-color: #eeeeee; color: black; cursor: pointer; }
50 | div.traceback blockquote { margin: 1em 0 0 0; padding: 0; }
51 | div.traceback pre:hover img { display: block; }
52 | div.traceback cite.filename { font-style: normal; color: #666666; }
53 |
54 | pre.console { border: 1px solid #ccc; background: white!important;
55 | color: black; padding: 5px!important;
56 | margin: 3px 0 0 0!important; cursor: default!important;
57 | overflow: auto; }
58 | pre.console form { color: #555; }
59 | pre.console input { background-color: transparent; color: #555;
60 | width: 90%; font-family: Consolas, "andale mono", "lucida console", monospace; font-size: 14px;
61 | border: none!important; }
62 |
63 | span.string { color: #30799B; }
64 | span.number { color: #9C1A1C; }
65 | span.help { color: #3A7734; }
66 | span.object { color: #485F6E; }
67 |
68 | pre.console div.traceback,
69 | pre.console div.box { margin: 5px; white-space: normal;
70 | border: 1px solid #dddddd; padding: 5px;
71 | font-family: sans-serif; }
72 | pre.console div.box h3,
73 | pre.console div.traceback h3 { margin: -5px -5px 5px -5px; padding: 5px;
74 | background: #e28d29; color: #ffffff; }
75 |
76 | pre.console div.traceback pre:hover { cursor: default; background: #eeeeee; }
77 | pre.console div.traceback pre.syntaxerror { background: #eeeeee; border: none;
78 | margin: 0px 0px 0px 0px;
79 | padding: 10px; border-top: 1px solid #dddddd; }
80 | pre.console div.noframe-traceback pre.syntaxerror { margin-top: 0px; border: none; }
81 |
82 | pre.console div.box pre.repr { padding: 0; margin: 0; background-color: white; border: none; }
83 | pre.console div.box table { margin-top: 6px; }
84 | pre.console div.box pre { border: none; }
85 | pre.console div.box pre.help { background-color: white; }
86 | pre.console div.box pre.help:hover { cursor: default; }
87 | pre.console table tr { vertical-align: top; }
88 | pre.console table th { white-space: nowrap; }
89 | div.console { border: 1px solid #dddddd; padding: 4px; background-color: #f5f5f5; }
90 |
91 | div.box table.source { border-collapse: collapse; width: 100%; background: #f5f5f5;
92 | font-size: 12px;
93 | }
94 | div.box table.source td { border-top: 1px solid #dddddd; padding: 4px 0 4px 10px; }
95 | div.box table.source td.lineno { color: #999; padding-right: 10px; width: 1px; }
96 | div.box table.source tr.in-frame { background-color: white; }
97 | div.box table.source tr.current { background-color: #dddddd; color: #e5762b; }
98 | div.sourceview { overflow: auto; border: 1px solid #ccc; }
99 |
100 | div.traceback .btn { margin: 1px 4px 1px 0; }
101 | div.traceback span, div.explanation span { margin: 2px; }
102 |
103 | div.sourceview span.current {
104 | display: inline-block;
105 | width: 100%;
106 | background-color: #ddd;
107 | }
108 |
109 | td.value {
110 | word-break: break-all;
111 | word-wrap: break-word;
112 | }
113 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/css/highlightjs_default.min.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#f0f0f0;-webkit-text-size-adjust:none}.hljs,.hljs-subst,.hljs-tag .hljs-title,.nginx .hljs-title{color:black}.hljs-string,.hljs-title,.hljs-constant,.hljs-parent,.hljs-tag .hljs-value,.hljs-rule .hljs-value,.hljs-preprocessor,.hljs-pragma,.hljs-name,.haml .hljs-symbol,.ruby .hljs-symbol,.ruby .hljs-symbol .hljs-string,.hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.hljs-addition,.hljs-flow,.hljs-stream,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.tex .hljs-special,.erlang_repl .hljs-function_or_atom,.asciidoc .hljs-header,.markdown .hljs-header,.coffeescript .hljs-attribute,.tp .hljs-variable{color:#800}.smartquote,.hljs-comment,.hljs-annotation,.diff .hljs-header,.hljs-chunk,.asciidoc .hljs-blockquote,.markdown .hljs-blockquote{color:#888}.hljs-number,.hljs-date,.hljs-regexp,.hljs-literal,.hljs-hexcolor,.smalltalk .hljs-symbol,.smalltalk .hljs-char,.go .hljs-constant,.hljs-change,.lasso .hljs-variable,.makefile .hljs-variable,.asciidoc .hljs-bullet,.markdown .hljs-bullet,.asciidoc .hljs-link_url,.markdown .hljs-link_url{color:#080}.hljs-label,.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-important,.hljs-pseudo,.hljs-pi,.haml .hljs-bullet,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-formula,.erlang_repl .hljs-reserved,.hljs-prompt,.asciidoc .hljs-link_label,.markdown .hljs-link_label,.vhdl .hljs-attribute,.clojure .hljs-attribute,.asciidoc .hljs-attribute,.lasso .hljs-attribute,.coffeescript .hljs-property,.hljs-phony{color:#88f}.hljs-keyword,.hljs-id,.hljs-title,.hljs-built_in,.css .hljs-tag,.hljs-doctag,.smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.hljs-type,.hljs-typename,.tex .hljs-command,.asciidoc .hljs-strong,.markdown .hljs-strong,.hljs-request,.hljs-status,.tp .hljs-data,.tp .hljs-io{font-weight:bold}.asciidoc .hljs-emphasis,.markdown .hljs-emphasis,.tp .hljs-units{font-style:italic}.nginx .hljs-built_in{font-weight:normal}.coffeescript .javascript,.javascript .xml,.lasso .markup,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:0.5}
2 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/css/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+git+python+sql&plugins=line-highlight+line-numbers */
2 | /**
3 | * prism.js default theme for JavaScript, CSS and HTML
4 | * Based on dabblet (http://dabblet.com)
5 | * @author Lea Verou
6 | */
7 |
8 | code[class*="language-"],
9 | pre[class*="language-"] {
10 | color: black;
11 | text-shadow: 0 1px white;
12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
13 | direction: ltr;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 | line-height: 1.5;
20 |
21 | -moz-tab-size: 4;
22 | -o-tab-size: 4;
23 | tab-size: 4;
24 |
25 | -webkit-hyphens: none;
26 | -moz-hyphens: none;
27 | -ms-hyphens: none;
28 | hyphens: none;
29 | }
30 |
31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
33 | text-shadow: none;
34 | background: #b3d4fc;
35 | }
36 |
37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
38 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
39 | text-shadow: none;
40 | background: #b3d4fc;
41 | }
42 |
43 | @media print {
44 | code[class*="language-"],
45 | pre[class*="language-"] {
46 | text-shadow: none;
47 | }
48 | }
49 |
50 | /* Code blocks */
51 | pre[class*="language-"] {
52 | padding: 1em;
53 | margin: .5em 0;
54 | overflow: auto;
55 | }
56 |
57 | :not(pre) > code[class*="language-"],
58 | pre[class*="language-"] {
59 | background: #f5f2f0;
60 | }
61 |
62 | /* Inline code */
63 | :not(pre) > code[class*="language-"] {
64 | padding: .1em;
65 | border-radius: .3em;
66 | white-space: normal;
67 | }
68 |
69 | .token.comment,
70 | .token.prolog,
71 | .token.doctype,
72 | .token.cdata {
73 | color: slategray;
74 | }
75 |
76 | .token.punctuation {
77 | color: #999;
78 | }
79 |
80 | .namespace {
81 | opacity: .7;
82 | }
83 |
84 | .token.property,
85 | .token.tag,
86 | .token.boolean,
87 | .token.number,
88 | .token.constant,
89 | .token.symbol,
90 | .token.deleted {
91 | color: #905;
92 | }
93 |
94 | .token.selector,
95 | .token.attr-name,
96 | .token.string,
97 | .token.char,
98 | .token.builtin,
99 | .token.inserted {
100 | color: #690;
101 | }
102 |
103 | .token.operator,
104 | .token.entity,
105 | .token.url,
106 | .language-css .token.string,
107 | .style .token.string {
108 | color: #a67f59;
109 | background: hsla(0, 0%, 100%, .5);
110 | }
111 |
112 | .token.atrule,
113 | .token.attr-value,
114 | .token.keyword {
115 | color: #07a;
116 | }
117 |
118 | .token.function {
119 | color: #DD4A68;
120 | }
121 |
122 | .token.regex,
123 | .token.important,
124 | .token.variable {
125 | color: #e90;
126 | }
127 |
128 | .token.important,
129 | .token.bold {
130 | font-weight: bold;
131 | }
132 | .token.italic {
133 | font-style: italic;
134 | }
135 |
136 | .token.entity {
137 | cursor: help;
138 | }
139 |
140 | pre[data-line] {
141 | position: relative;
142 | padding: 1em 0 1em 3em;
143 | }
144 |
145 | .line-highlight {
146 | position: absolute;
147 | left: 0;
148 | right: 0;
149 | padding: inherit 0;
150 | margin-top: 1em; /* Same as .prism’s padding-top */
151 |
152 | background: hsla(24, 20%, 50%,.08);
153 | background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
154 | background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
155 | background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
156 | background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
157 |
158 | pointer-events: none;
159 |
160 | line-height: inherit;
161 | white-space: pre;
162 | }
163 |
164 | .line-highlight:before,
165 | .line-highlight[data-end]:after {
166 | content: attr(data-start);
167 | position: absolute;
168 | top: .4em;
169 | left: .6em;
170 | min-width: 1em;
171 | padding: 0 .5em;
172 | background-color: hsla(24, 20%, 50%,.4);
173 | color: hsl(24, 20%, 95%);
174 | font: bold 65%/1.5 sans-serif;
175 | text-align: center;
176 | vertical-align: .3em;
177 | border-radius: 999px;
178 | text-shadow: none;
179 | box-shadow: 0 1px white;
180 | }
181 |
182 | .line-highlight[data-end]:after {
183 | content: attr(data-end);
184 | top: auto;
185 | bottom: .4em;
186 | }
187 | pre.line-numbers {
188 | position: relative;
189 | padding-left: 3.8em;
190 | counter-reset: linenumber;
191 | }
192 |
193 | pre.line-numbers > code {
194 | position: relative;
195 | }
196 |
197 | .line-numbers .line-numbers-rows {
198 | position: absolute;
199 | pointer-events: none;
200 | top: 0;
201 | font-size: 100%;
202 | left: -3.8em;
203 | width: 3em; /* works for line-numbers below 1000 lines */
204 | letter-spacing: -1px;
205 | border-right: 1px solid #999;
206 |
207 | -webkit-user-select: none;
208 | -moz-user-select: none;
209 | -ms-user-select: none;
210 | user-select: none;
211 |
212 | }
213 |
214 | .line-numbers-rows > span {
215 | pointer-events: none;
216 | display: block;
217 | counter-increment: linenumber;
218 | }
219 |
220 | .line-numbers-rows > span:before {
221 | content: counter(linenumber);
222 | color: #999;
223 | display: block;
224 | padding-right: 0.8em;
225 | text-align: right;
226 | }
227 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/css/toolbar.css:
--------------------------------------------------------------------------------
1 | ._200 {
2 | background-color: #468847;
3 | }
4 | ._500 {
5 | background-color: #b94a48;
6 | }
7 | ._404{
8 | background-color: #3a87ad;
9 | }
10 |
11 | .switch {
12 | font-size: 10px;
13 | display: block;
14 | color: white;
15 | height: 16px;
16 | width: 16px;
17 | cursor: pointer;
18 | }
19 |
20 | .switch.active {
21 | background-image: url(../img/tick.png);
22 | }
23 |
24 | .switch.inactive{
25 | background-image: url(../img/tick-red.png);
26 | }
27 |
28 | li.nav-label {
29 | position: relative;
30 | display: block;
31 | padding: 10px 15px 10px 0px;
32 | }
33 |
34 | li.nav-label strong {
35 | font-size: 150%;
36 | }
37 | a.active {
38 | background: #fff;
39 | }
40 |
41 | #pDebug #pDebugToolbarHandle {
42 | position:fixed;
43 | border:1px solid #e28d29;
44 | top: 25%;
45 | right:0;
46 | z-index:100000000;
47 | }
48 |
49 | @media print {
50 | #pDebug {
51 | display: none;
52 | }
53 | }
54 |
55 | #pDebug a#pShowToolBarButton {
56 | display:block;
57 | height:40px;
58 | width:40px;
59 | border-right:none;
60 | border-bottom:1px solid #fff;
61 | border-top:1px solid #fff;
62 | border-left:1px solid #fff;
63 | color:#fff;
64 | font-size:10px;
65 | font-weight:bold;
66 | text-decoration:none;
67 | text-align:center;
68 | text-indent:-999999px;
69 | background-color:#e28d29;
70 | background-size:40px 40px;
71 | background-image:url(../img/aiohttp.svg);
72 | background-position: left center;
73 | background-repead: no-repeat;
74 | opacity:0.8;
75 | filter:alpha(opacity=80);
76 | }
77 |
78 | #pDebug a#pShowToolBarButton:hover {
79 | background-color:#e28d29;
80 | padding-right:2px;
81 | border-top-color:#fff;
82 | border-left-color:#fff;
83 | border-bottom-color:#fff;
84 | opacity:1.0;
85 | }
86 |
87 | /* tablesorter */
88 | .header { cursor: pointer; position: relative; }
89 | .header:before { position: absolute; right: 0; bottom: 40%; content: '▲'; font-size: 10px; color: #bbb; }
90 | .header:after { position: absolute; right: 0; bottom: 20%; content: '▼'; font-size: 10px; color: #bbb; }
91 | .headerSortUp:before { content: ''; }
92 | .headerSortUp:after { color: inherit; }
93 | .headerSortDown:before { color: inherit; }
94 | .headerSortDown:after { content: ''; }
95 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/css/toolbar_button.css:
--------------------------------------------------------------------------------
1 |
2 | #pDebug #pDebugToolbarHandle {
3 | position:fixed;
4 | border:2px solid #e28d29;
5 | top: 25%;
6 | right:0;
7 | z-index:100000000;
8 |
9 | -webkit-border-top-left-radius: 20px;
10 | -webkit-border-bottom-left-radius: 20px;
11 | -moz-border-radius-topleft: 20px;
12 | -moz-border-radius-bottomleft: 20px;
13 | border-top-left-radius: 20px;
14 | border-bottom-left-radius: 20px;
15 | }
16 |
17 |
18 | #pDebug a#pShowToolBarButton {
19 | display:block;
20 | height:40px;
21 | width:50px;
22 | border-right:none;
23 | border-bottom:1px solid #fff;
24 | border-top:1px solid #fff;
25 | border-left:1px solid #fff;
26 | color:#fff;
27 | font-size:10px;
28 | font-weight:bold;
29 | text-decoration:none;
30 | text-align:center;
31 | text-indent:-999999px;
32 | background-color:#fff;
33 | background-size:40px 40px;
34 | background-image:url(../img/aiohttp.svg);
35 | background-position: left center;
36 | background-repeat: no-repeat;
37 | opacity:0.8;
38 | filter:alpha(opacity=80);
39 |
40 | -webkit-border-top-left-radius: 20px;
41 | -webkit-border-bottom-left-radius: 20px;
42 | -moz-border-radius-topleft: 20px;
43 | -moz-border-radius-bottomleft: 20px;
44 | border-top-left-radius: 20px;
45 | border-bottom-left-radius: 20px;
46 | }
47 |
48 | #pDebug a#pShowToolBarButton:hover {
49 | background-color:#fff;
50 | border-top-color:#fff;
51 | border-left-color:#fff;
52 | border-bottom-color:#fff;
53 |
54 | border-right:5px solid #ffc17a;
55 | opacity:1.0;
56 |
57 | -webkit-border-top-left-radius: 20px;
58 | -webkit-border-bottom-left-radius: 20px;
59 | -moz-border-radius-topleft: 20px;
60 | -moz-border-radius-bottomleft: 20px;
61 | border-top-left-radius: 20px;
62 | border-bottom-left-radius: 20px;
63 | }
64 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/font/FONT_LICENSE:
--------------------------------------------------------------------------------
1 | -------------------------------
2 | UBUNTU FONT LICENCE Version 1.0
3 | -------------------------------
4 |
5 | PREAMBLE
6 | This licence allows the licensed fonts to be used, studied, modified and
7 | redistributed freely. The fonts, including any derivative works, can be
8 | bundled, embedded, and redistributed provided the terms of this licence
9 | are met. The fonts and derivatives, however, cannot be released under
10 | any other licence. The requirement for fonts to remain under this
11 | licence does not require any document created using the fonts or their
12 | derivatives to be published under this licence, as long as the primary
13 | purpose of the document is not to be a vehicle for the distribution of
14 | the fonts.
15 |
16 | DEFINITIONS
17 | "Font Software" refers to the set of files released by the Copyright
18 | Holder(s) under this licence and clearly marked as such. This may
19 | include source files, build scripts and documentation.
20 |
21 | "Original Version" refers to the collection of Font Software components
22 | as received under this licence.
23 |
24 | "Modified Version" refers to any derivative made by adding to, deleting,
25 | or substituting -- in part or in whole -- any of the components of the
26 | Original Version, by changing formats or by porting the Font Software to
27 | a new environment.
28 |
29 | "Copyright Holder(s)" refers to all individuals and companies who have a
30 | copyright ownership of the Font Software.
31 |
32 | "Substantially Changed" refers to Modified Versions which can be easily
33 | identified as dissimilar to the Font Software by users of the Font
34 | Software comparing the Original Version with the Modified Version.
35 |
36 | To "Propagate" a work means to do anything with it that, without
37 | permission, would make you directly or secondarily liable for
38 | infringement under applicable copyright law, except executing it on a
39 | computer or modifying a private copy. Propagation includes copying,
40 | distribution (with or without modification and with or without charging
41 | a redistribution fee), making available to the public, and in some
42 | countries other activities as well.
43 |
44 | PERMISSION & CONDITIONS
45 | This licence does not grant any rights under trademark law and all such
46 | rights are reserved.
47 |
48 | Permission is hereby granted, free of charge, to any person obtaining a
49 | copy of the Font Software, to propagate the Font Software, subject to
50 | the below conditions:
51 |
52 | 1) Each copy of the Font Software must contain the above copyright
53 | notice and this licence. These can be included either as stand-alone
54 | text files, human-readable headers or in the appropriate machine-
55 | readable metadata fields within text or binary files as long as those
56 | fields can be easily viewed by the user.
57 |
58 | 2) The font name complies with the following:
59 | (a) The Original Version must retain its name, unmodified.
60 | (b) Modified Versions which are Substantially Changed must be renamed to
61 | avoid use of the name of the Original Version or similar names entirely.
62 | (c) Modified Versions which are not Substantially Changed must be
63 | renamed to both (i) retain the name of the Original Version and (ii) add
64 | additional naming elements to distinguish the Modified Version from the
65 | Original Version. The name of such Modified Versions must be the name of
66 | the Original Version, with "derivative X" where X represents the name of
67 | the new work, appended to that name.
68 |
69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the
70 | Font Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except (i) as required by this licence, (ii) to
72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
73 | their explicit written permission.
74 |
75 | 4) The Font Software, modified or unmodified, in part or in whole, must
76 | be distributed entirely under this licence, and must not be distributed
77 | under any other licence. The requirement for fonts to remain under this
78 | licence does not affect any document created using the Font Software,
79 | except any version of the Font Software extracted from a document
80 | created using the Font Software may only be distributed under this
81 | licence.
82 |
83 | TERMINATION
84 | This licence becomes null and void if any of the above conditions are
85 | not met.
86 |
87 | DISCLAIMER
88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
96 | DEALINGS IN THE FONT SOFTWARE.
97 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/font/ubuntu.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/font/ubuntu.ttf
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/asc.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/asc.gif
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/back.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/back_hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/back_hover.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/bg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/bg.gif
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/close.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/close_hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/close_hover.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/console.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/desc.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/desc.gif
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/headerbg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/headerbg.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/indicator.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/less.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/less.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/more.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/panel_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/panel_bg.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/source.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/source.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/tick-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/tick-red.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/img/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/aiohttp_debugtoolbar/static/img/tick.png
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/js/README.rst:
--------------------------------------------------------------------------------
1 | A note on the included libraries
2 | ================================
3 |
4 | The following libraries have been patched to prevent conflicts with the
5 | applications that include this toolbar. If these libraries are upgraded they
6 | must be re-patched.
7 |
8 | - require.js
9 | - jquery-1.7.2.min.js
10 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/js/debugger.js:
--------------------------------------------------------------------------------
1 | pyramid_debugtoolbar_require.config({
2 | paths: {
3 | "jquery": "jquery-1.10.2.min",
4 | "toolbar": "toolbar"
5 | }
6 | });
7 |
8 | pyramid_debugtoolbar_require([
9 | "jquery",
10 | "toolbar"], function($, tablesorter, toolbar) {
11 |
12 | $(function() {
13 | var sourceView = null;
14 |
15 | /**
16 | * if we are in console mode, show the console.
17 | */
18 | if (window.CONSOLE_MODE && window.EVALEX) {
19 | openShell(null, $('div.console div.inner').empty(), 0);
20 | }
21 |
22 | $('div.traceback div.frame').each(function() {
23 | var
24 | target = $('pre', this)
25 | .click(function() {
26 | sourceButton.click();
27 | }),
28 | consoleNode = null, source = null,
29 | frameID = this.id.substring(6);
30 |
31 | /**
32 | * Add an interactive console to the frames
33 | */
34 | if (EVALEX)
35 | $('' +
36 | ' ')
38 | .attr('title', 'Open an interactive python shell in this frame')
39 | .click(function() {
40 | consoleNode = openShell(consoleNode, target, frameID);
41 | return false;
42 | })
43 | .prependTo(target);
44 |
45 | /**
46 | * Show sourcecode
47 | */
48 | var sourceButton = $('' +
49 | ' ')
51 | .attr('title', 'Display the sourcecode for this frame')
52 | .click(function () {
53 | if (!sourceView)
54 | $('h2', sourceView =
55 | $('View Source ')
57 | .insertBefore('div.explanation'))
58 | .css('cursor', 'pointer')
59 | .click(function () {
60 | sourceView.slideUp('fast');
61 | });
62 | $.get(window.DEBUG_TOOLBAR_ROOT_PATH + '/source',
63 | {frm: frameID, token: window.DEBUGGER_TOKEN}, function (data) {
64 | var dataLine;
65 | var inFrame = data.inFrame;
66 | inFrame[0]++;
67 | if (inFrame !== null && inFrame[0] !== inFrame[1]) {
68 | dataLine = '' + inFrame[0] + '-' + (inFrame[1]);
69 | //if (data.line !== inFrame[0] && data.line !== inFrame[1]) {
70 | dataLine += (',' + data.line);
71 | //}
72 | } else {
73 | dataLine = '' + data.line
74 | }
75 | var code = '
'
77 | + data.source + '
';
78 |
79 | $('pre', sourceView)
80 | .replaceWith(code);
81 |
82 | Prism.highlightElement(document.querySelector('#pDebugTracebackSrc'));
83 |
84 | if (!sourceView.is(':visible'))
85 | sourceView.slideDown('fast', function () {
86 | focusSourceBlock();
87 | });
88 | else
89 | focusSourceBlock();
90 | });
91 | return false;
92 | })
93 | .prependTo(target);
94 | });
95 |
96 | /**
97 | * toggle traceback types on click.
98 | */
99 | $('h2.traceback').click(function() {
100 | $(this).next().slideToggle('fast');
101 | $('div.plain').slideToggle('fast');
102 | }).css('cursor', 'pointer');
103 | $('div.plain').hide();
104 |
105 | /**
106 | * Add extra info (this is here so that only users with JavaScript
107 | * enabled see it.)
108 | */
109 | $('span.nojavascript')
110 | .removeClass('nojavascript')
111 | .html('
To switch between the interactive traceback and the plaintext ' +
112 | 'one, you can click on the "Traceback" headline. From the text ' +
113 | 'traceback you can also create a paste of it. ' + (!window.EVALEX ? '' :
114 | 'For code execution mouse-over the frame you want to debug and ' +
115 | 'click on the console icon on the right side.' +
116 | '
You can execute arbitrary Python code in the stack frames and ' +
117 | 'there are some extra helpers available for introspection:' +
118 | '
dump()
shows all variables in the frame' +
119 | 'dump(obj)
dumps all that\'s known about the object'));
120 |
121 | /**
122 | * Add the pastebin feature
123 | */
124 | $('div.plain form')
125 | .submit(function() {
126 | var label = $('input[type="submit"]', this);
127 | var old_val = label.val();
128 | label.val('submitting...');
129 | $.ajax({
130 | dataType: 'json',
131 | url: window.DEBUG_TOOLBAR_ROOT_PATH + '/paste',
132 | data: {tb: window.TRACEBACK, token: window.DEBUGGER_TOKEN},
133 | success: function(data) {
134 | $('div.plain span.pastemessage')
135 | .removeClass('pastemessage')
136 | .text('Paste created: ')
137 | .append($('
#' + data.id + ' ').attr('href', data.url));
138 | },
139 | error: function() {
140 | alert('Error: Could not submit paste. No network connection?');
141 | label.val(old_val);
142 | }
143 | });
144 | return false;
145 | });
146 |
147 | // if we have javascript we submit by ajax anyways, so no need for the
148 | // not scaling textarea.
149 | var plainTraceback = $('div.plain textarea');
150 | plainTraceback.replaceWith($('
').text(plainTraceback.text()));
151 |
152 | /**
153 | * Helper function for shell initialization
154 | */
155 | function openShell(consoleNode, target, frameID) {
156 | if (consoleNode)
157 | return consoleNode.slideToggle('fast');
158 | consoleNode = $('')
159 | .appendTo(target.parent())
160 | .hide()
161 | var historyPos = 0, history = [''];
162 | var output = $('[console ready]
')
163 | .appendTo(consoleNode);
164 | var form = $(' ')
165 | .submit(function() {
166 | var cmd = command.val();
167 | $.get(window.DEBUG_TOOLBAR_ROOT_PATH + '/execute', {
168 | cmd: cmd, frm: frameID, token:window.DEBUGGER_TOKEN}, function(data) {
169 | var tmp = $('
').html(data);
170 | output.append(tmp);
171 | command.focus();
172 | consoleNode.scrollTop(command.position().top);
173 | var old = history.pop();
174 | history.push(cmd);
175 | if (typeof old != 'undefined')
176 | history.push(old);
177 | historyPos = history.length - 1;
178 | });
179 | command.val('');
180 | return false;
181 | }).
182 | appendTo(consoleNode);
183 |
184 | var command = $('
')
185 | .appendTo(form)
186 | .keydown(function(e) {
187 | if (e.charCode == 100 && e.ctrlKey) {
188 | output.text('--- screen cleared ---');
189 | return false;
190 | }
191 | else if (e.charCode == 0 && (e.keyCode == 38 || e.keyCode == 40)) {
192 | if (e.keyCode == 38 && historyPos > 0)
193 | historyPos--;
194 | else if (e.keyCode == 40 && historyPos < history.length)
195 | historyPos++;
196 | command.val(history[historyPos]);
197 | return false;
198 | }
199 | });
200 |
201 | return consoleNode.slideDown('fast', function() {
202 | command.focus();
203 | });
204 | }
205 |
206 | /**
207 | * Focus the current block in the source view.
208 | */
209 | function focusSourceBlock() {
210 | var tmp, line = $('pre code .line-highlight');
211 | for (var i = 0; i < 7; i++) {
212 | tmp = line.prev();
213 | if (!(tmp && tmp.is('.in-frame')))
214 | break;
215 | line = tmp;
216 | }
217 | var container = $('div.sourceview');
218 | $('html, body').scrollTop(line.offset().top);
219 | }
220 | });
221 | $.noConflict(true);
222 | });
223 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/js/jquery.cookie.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery Cookie Plugin v1.3.1
3 | * https://github.com/carhartl/jquery-cookie
4 | *
5 | * Copyright 2013 Klaus Hartl
6 | * Released under the MIT license
7 | */
8 | (function (factory) {
9 | if (typeof define === 'function' && define.amd) {
10 | // AMD. Register as anonymous module.
11 | define(['jquery'], factory);
12 | } else {
13 | // Browser globals.
14 | factory(jQuery);
15 | }
16 | }(function ($) {
17 |
18 | var pluses = /\+/g;
19 |
20 | function raw(s) {
21 | return s;
22 | }
23 |
24 | function decoded(s) {
25 | return decodeURIComponent(s.replace(pluses, ' '));
26 | }
27 |
28 | function converted(s) {
29 | if (s.indexOf('"') === 0) {
30 | // This is a quoted cookie as according to RFC2068, unescape
31 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
32 | }
33 | try {
34 | return config.json ? JSON.parse(s) : s;
35 | } catch(er) {}
36 | }
37 |
38 | var config = $.cookie = function (key, value, options) {
39 |
40 | // write
41 | if (value !== undefined) {
42 | options = $.extend({}, config.defaults, options);
43 |
44 | if (typeof options.expires === 'number') {
45 | var days = options.expires, t = options.expires = new Date();
46 | t.setDate(t.getDate() + days);
47 | }
48 |
49 | value = config.json ? JSON.stringify(value) : String(value);
50 |
51 | return (document.cookie = [
52 | config.raw ? key : encodeURIComponent(key),
53 | '=',
54 | config.raw ? value : encodeURIComponent(value),
55 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
56 | options.path ? '; path=' + options.path : '',
57 | options.domain ? '; domain=' + options.domain : '',
58 | options.secure ? '; secure' : ''
59 | ].join(''));
60 | }
61 |
62 | // read
63 | var decode = config.raw ? raw : decoded;
64 | var cookies = document.cookie.split('; ');
65 | var result = key ? undefined : {};
66 | for (var i = 0, l = cookies.length; i < l; i++) {
67 | var parts = cookies[i].split('=');
68 | var name = decode(parts.shift());
69 | var cookie = decode(parts.join('='));
70 |
71 | if (key && key === name) {
72 | result = converted(cookie);
73 | break;
74 | }
75 |
76 | if (!key) {
77 | result[name] = converted(cookie);
78 | }
79 | }
80 |
81 | return result;
82 | };
83 |
84 | config.defaults = {};
85 |
86 | $.removeCookie = function (key, options) {
87 | if ($.cookie(key) !== undefined) {
88 | // Must not alter options, thus extending a fresh object...
89 | $.cookie(key, '', $.extend({}, options, { expires: -1 }));
90 | return true;
91 | }
92 | return false;
93 | };
94 |
95 | }));
96 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/js/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
QUnit Tests for the Pyramid Debug Toolbar
6 |
7 |
8 |
9 |
10 |
11 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/static/js/toolbar.js:
--------------------------------------------------------------------------------
1 | var COOKIE_NAME_ACTIVE = 'pdtb_active';
2 |
3 |
4 | function toggle_content(elem) {
5 | if (elem.is(':visible')) {
6 | elem.hide();
7 | } else {
8 | elem.show();
9 | }
10 | }
11 |
12 | function toggle_active(elem) {
13 | elem.toggleClass('active');
14 | }
15 |
16 | jQuery(document).ready(function($) {
17 |
18 |
19 | // When clicked on the panels menu
20 | $(".pDebugPanels li:not(.disabled) a").click( function(event_) {
21 | event_.stopPropagation();
22 | $(".pDebugPanels li").removeClass("active");
23 | parent_ = $(this).parent();
24 | toggle_active(parent_);
25 |
26 | $(".panelContent").hide();
27 | $(".pDebugWindow").show();
28 | current = $('.pDebugWindow #' + parent_.attr('id') + '-content');
29 | current.show();
30 | });
31 |
32 |
33 | $('#settings .switch').click(function() {
34 | var $panel = $(this).parent();
35 | var $this = $(this);
36 | var dom_id = $this.attr('id').replace("-switch", "");
37 | // Turn cookie content into an array of active panels
38 | var active_str = $.cookie(COOKIE_NAME_ACTIVE);
39 | var active = (active_str) ? active_str.split(';') : [];
40 | active = $.grep(active, function(n,i) { return n != dom_id; });
41 | if ($this.hasClass('active')) {
42 | $this.removeClass('active');
43 | $this.addClass('inactive');
44 | }
45 | else {
46 | active.push(dom_id);
47 | $this.removeClass('inactive');
48 | $this.addClass('active');
49 | }
50 | if (active.length > 0) {
51 | $.cookie(COOKIE_NAME_ACTIVE, active.join(';'), {
52 | path: '/', expires: 10
53 | });
54 | }
55 | else {
56 | $.cookie(COOKIE_NAME_ACTIVE, null, {
57 | path: '/', expires: -1
58 | });
59 | }
60 | });
61 |
62 | // $(".pDebugSortable").tablesorter();
63 |
64 | bootstrap_panels = ['pDebugVersionPanel', 'pDebugHeaderPanel']
65 |
66 | for (var i = 0; i < bootstrap_panels.length; i++) {
67 | $('.pDebugWindow #' + bootstrap_panels[i] + '-content').show();
68 | $('li#' + bootstrap_panels[i]).addClass('active');
69 | }
70 |
71 | });
72 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/tbtools/__init__.py:
--------------------------------------------------------------------------------
1 | # TODO: remove somehow
2 | def text_(s, encoding="latin-1", errors="strict"):
3 | if isinstance(s, bytes):
4 | return s.decode(encoding, errors)
5 | return s # pragma: no cover
6 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/tbtools/console.py:
--------------------------------------------------------------------------------
1 | """werkzeug.debug.console
2 |
3 | Interactive console support.
4 |
5 | :copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
6 | :license: BSD.
7 | """
8 | import code
9 | import sys
10 | import threading
11 | from types import CodeType
12 |
13 | from .repr import debug_repr, dump, helper
14 | from ..utils import escape
15 |
16 | _local = threading.local()
17 |
18 |
19 | class HTMLStringO:
20 | """A StringO version that HTML escapes on write."""
21 |
22 | def __init__(self):
23 | self._buffer = []
24 |
25 | def isatty(self):
26 | return False
27 |
28 | def close(self):
29 | pass
30 |
31 | def flush(self):
32 | pass
33 |
34 | def seek(self, n, mode=0):
35 | pass
36 |
37 | def readline(self):
38 | if len(self._buffer) == 0:
39 | return ""
40 | ret = self._buffer[0]
41 | del self._buffer[0]
42 | return ret
43 |
44 | def reset(self):
45 | val = "".join(self._buffer)
46 | del self._buffer[:]
47 | return val
48 |
49 | def _write(self, x):
50 | if isinstance(x, bytes):
51 | x = str(x, encoding="utf-8", errors="replace")
52 | self._buffer.append(x)
53 |
54 | def write(self, x):
55 | self._write(escape(x))
56 |
57 | def writelines(self, x):
58 | self._write(escape("".join(x)))
59 |
60 |
61 | class ThreadedStream:
62 | """Thread-local wrapper for sys.stdout for the interactive console."""
63 |
64 | @staticmethod
65 | def push():
66 | if not isinstance(sys.stdout, ThreadedStream):
67 | sys.stdout = ThreadedStream()
68 | _local.stream = HTMLStringO()
69 |
70 | @staticmethod
71 | def fetch():
72 | try:
73 | stream = _local.stream
74 | except AttributeError:
75 | return ""
76 | return stream.reset()
77 |
78 | @staticmethod
79 | def displayhook(obj):
80 | try:
81 | stream = _local.stream
82 | except AttributeError:
83 | return _displayhook(obj)
84 | # stream._write bypasses escaping as debug_repr is
85 | # already generating HTML for us.
86 | if obj is not None:
87 | _local._current_ipy.locals["_"] = obj
88 | stream._write(debug_repr(obj))
89 |
90 | def __setattr__(self, name, value):
91 | raise AttributeError("read only attribute %s" % name)
92 |
93 | def __dir__(self):
94 | return dir(sys.__stdout__)
95 |
96 | def __getattribute__(self, name):
97 | if name == "__members__":
98 | return dir(sys.__stdout__)
99 | try:
100 | stream = _local.stream
101 | except AttributeError:
102 | stream = sys.__stdout__
103 | return getattr(stream, name)
104 |
105 | def __repr__(self):
106 | return repr(sys.__stdout__)
107 |
108 |
109 | # add the threaded stream as display hook
110 | _displayhook = sys.displayhook
111 | sys.displayhook = ThreadedStream.displayhook
112 |
113 |
114 | class _ConsoleLoader:
115 | def __init__(self):
116 | self._storage = {}
117 |
118 | def register(self, code, source):
119 | self._storage[id(code)] = source
120 | # register code objects of wrapped functions too.
121 | for var in code.co_consts:
122 | if isinstance(var, CodeType):
123 | self._storage[id(var)] = source
124 |
125 | def get_source_by_code(self, code):
126 | try:
127 | return self._storage[id(code)]
128 | except KeyError:
129 | pass
130 |
131 |
132 | def _wrap_compiler(console):
133 | compile = console.compile
134 |
135 | def func(source, filename, symbol):
136 | code = compile(source, filename, symbol)
137 | console.loader.register(code, source)
138 | return code
139 |
140 | console.compile = func
141 |
142 |
143 | class _InteractiveConsole(code.InteractiveInterpreter):
144 | def __init__(self, app, globals, locals):
145 | self._app = app
146 | code.InteractiveInterpreter.__init__(self, locals)
147 | self.globals = dict(globals)
148 | self.globals["dump"] = dump
149 | self.globals["help"] = helper
150 | self.globals["__loader__"] = self.loader = _ConsoleLoader()
151 | self.more = False
152 | self.buffer = []
153 | _wrap_compiler(self)
154 |
155 | def runsource(self, source):
156 | source = source.rstrip() + "\n"
157 | ThreadedStream.push()
158 | prompt = self.more and "... " or ">>> "
159 | try:
160 | source_to_eval = "".join(self.buffer + [source])
161 | if code.InteractiveInterpreter.runsource(
162 | self, source_to_eval, "
", "single"
163 | ):
164 | self.more = True
165 | self.buffer.append(source)
166 | else:
167 | self.more = False
168 | del self.buffer[:]
169 | finally:
170 | output = ThreadedStream.fetch()
171 | return prompt + source + output
172 |
173 | def runcode(self, code):
174 | try:
175 | exec(code, self.globals, self.locals) # noqa: S102
176 | except Exception as exc:
177 | self.showtraceback(exc)
178 |
179 | def showtraceback(self, exc):
180 | from .tbtools import get_current_traceback
181 |
182 | tb = get_current_traceback(skip=1, exc=exc, app=self._app)
183 | sys.stdout._write(tb.render_summary(self._app))
184 |
185 | def showsyntaxerror(self, filename=None):
186 | from .tbtools import get_current_traceback
187 |
188 | exc = SyntaxError(filename)
189 | tb = get_current_traceback(skip=6, exc=exc, app=self._app)
190 | sys.stdout._write(tb.render_summary(self._app))
191 |
192 | def write(self, data):
193 | sys.stdout.write(data)
194 |
195 |
196 | class Console:
197 | """An interactive console."""
198 |
199 | def __init__(self, app, globals=None, locals=None):
200 | self._app = app
201 | if locals is None:
202 | locals = {}
203 | if globals is None:
204 | globals = {}
205 | self._ipy = _InteractiveConsole(app, globals, locals)
206 |
207 | def eval(self, code):
208 | _local._current_ipy = self._ipy
209 | old_sys_stdout = sys.stdout
210 | try:
211 | return self._ipy.runsource(code)
212 | finally:
213 | sys.stdout = old_sys_stdout
214 |
215 |
216 | class _ConsoleFrame:
217 | """Helper class so that we can reuse the frame console code for the
218 | standalone console.
219 | """
220 |
221 | def __init__(self, namespace, app):
222 | self.console = Console(namespace, app)
223 | self.id = 0
224 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/console.jinja2:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | {{ title }} // Werkzeug Debugger
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Interactive Console
20 |
21 |
22 | In this console you can execute Python expressions in the context of
23 | the application. The initial namespace was created by the debugger
24 | automatically.
25 |
26 |
27 |
28 |
The Console requires JavaScript.
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/exception.jinja2:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | {{ title }} // Werkzeug Debugger
7 |
8 |
9 |
10 |
12 | {# #}
13 |
14 |
22 |
24 |
25 |
26 |
27 |
28 |
{{ exception_type }}
29 |
30 |
{{ exception }}
31 |
32 |
Traceback (most recent call last)
33 | {{ summary|safe }}
34 |
35 |
36 |
37 | This is the Copy/Paste friendly version of the traceback.
38 |
39 |
41 |
42 |
43 |
44 |
{{ exception }}
45 |
46 |
47 |
48 |
49 | Warning: this feature should not be enabled on production
50 | systems.
51 |
52 |
53 | {% if evalex %}
54 |
55 |
56 | Hover over any gray area in the traceback and click on the
57 |
58 |
59 |
60 | button on the right hand side of that gray area to show an interactive
61 | console for the associated frame. Type arbitrary Python into the
62 | console; it will be evaluated in the context of the associated frame. In
63 | the interactive console there are helpers available for introspection:
64 |
65 |
66 | dump()
shows all variables in the frame
67 | dump(obj)
dumps all that's known about the object
68 |
69 |
70 | {% endif %}
71 |
72 |
73 | Hover over any gray area in the traceback and click on
74 |
75 |
76 |
77 | on the right hand side of that gray area to show the source of the file
78 | associated with the frame.
79 |
80 |
81 |
82 | Click on the traceback header to switch back and forth between the
83 | rendered version of the traceback and a plaintext copy-paste-friendly
84 | version of the traceback.
85 |
86 |
87 |
88 | URL to recover this traceback page: {{ url }}
89 |
90 |
91 |
92 |
96 |
97 |
102 |
103 | {# #}
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/exception_summary.jinja2:
--------------------------------------------------------------------------------
1 |
2 | {{ title }}
3 |
4 | {{ description|safe }}
5 |
6 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/global_tab.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
39 |
40 |
41 | {% for panel in global_panels %}
42 | {% if panel.has_content %}
43 |
44 |
45 |
{{ panel.title }}
46 |
47 |
48 |
49 | {{ panel.render_content(request)|safe }}
50 |
51 |
52 |
53 | {% endif %}
54 | {% endfor %}
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/history_tab.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
29 |
30 |
31 | {% for panel in panels %}
32 | {% if panel.has_content %}
33 |
34 |
35 |
{{ panel.title }}
36 |
37 |
38 |
39 | {{ panel.render_content(request)|safe }}
40 |
41 |
42 |
43 | {% endif %}
44 | {% endfor %}
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/redirect.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 | Redirect intercepted
4 |
5 |
6 | Redirect ({{ redirect_code }})
7 | Location: {{ redirect_to }}
8 |
9 |
10 | The Debug Toolbar has intercepted a redirect to the above URL for
11 | debug viewing purposes. You can click the above link to continue
12 | with the redirect as normal. If you'd like to disable this
13 | feature, you can set the settings
14 | variable debugtoolbar.intercept_redirects
15 | to False
.
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/settings_tab.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Settings
6 |
7 |
Some aiohttp debug toolbar panels can be activated (this enables additional features)
8 |
9 | {% for panel in panels %}
10 | {% if panel.user_activate %}
11 |
12 | {{ panel.name }}
13 |
14 |
Enable:
17 | {% endif %}
18 | {% endfor %}
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/templates/toolbar.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Aiohttp Debug Toolbar
6 |
7 |
8 |
9 |
10 |
11 | {# #}
12 |
13 |
14 |
15 | {# include scripts here that should be included before pageload #}
16 | {# this *should* only be jquery, as we only need the `$` variable defined #}
17 | {# in order for other javascript to be run after the document is loaded #}
18 |
19 |
22 |
23 |
24 |
25 |
26 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {% include "history_tab.jinja2" %}
55 |
56 |
57 |
58 | {% include "global_tab.jinja2" %}
59 |
60 |
61 |
62 | {% include "settings_tab.jinja2" %}
63 |
64 |
65 |
66 |
67 |
68 |
69 | {# scripts that can be included after pageload #}
70 |
71 |
72 |
73 |
74 |
131 | {# #}
132 | {# #}
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/toolbar.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import unquote as url_unquote
2 |
3 | from aiohttp.web import Response
4 |
5 | from .utils import APP_KEY, STATIC_ROUTE_NAME, replace_insensitive
6 |
7 | __all__ = ["DebugToolbar"]
8 |
9 |
10 | class DebugToolbar:
11 | def __init__(self, request, panel_classes, global_panel_classes):
12 | self.panels = []
13 | self.global_panels = []
14 | self.request = request
15 | self.status = 200
16 |
17 | # Panels can be be activated (more features) (e.g. Performace panel)
18 | pdtb_active = url_unquote(request.cookies.get("pdtb_active", ""))
19 |
20 | activated = pdtb_active.split(";")
21 | # XXX
22 | for panel_class in panel_classes:
23 | panel_inst = panel_class(request)
24 | if panel_inst.dom_id in activated and panel_inst.has_content:
25 | panel_inst.is_active = True
26 | self.panels.append(panel_inst)
27 |
28 | for panel_class in global_panel_classes:
29 | panel_inst = panel_class(request)
30 | if panel_inst.dom_id in activated and panel_inst.has_content:
31 | panel_inst.is_active = True
32 | self.global_panels.append(panel_inst)
33 |
34 | @property
35 | def json(self):
36 | return {
37 | "method": self.request.method,
38 | "path": self.request.path,
39 | "scheme": "http",
40 | "status_code": self.status,
41 | }
42 |
43 | async def process_response(self, request, response):
44 | # if isinstance(response, WSGIHTTPException):
45 | # the body of a WSGIHTTPException needs to be "prepared"
46 | # response.prepare(request.environ)
47 | for panel in self.panels:
48 | await panel.process_response(response)
49 | for panel in self.global_panels:
50 | await panel.process_response(response)
51 |
52 | def inject(self, request, response):
53 | """
54 | Inject the debug toolbar iframe into an HTML response.
55 | """
56 | # called in host app
57 | if not isinstance(response, Response):
58 | return
59 | settings = request.app[APP_KEY]["settings"]
60 | response_html = response.body
61 | route = request.app.router["debugtoolbar.request"]
62 | toolbar_url = route.url_for(request_id=request["id"])
63 |
64 | button_style = settings["button_style"]
65 |
66 | css_path = request.app.router[STATIC_ROUTE_NAME].url_for(
67 | filename="css/toolbar_button.css"
68 | )
69 |
70 | toolbar_css = toolbar_css_template % {"css_path": css_path}
71 | toolbar_html = toolbar_html_template % {
72 | "button_style": button_style,
73 | "css_path": css_path,
74 | "toolbar_url": toolbar_url,
75 | }
76 |
77 | toolbar_html = toolbar_html.encode(response.charset or "utf-8")
78 | toolbar_css = toolbar_css.encode(response.charset or "utf-8")
79 | response_html = replace_insensitive(
80 | response_html, b"", toolbar_css + b""
81 | )
82 | response.body = replace_insensitive(
83 | response_html, b"
9 | AJAX Test
10 | This page makes an AJAX request every 5 seconds.
11 |
17 | ", toolbar_html + b""
84 | )
85 |
86 |
87 | toolbar_css_template = """\
88 | """
89 |
90 | toolbar_html_template = """\
91 |
98 | """
99 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/utils.py:
--------------------------------------------------------------------------------
1 | import binascii
2 | import ipaddress
3 | import os
4 | import sys
5 | from collections import deque
6 | from itertools import islice
7 | from typing import Literal, Sequence, TYPE_CHECKING, Tuple, Type, TypedDict
8 |
9 | import jinja2
10 | from aiohttp.web import AppKey
11 |
12 | if TYPE_CHECKING: # pragma: no cover
13 | from .panels.base import DebugPanel
14 | else:
15 | DebugPanel = None
16 |
17 | REDIRECT_CODES = (300, 301, 302, 303, 305, 307, 308)
18 | STATIC_PATH = "static/"
19 | ROOT_ROUTE_NAME = "debugtoolbar.main"
20 | STATIC_ROUTE_NAME = "debugtoolbar.static"
21 | EXC_ROUTE_NAME = "debugtoolbar.exception"
22 |
23 |
24 | def hexlify(value):
25 | # value must be int or bytes
26 | if isinstance(value, int):
27 | value = bytes(str(value), encoding="utf-8")
28 | return str(binascii.hexlify(value), encoding="utf-8")
29 |
30 |
31 | # TODO: refactor to simpler container or change to ordered dict
32 | class ToolbarStorage(deque):
33 | """Deque for storing Toolbar objects."""
34 |
35 | def __init__(self, max_elem):
36 | super().__init__([], max_elem)
37 |
38 | def get(self, request_id, default=None):
39 | dict_ = dict(self)
40 | return dict_.get(request_id, default)
41 |
42 | def put(self, request_id, request):
43 | self.appendleft((request_id, request))
44 |
45 | def last(self, num_items):
46 | """Returns the last `num_items` Toolbar objects"""
47 | return list(islice(self, 0, num_items))
48 |
49 |
50 | class ExceptionHistory:
51 | def __init__(self):
52 | self.frames = {}
53 | self.tracebacks = {}
54 | self.eval_exc = "show"
55 |
56 |
57 | class _Config(TypedDict):
58 | enabled: bool
59 | intercept_exc: Literal["debug", "display", False]
60 | intercept_redirects: bool
61 | panels: Tuple[Type[DebugPanel], ...]
62 | extra_panels: Tuple[Type[DebugPanel], ...]
63 | global_panels: Tuple[Type[DebugPanel], ...]
64 | hosts: Sequence[str]
65 | exclude_prefixes: Tuple[str, ...]
66 | check_host: bool
67 | button_style: str
68 | max_visible_requests: int
69 | path_prefix: str
70 |
71 |
72 | class AppState(TypedDict):
73 | exc_history: ExceptionHistory
74 | pdtb_token: str
75 | request_history: ToolbarStorage
76 | settings: _Config
77 |
78 |
79 | APP_KEY = AppKey("APP_KEY", AppState)
80 | TEMPLATE_KEY = AppKey("TEMPLATE_KEY", jinja2.Environment)
81 |
82 |
83 | def addr_in(addr, hosts):
84 | for host in hosts:
85 | if ipaddress.ip_address(addr) in ipaddress.ip_network(host):
86 | return True
87 | return False
88 |
89 |
90 | def replace_insensitive(string, target, replacement):
91 | """Similar to string.replace() but is case insensitive
92 | Code borrowed from: http://forums.devshed.com/python-programming-11/
93 | case-insensitive-string-replace-490921.html
94 | """
95 | no_case = string.lower()
96 | index = no_case.rfind(target.lower())
97 | if index >= 0:
98 | start = index + len(target)
99 | return string[:index] + replacement + string[start:]
100 | else: # no results so return the original string
101 | return string
102 |
103 |
104 | def render(template_name, app, context, *, app_key=TEMPLATE_KEY, **kw):
105 | lookup = app[app_key]
106 | template = lookup.get_template(template_name)
107 | c = context.copy()
108 | c.update(kw)
109 | txt = template.render(**c)
110 | return txt
111 |
112 |
113 | def common_segment_count(path, value):
114 | """Return the number of path segments common to both"""
115 | i = 0
116 | if len(path) <= len(value):
117 | for x1, x2 in zip(path, value):
118 | if x1 == x2:
119 | i += 1
120 | else:
121 | return 0
122 | return i
123 |
124 |
125 | def format_fname(value, _sys_path=None):
126 | if _sys_path is None:
127 | _sys_path = sys.path # dependency injection
128 | # If the value is not an absolute path, the it is a builtin or
129 | # a relative file (thus a project file).
130 | if not os.path.isabs(value):
131 | if value.startswith(("{", "<")):
132 | return value
133 | if value.startswith("." + os.path.sep):
134 | return value
135 | return "." + os.path.sep + value
136 |
137 | # Loop through sys.path to find the longest match and return
138 | # the relative path from there.
139 | prefix_len = 0
140 | value_segs = value.split(os.path.sep)
141 | for path in _sys_path:
142 | count = common_segment_count(path.split(os.path.sep), value_segs)
143 | if count > prefix_len:
144 | prefix_len = count
145 | return "<%s>" % os.path.sep.join(value_segs[prefix_len:])
146 |
147 |
148 | def escape(s, quote=False):
149 | """Replace special characters "&", "<" and ">" to HTML-safe sequences. If
150 | the optional flag `quote` is `True`, the quotation mark character is
151 | also translated.
152 |
153 | There is a special handling for `None` which escapes to an empty string.
154 |
155 | :param s: the string to escape.
156 | :param quote: set to true to also escape double quotes.
157 | """
158 | if s is None:
159 | return ""
160 |
161 | if not isinstance(s, (str, bytes)):
162 | s = str(s)
163 | if isinstance(s, bytes):
164 | try:
165 | s.decode("ascii")
166 | except UnicodeDecodeError:
167 | s = s.decode("utf-8", "replace")
168 | s = s.replace("&", "&").replace("<", "<").replace(">", ">")
169 | if quote:
170 | s = s.replace('"', """)
171 | return s
172 |
173 |
174 | class ContextSwitcher:
175 | """This object is alternative to *await*. It is useful in cases
176 | when you need to track context switches inside coroutine.
177 |
178 | see: https://www.python.org/dev/peps/pep-0380/#formal-semantics
179 | """
180 |
181 | def __init__(self):
182 | self._on_context_switch_out = []
183 | self._on_context_switch_in = []
184 |
185 | def add_context_in(self, callback):
186 | if not callable(callback):
187 | raise ValueError("callback should be callable")
188 | self._on_context_switch_in.append(callback)
189 |
190 | def add_context_out(self, callback):
191 | if not callable(callback):
192 | raise ValueError("callback should be callable")
193 | self._on_context_switch_out.append(callback)
194 |
195 | def __call__(self, expr):
196 | def iterate():
197 | for callbale in self._on_context_switch_in:
198 | callbale()
199 |
200 | _i = iter(expr.__await__())
201 | try:
202 | _y = next(_i)
203 | except StopIteration as _e:
204 | _r = _e.value
205 | else:
206 | while 1:
207 | try:
208 | for callbale in self._on_context_switch_out:
209 | callbale()
210 | _s = yield _y
211 | for callbale in self._on_context_switch_in:
212 | callbale()
213 | except GeneratorExit as _e:
214 | try:
215 | _m = _i.close
216 | except AttributeError:
217 | pass
218 | else:
219 | _m()
220 | raise _e
221 | except BaseException as _e:
222 | _x = sys.exc_info()
223 | try:
224 | _m = _i.throw
225 | except AttributeError:
226 | raise _e
227 | else:
228 | try:
229 | _y = _m(*_x)
230 | except StopIteration as _e:
231 | _r = _e.value
232 | break
233 | else:
234 | try:
235 | if _s is None:
236 | _y = next(_i)
237 | else:
238 | _y = _i.send(_s)
239 | except StopIteration as _e:
240 | _r = _e.value
241 | break
242 | result = _r
243 | for callbale in self._on_context_switch_out:
244 | callbale()
245 | return result
246 |
247 | return _Coro(iterate())
248 |
249 |
250 | class _Coro:
251 | __slots__ = ("_it",)
252 |
253 | def __init__(self, it):
254 | self._it = it
255 |
256 | def __await__(self):
257 | return self._it
258 |
--------------------------------------------------------------------------------
/aiohttp_debugtoolbar/views.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import aiohttp_jinja2
4 | from aiohttp import web
5 |
6 | # from .tbtools.console import _ConsoleFrame
7 | from .utils import APP_KEY, ROOT_ROUTE_NAME, STATIC_ROUTE_NAME, TEMPLATE_KEY
8 |
9 |
10 | @aiohttp_jinja2.template("toolbar.jinja2", app_key=TEMPLATE_KEY)
11 | async def request_view(request):
12 | settings = request.app[APP_KEY]["settings"]
13 | history = request.app[APP_KEY]["request_history"]
14 |
15 | try:
16 | last_request_pair = history.last(1)[0]
17 | except IndexError:
18 | last_request_id = None
19 | else:
20 | last_request_id = last_request_pair[0]
21 |
22 | request_id = request.match_info.get("request_id", last_request_id)
23 |
24 | toolbar = history.get(request_id, None)
25 |
26 | panels = toolbar.panels if toolbar else []
27 | global_panels = toolbar.global_panels if toolbar else []
28 |
29 | static_path = request.app.router[STATIC_ROUTE_NAME].canonical
30 | root_path = request.app.router[ROOT_ROUTE_NAME].url_for()
31 |
32 | button_style = settings.get("button_style", "")
33 | max_visible_requests = settings["max_visible_requests"]
34 |
35 | hist_toolbars = history.last(max_visible_requests)
36 |
37 | return {
38 | "panels": panels,
39 | "static_path": static_path,
40 | "root_path": root_path,
41 | "button_style": button_style,
42 | "history": hist_toolbars,
43 | "global_panels": global_panels,
44 | "request_id": request_id,
45 | "request": toolbar.request if toolbar else None,
46 | }
47 |
48 |
49 | class ExceptionDebugView:
50 | def _validate_token(self, request):
51 | exc_history = self._exception_history(request)
52 | token = request.query.get("token")
53 |
54 | if exc_history is None:
55 | raise web.HTTPBadRequest(text="No exception history")
56 | if not token:
57 | raise web.HTTPBadRequest(text="No token in request")
58 | if not (token == request.app[APP_KEY]["pdtb_token"]):
59 | raise web.HTTPBadRequest(text="Bad token in request")
60 |
61 | def _exception_history(self, request):
62 | return request.app[APP_KEY]["exc_history"]
63 |
64 | def _get_frame(self, request):
65 | frm = request.query.get("frm")
66 | if frm is not None:
67 | frm = int(frm)
68 | return frm
69 |
70 | async def _get_tb(self, request):
71 | await request.read()
72 | tb = request.query.get("tb")
73 | if not tb:
74 | await request.post()
75 | tb = request.POST.get("tb")
76 | if tb is not None:
77 | tb = int(tb)
78 | return tb
79 |
80 | async def _get_cmd(self, request):
81 | await request.read()
82 | cmd = request.query.get("cmd")
83 | if not cmd:
84 | await request.post()
85 | cmd = request.POST.get("cmd")
86 | return cmd
87 |
88 | async def exception(self, request):
89 | self._validate_token(request)
90 | tb_id = await self._get_tb(request)
91 | tb = self._exception_history(request).tracebacks[tb_id]
92 | body = tb.render_full(request).encode("utf-8", "replace")
93 | response = web.Response(status=200)
94 | response.body = body
95 | return response
96 |
97 | async def source(self, request):
98 | self._validate_token(request)
99 | exc_history = self._exception_history(request)
100 | _frame = self._get_frame(request)
101 | if _frame is not None:
102 | frame = exc_history.frames.get(_frame)
103 | if frame is not None:
104 | # text = frame.render_source()
105 | in_frame = frame.get_in_frame_range()
106 | text = json.dumps(
107 | {
108 | "line": frame.lineno,
109 | "inFrame": in_frame,
110 | "source": "\n".join(frame.sourcelines),
111 | }
112 | )
113 | return web.Response(text=text, content_type="application/json")
114 | raise web.HTTPBadRequest()
115 |
116 | async def execute(self, request):
117 | self._validate_token(request)
118 |
119 | _exc_history = self._exception_history(request)
120 | if _exc_history.eval_exc:
121 | exc_history = _exc_history
122 | cmd = await self._get_cmd(request)
123 | frame = self._get_frame(request)
124 | if frame is not None and cmd is not None:
125 | frame = exc_history.frames.get(frame)
126 | if frame is not None:
127 | result = frame.console.eval(cmd)
128 | return web.Response(text=result, content_type="text/html")
129 | raise web.HTTPBadRequest()
130 |
131 | # TODO: figure out how to enable console mode on frontend
132 | # @aiohttp_jinja2.template('console.jinja2', app_key=TEMPLATE_KEY)
133 | # async def console(self, request):
134 | # self._validate_token(request)
135 | # static_path = request.app.router[STATIC_ROUTE_NAME].canonical
136 | # root_path = request.app.router[ROOT_ROUTE_NAME].url()
137 | # token = request.query.get('token')
138 | # tb = await self._get_tb(request)
139 | #
140 | # _exc_history = self._exception_history(request)
141 | # vars = {
142 | # 'evalex': _exc_history.eval_exc and 'true' or 'false',
143 | # 'console': 'true',
144 | # 'title': 'Console',
145 | # 'traceback_id': tb or -1,
146 | # 'root_path': root_path,
147 | # 'static_path': static_path,
148 | # 'token': token,
149 | # }
150 | # if 0 not in _exc_history.frames:
151 | # _exc_history.frames[0] = _ConsoleFrame({})
152 | # return vars
153 |
154 |
155 | U_SSE_PAYLOAD = "id: {0}\nevent: new_request\ndata: {1}\n\n"
156 |
157 |
158 | async def sse(request):
159 | response = web.Response(status=200)
160 | response.content_type = "text/event-stream"
161 | history = request.app[APP_KEY]["request_history"]
162 | response.text = ""
163 |
164 | active_request_id = str(request.match_info.get("request_id"))
165 | client_last_request_id = str(request.headers.get("Last-Event-Id", 0))
166 |
167 | settings = request.app[APP_KEY]["settings"]
168 | max_visible_requests = settings["max_visible_requests"]
169 |
170 | if history:
171 | last_request_pair = history.last(1)[0]
172 | last_request_id = last_request_pair[0]
173 | if not last_request_id == client_last_request_id:
174 | data = []
175 | for _id, toolbar in history.last(max_visible_requests):
176 | req_type = "active" if active_request_id == _id else ""
177 | data.append([_id, toolbar.json, req_type])
178 |
179 | if data:
180 | response.text = U_SSE_PAYLOAD.format(last_request_id, json.dumps(data))
181 | return response
182 |
--------------------------------------------------------------------------------
/demo/README.rst:
--------------------------------------------------------------------------------
1 | Play With Demo
2 | --------------
3 |
4 |
5 | 1) clone repository::
6 |
7 | $ git clone git@github.com:aio-libs/aiohttp_debugtoolbar.git
8 |
9 | 2) create virtual environment, for instance using *virtualenvwraper*::
10 |
11 | $ cd aiohttp_debugtoolbar
12 | $ mkvirtualenv -p `which python3` aiohttp_debugtoolbar
13 |
14 | 3) install ``aiohttp_jinja2``::
15 |
16 | $ pip install aiohttp_jinja2
17 |
18 | 4) install `aiohttp_debugtoolbar` and other dependencies::
19 |
20 | $ pip install -e .
21 |
22 | 5) run demo.py::
23 |
24 | $ python3 demo/demo.py
25 |
26 | Now you can play with `aiohttp_debugtoolbar` on http://127.0.0.1:9000
27 |
--------------------------------------------------------------------------------
/demo/aiohttp_debugtoolba_sceenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aio-libs/aiohttp-debugtoolbar/f83b8438c6f3ea48a7de54f3fc88c97914a62c2e/demo/aiohttp_debugtoolba_sceenshot.png
--------------------------------------------------------------------------------
/demo/demo.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from pathlib import Path
3 |
4 | import aiohttp_jinja2
5 | import jinja2
6 | from aiohttp import web
7 |
8 | import aiohttp_debugtoolbar
9 |
10 | PROJECT_ROOT = Path(__file__).parent
11 | TEMPLATE_DIR = PROJECT_ROOT / "templates"
12 |
13 |
14 | @aiohttp_jinja2.template("index.jinja2")
15 | async def index(request):
16 | log.info("Info logger for index page")
17 | log.debug("Debug logger for index page")
18 | log.critical("Critical logger for index page")
19 |
20 | return {"title": "Aiohttp Debugtoolbar"}
21 |
22 |
23 | async def exception(request):
24 | log.error("NotImplementedError exception handler")
25 | raise NotImplementedError
26 |
27 |
28 | @aiohttp_jinja2.template("ajax.jinja2")
29 | async def ajax(request):
30 | if request.method == "POST":
31 | log.info("Ajax POST request received")
32 | return web.json_response({"ajax": "success"})
33 |
34 |
35 | async def redirect(request):
36 | log.info("redirect handler")
37 | raise web.HTTPSeeOther(location="/")
38 |
39 |
40 | @aiohttp_jinja2.template("error.jinja2")
41 | async def jinja2_exception(request):
42 | return {"title": "Test jinja2 template exceptions"}
43 |
44 |
45 | async def init():
46 | PROJECT_ROOT = Path(__file__).parent
47 |
48 | app = web.Application()
49 | aiohttp_debugtoolbar.setup(app, intercept_exc="debug")
50 |
51 | loader = jinja2.FileSystemLoader([str(TEMPLATE_DIR)])
52 | aiohttp_jinja2.setup(app, loader=loader)
53 |
54 | routes = [
55 | web.get("/", index, name="index"),
56 | web.get("/redirect", redirect, name="redirect"),
57 | web.get("/exception", exception, name="exception"),
58 | web.get("/jinja2_exc", jinja2_exception, name="jinja2_exception"),
59 | web.get("/ajax", ajax, name="ajax"),
60 | web.post("/ajax", ajax, name="ajax"),
61 | web.static("/static", PROJECT_ROOT / "static"),
62 | ]
63 |
64 | app.add_routes(routes)
65 | return app
66 |
67 |
68 | if __name__ == "__main__":
69 | log = logging.getLogger(__file__)
70 | logging.basicConfig(level=logging.DEBUG)
71 |
72 | web.run_app(init(), host="127.0.0.1", port=9000)
73 |
--------------------------------------------------------------------------------
/demo/static/main.js:
--------------------------------------------------------------------------------
1 |
2 | require.config({
3 | paths: {
4 | "jquery": "jquery-1.7.2.min",
5 | }
6 | });
7 |
8 | require(["jquery"], function($) {
9 | $(function() {
10 | console.log('TEST REQUIREJS');
11 | });
12 | });
13 |
14 | define("main", function(){});
15 |
--------------------------------------------------------------------------------
/demo/templates/ajax.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |