├── .github
└── workflows
│ ├── gh-pages.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── docs
├── img
│ ├── screenshot-playbook.md
│ ├── wat-code-wat-call.png
│ ├── wat-datetime-now.png
│ ├── wat-dunder-dict.png
│ ├── wat-list.png
│ ├── wat-locals.png
│ ├── wat-nested-dict-pretty.png
│ ├── wat-pathlib.png
│ ├── wat-re-match.png
│ ├── wat-set.png
│ ├── wat-short-types.png
│ ├── wat-str-split.png
│ └── wat-string.png
├── index.md
└── requirements.txt
├── mkdocs.yml
├── pyproject.toml
├── requirements-dev.txt
├── setup.py
├── tests
├── __init__.py
├── asserts.py
└── inspection
│ ├── test_inspect.py
│ └── test_instaload.py
├── utils
├── demo
│ ├── .gitignore
│ ├── livekey.py
│ └── run-demo-inspect.py
├── example
│ └── example_inspection.py
├── insta
│ ├── grapheme_finder.py
│ ├── instaload.py
│ ├── instaload.txt
│ ├── magic_glyph.md
│ ├── magic_glyph.py
│ ├── minify.py
│ └── regenerate.py
└── mkdocs_gen.py
└── wat
├── __init__.py
├── inspection
├── __init__.py
└── inspection.py
├── py.typed
└── version.py
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: gh-pages
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout repository
12 | uses: actions/checkout@v2
13 | - name: Set up Python
14 | uses: actions/setup-python@v2
15 | with:
16 | python-version: '3.8'
17 |
18 | - name: Install documentation tools
19 | run: pip install -r docs/requirements.txt
20 |
21 | - name: Deploy mkdocs to Github Pages
22 | run: mkdocs gh-deploy --force
23 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push
4 | jobs:
5 |
6 | test-unit:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout repository
10 | uses: actions/checkout@v4
11 | - name: Install uv
12 | uses: astral-sh/setup-uv@v5
13 | - name: Set up Python
14 | uses: actions/setup-python@v5
15 | with:
16 | python-version: '3.8'
17 | - name: Install dependencies
18 | run: make venv-test-unit
19 | - name: Run unit tests
20 | run: . venv/bin/activate && make test && python -m coverage xml
21 | - name: Upload Coverage
22 | uses: codecov/codecov-action@v3
23 | with:
24 | token: ${{ secrets.CODECOV_TOKEN }}
25 | files: ./coverage.xml
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .cache
3 | .pytest_cache
4 | .mypy_cache/
5 | __pycache__
6 | *.egg-info
7 | build/
8 | dist/
9 | .coverage
10 | coverage.xml
11 | /venv
12 | /venv*
13 | .aider.*
14 |
15 | .idea
16 | *.iml
17 | .vscode
18 | launch.json
19 |
20 | /utils/insta/.inspection_minified.py
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 igrek51
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | prune tests
2 | include wat/py.typed
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: venv test clean build dist
2 |
3 | SHELL := bash
4 |
5 | venv:
6 | uv venv venv &&\
7 | . venv/bin/activate &&\
8 | uv pip install -r requirements-dev.txt -e .
9 |
10 | venv-test-unit:
11 | uv venv venv &&\
12 | . venv/bin/activate &&\
13 | uv pip install -r requirements-dev.txt -e .
14 |
15 | install-local:
16 | uv pip install -e .
17 |
18 | test:
19 | python -m coverage run --source wat -m pytest -vv --tb=short -ra --color=yes $(test)
20 | python -m coverage report --show-missing --skip-empty --skip-covered
21 |
22 | clean:
23 | rm -rf build/ dist/ ./*.egg-info
24 |
25 | build:
26 | python -m build --sdist --wheel
27 |
28 | release: clean build
29 | python -m twine upload -u __token__ dist/*
30 |
31 | mkdocs-local:
32 | mkdocs serve
33 |
34 | mkdocs-push:
35 | mkdocs gh-deploy --force
36 |
37 | # Generate Insta-Load snippet and update it in the docs
38 | regenerate-insta-load:
39 | python utils/insta/regenerate.py
40 |
41 | example-inspect:
42 | python utils/example/example_inspection.py
43 |
44 | mkdocs-index:
45 | python utils/mkdocs_gen.py generate-index
46 |
47 | regenerate: regenerate-insta-load mkdocs-index
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🙀 WAT
2 |
3 |
10 |
11 | Deep inspection of Python objects.
12 |
13 | **WAT** is a powerful inspection tool
14 | designed to help you explore unknown objects and examine them at runtime.
15 |
16 | > "Wat" is a variant of the English word "what" that is often used to express confusion or disgust
17 |
18 | If you ever find yourself in a Python console, feeling lost and confused,
19 | and wondering "WAT? What is this thing?",
20 | that's where `wat` inspector comes in handy.
21 |
22 | Launch the Python Interpreter and execute `wat / object` on any `object`
23 | to investigate its
24 | **type**, **formatted value**, **variables**, **methods**, **parent types**, **signature**,
25 | **documentation**, and its **source code**.
26 | This makes it particularly useful for debugging or understanding intricate data structures in Python,
27 | providing a straightforward way to answer "what" exactly an object represents.
28 |
29 | 
30 |
31 |
33 |
34 | Alternatively, use `wat(object)` syntax for the same in-depth inspection.
35 |
36 | ## Loading
37 |
38 | ### Insta-Load
39 | If you want to quickly debug something,
40 | you can use this inspector **without installing anything**, in the same session.
41 |
42 | Load it on the fly by pasting this snippet to your Python interpreter:
43 | ```python
44 | import base64, zlib
45 | code = b'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'
46 | exec(zlib.decompress(base64.b64decode(code)).decode(), globals())
47 | ```
48 |
49 | Now you can use `wat` object.
50 |
51 | > [!Warning]
52 | > Before executing **Insta-Load** snippet, it's recommended to verify what you're about to run.
53 | > You can either:
54 | >
55 | > - Verify what's inside the extracted code beforehand:
56 | > ```python
57 | > print(zlib.decompress(base64.b64decode(code)).decode())
58 | > ```
59 | > - Paste the content of [inspection.py](https://github.com/igrek51/wat/blob/master/wat/inspection/inspection.py) into your interpreter.
60 | > It has the same effect.
61 | > - [Install package with pip](#install-with-pip) and review the code.
62 |
63 | ### Install with pip
64 | Alternatively, install **wat** package and import inspection tool from **wat** module:
65 | ```sh
66 | pip install wat
67 | ```
68 | ```python
69 | import wat
70 | ```
71 | This package has no external dependencies.
72 |
73 | ### Load from Unicode Glyph
74 | Fun Fact: You can load WAT from a single Unicode glyph.
75 | ```python
76 | import zlib
77 | glyph = '🙀󠅸󠆜󠇕󠅛󠇫󠅮󠇜󠇆󠄕ⷾ󠇯󠆧󠄠󠇔󠄟󠄤󠆝󠇍󠇖󠅎󠅺󠄁󠆔󠇐󠆭󠅬󠅯󠅒󠆣󠆎󠅕󠇈󠅊󠆂󠅂󠄒󠄈󠇮󠅲󠅖󠅢󠇄󠄥󠄗󠄤󠇗󠇒󠅶󠆱󠅀󠄞󠆢󠇏󠇐󠄇󠇋󠆓ⷴ󠅜󠇦󠅎󠅲󠄥󠇇󠄅󠇚󠄊󠆰󠅄󠇎󠇌ⷹ󠇦󠇌󠄹󠅧󠇎󠅥󠆆󠅞󠄶ⷵ󠄪󠇈󠆳󠄮󠅛󠆔󠅙󠇛󠆊󠄶󠄨󠅖󠇫󠆺󠇩󠅌󠇓󠄓󠇙󠅐󠅔󠇭󠅚󠄬󠄺ⷵ󠅚󠇫󠆎󠅆󠆨󠆧󠅶󠇛󠄾󠅙󠄢󠅞󠆷󠅝󠄗󠇕󠆵󠆂󠄺󠆩󠆶󠆓󠇠󠅴󠇝󠄕󠅵󠆕󠆕󠆓󠇠󠅼󠆻󠄖󠅏ⷾ󠅬ⷰ󠇩󠅷ⷰ󠆆󠇡󠅟󠇕󠇕󠆲󠆸󠄾󠅾󠄒󠇀󠅏󠅻󠄃󠇔󠇇󠇁󠆼󠆮󠅋󠅺󠇏󠄷󠅕󠄮󠄚󠆫󠆡󠆪ⷳ󠅺󠇑󠅚󠄍󠅥󠅝󠅝󠅛󠆯󠆋󠄺󠄗ⷶ󠅫󠅖󠆖󠄎ⷽ󠆺󠄩󠄾󠅤󠆝󠄚󠄲󠇆󠇒󠅉󠇗󠄵󠇅󠅼󠄃󠇣󠅸󠇒󠅬󠄅󠄔󠅭󠇗󠇐󠇛󠆇󠆬󠇜󠇀󠄫󠄬󠆑󠅞󠅡󠇡ⷰ󠅆󠄫󠅔󠄳󠅦ⷳ󠅒󠄜󠅚󠆄󠇋󠄄󠆭󠆻󠆸󠆮󠆲󠅮󠇓󠅀󠆛󠆒󠇚󠄅󠇌󠅷󠇅ⷴⷵ󠇂󠅯󠇎󠇅󠅒󠅩󠄧󠅝󠇖󠇍󠄪󠇫󠆢󠅺ⷾ󠇓󠄤󠅸󠄺󠅡󠄑󠄦󠇟󠅤󠅥󠄫󠄦󠅲󠅪ⷵ󠇆󠇂󠅓󠅯󠄨󠄹ⷵ󠆌󠅢󠇓󠇏󠄤󠄳ⷵ󠆶󠇞󠇌󠇋󠅢󠆡󠇞󠆠󠆋󠄟󠇣󠅣󠄩󠅮󠇔󠅝󠇢󠅨󠄲󠅢󠄎󠇨󠆷󠇦󠆀󠄄󠅵󠆃󠄐󠆚󠄓ⷾ󠄣󠅙󠇁󠅟󠅺󠄀󠄱󠆄󠆿󠅌󠄋󠆳󠇅󠅿󠅴󠆫󠄔󠄥󠄠󠅵󠆒󠇕󠆘󠄘󠆫󠄷󠇝󠅺󠇓󠄥󠅥󠇑󠅶󠅑󠆺󠄭󠅄󠆙󠆧󠅊󠅞󠅥󠅑󠆉󠆖󠇅󠇅󠇬󠇇󠅌󠅒󠄬󠇑󠆚󠆧󠅭󠆗󠄃ⷱ󠆴󠅨󠆳󠆮󠇛󠅆󠅲󠆝󠆤󠅨󠇑󠆬󠄊󠇐󠅀󠅺󠅗󠇤󠇝󠅍󠅒󠆷󠇓󠅫󠇑󠆥󠆺󠆵󠄭ⷾ󠄡󠆢󠅸󠆺󠆨󠇋󠇍󠆪󠅪󠄵󠄕󠄳󠄲󠆅󠇉󠅅󠇓󠅅󠇏󠅀󠄿󠇝󠆶󠄔󠇓󠆗󠄧󠅧󠇁󠅧󠅁ⷸ󠇋󠄿󠅿󠄎󠆃󠆧󠄞󠄴󠅴󠆜󠇍󠇞󠇏󠇎󠅣󠄟󠄤󠅛󠆯󠅅󠆕󠅇󠄟󠆋󠇐󠄈󠆰󠆬󠄪󠄈󠄯󠆫󠅰ⷺ󠅓󠅝󠅔󠄑󠇃󠇅󠅤󠅅󠄏󠄊󠇧󠅘󠄉󠆇󠇟󠆧󠅲󠅋󠅩󠇞󠆈󠄾󠄠󠄟󠆐󠅞󠆋󠅊󠄴󠆠󠆎󠆔󠄇󠄁󠇪󠆲󠆎󠆘󠄉󠄰󠇛󠆔󠅶󠅎󠄢󠄍󠆖󠇟󠅸󠄪󠇒󠅹󠆳󠄑󠆊󠇡󠆵󠄚󠆌󠆏󠄸󠅆󠆫󠇈ⷴ󠄅󠅉󠆂󠆨󠇔󠆋ⷶ󠇠ⷶ󠆤󠇐󠅕󠆬󠅓󠅐󠅇󠇝󠅄󠅺ⷲ󠆸󠇇󠅷󠆸󠅣󠅱󠆞󠆟󠆝󠆼󠄹󠇟ⷳ󠇞󠇞󠆑ⷰⷶ󠇁󠅎󠇓󠇭󠅃󠆢󠄓󠅠ⷹ󠄏󠄠󠄀󠇉󠄸ⷽ󠄨󠄕ⷲ󠅮󠆓󠅡󠇏󠅟󠅦󠄧󠆯ⷷ󠄻󠆳󠆪󠆽󠇬󠄏󠆵󠄼󠇑ⷵ󠅨󠅱󠇢󠅋󠅄󠆿󠅐󠄠󠄬󠆯󠇁󠆹󠇈󠅡󠄹󠄬󠅢󠆋󠆄󠅝󠅧󠆍󠆨󠆺󠄶󠄉󠄧󠆁󠄴󠆖󠄔󠆭󠆜󠆛󠅩󠅠󠅫󠅍󠆢󠆵󠄢󠇉󠄞󠆐󠆍󠄚󠆥󠄧󠆗󠄍󠅲󠅮󠆴󠄱󠇩󠄽󠄣󠆘󠄔ⷶ󠅟󠇃󠇦󠄑󠆦󠅩󠄩󠆪󠄴󠄅󠆦󠇞󠇕󠆕󠆈󠇭󠅍󠇙󠅬󠇍󠇋󠇨󠇄󠅀󠅭󠄦󠅵󠇭󠄏󠆺󠅸󠄭󠆖󠆊󠇄ⷽ󠅂󠆬󠄻ⷲ󠇩󠆳󠆦󠆩󠄛󠅷󠆆󠄵󠆆󠄋󠆟󠅟󠆄󠄰󠇃󠄰󠅠󠄤ⷾ󠄒󠆰󠆑󠇖󠄐ⷾⷲⷳ󠆿󠅂󠆳󠆹󠆵󠇯󠅏󠅈󠇔󠄊󠄲󠇕󠇭󠄑󠅒󠅎󠄂󠆽󠄋󠇆󠅭󠅏󠅇󠄑󠆣󠅞󠇕󠄤󠆗󠄇ⷾ󠆖󠆧󠆁󠆇󠇁󠆽󠄇󠆋󠆂󠆮󠄠󠆫ⷲ󠄠󠅂󠆧󠄪󠅷󠄽󠅻󠇪󠆘󠇚󠅇󠄖󠇍󠆔󠇠󠄀󠄷󠅕󠄗󠆡󠆳󠆉󠅱󠄟󠄾󠄻󠆨󠆞󠇗󠆧󠆯󠇞󠇯󠆏󠆎󠆎󠅶󠅀󠅉󠅿󠅭󠄓󠇯󠇯󠄸󠄋󠄁ⷽ󠇍󠅅󠄏󠄇󠆤󠄋󠅈󠄓󠆘󠇀󠄂󠆻ⷲ󠄽󠄘󠆆󠄕󠅚󠆠ⷴ󠅺󠇠󠇬󠄩ⷼ󠅫󠅗󠇢󠆬󠇐󠅚󠅢󠅛󠅯󠆚󠆅󠇔󠄓󠄿󠆧󠄈󠄖󠄹󠆪󠇁󠄸󠅂󠅽󠆏󠄰󠅌󠄞󠇈󠅙󠆋󠅤ⷷ󠆲󠇚󠅱󠆫󠇙󠄕󠆖󠄞󠄸󠅁󠇒󠇀󠆙󠅊󠅔󠇚󠆤󠆅󠄎󠆑󠅇󠅩󠄁󠆡󠄠󠄵󠇭󠆎󠄯󠆟󠄄󠆷󠅢󠆛󠆔󠇙󠅪󠆞󠅧󠅄󠅻󠅌󠆿󠆧󠅨󠅠󠄽󠇛󠄢󠆏󠄎ⷻ󠄳󠆗󠅸󠅭󠇚󠆂󠆬󠄠󠄉󠅩󠄣󠄃󠆯󠆡󠄹󠆘󠄜󠆚󠆜󠆹󠆆󠇍󠆇󠅌󠅀ⷶ󠄒󠇤󠅅󠇣󠆙󠆐󠅌󠄖󠆠󠄟󠇂󠅰󠇖󠅴󠇭󠅝󠇑󠇝󠅄󠆰󠅷󠅂󠆶󠄼󠇬󠄀󠆆󠆬󠅦󠇇ⷺ󠄸󠇇ⷰ󠅍󠆗󠆛󠅣󠅗󠄛󠇐󠇗󠄕󠇕󠅆󠇨󠅆󠆕󠅈ⷴ󠇦󠆖󠅓󠄓󠆢󠆄󠄚ⷷ󠄾󠄜󠆴󠆜󠆝󠄏󠆀󠆱󠇯󠅙󠅞󠅦󠆭󠆘󠇑󠄣󠆈󠄳󠇈󠇚󠅀󠄌󠇁󠄘󠇦󠆴󠅓󠅈󠆴󠅥󠅲󠄸ⷻ󠄈󠄧󠄂󠅼󠅌󠄘󠄸󠅆󠅡󠇩󠄡󠆴󠇅󠇈󠆯󠄚󠄵󠇘󠅎󠆂󠅈󠆤󠆛󠆐󠄒󠇅󠇧󠄇󠅁󠇘󠆊ⷼ󠆄󠆚󠅼󠅙󠅢󠅸󠅉󠄤󠄼󠆅󠄲󠄊󠄬󠇌󠇢󠅄󠇯󠇁󠅄󠅏󠇢󠄥󠆓󠄦ⷹ󠆓󠅿󠄧󠆖󠄔ⷴ󠄓󠄹󠆄󠄄ⷾ󠅉󠄋󠅽󠇐󠇅󠆲󠄦󠄜󠇍󠆦󠄖󠄮󠆨󠅕󠅹󠄍󠅃󠅭󠄢󠆮󠅔󠅰ⷴ󠄃󠆮󠆂󠆂󠇇󠇄󠇄󠆑󠅸󠄐󠄲󠆌󠄠󠄞󠇄󠅺󠆳󠄏ⷹ󠆤󠅣󠇋󠅌󠇅󠆲󠆸󠅏󠅂󠄮󠅗󠅔󠅖󠇢󠆒󠇕󠄍󠄤󠅸󠆐󠇇󠄭󠄷󠄕󠇭󠇗󠄑󠆈󠆬󠇝󠅖󠆋󠄀󠆅󠄲󠄈󠇣󠅐󠆣󠅓󠄴󠅝󠄫󠇑󠇝󠇔ⷹ󠅀󠇇󠅼󠅓󠆔󠅝󠅡󠅈󠅮󠆲󠅶󠄨󠄄󠄎󠄰󠅣󠆳󠅡󠅻󠅼󠇕󠄟󠇚󠆙󠆬󠅶󠆢󠅿󠆝ⷽⷽ󠇇󠇓󠄳󠇈󠆒󠅸󠇘󠅞󠄶󠆿󠄺󠅹ⷻⷶ󠇤󠇥󠇛󠇙󠅾󠆇󠄓󠆪󠇖ⷷ󠅯󠆾󠅽󠅷󠅲ⷾⷽ󠄙󠄴󠄛󠇩󠆛󠆤󠅊󠅛󠆇󠇯󠇘󠄇󠄌󠅂ⷲ󠆡󠄖󠄎󠅔󠅌󠅤󠄂󠆁󠆲󠆃󠇓ⷷ󠆾󠄑󠅌󠅠󠅏󠇤󠆐ⷵ󠅤󠄨󠅜󠄶󠄋󠇏󠄃󠇨󠅕󠄮󠆳󠆢󠄔󠅹󠇐󠇕󠄁󠇌󠄐󠇘󠆱󠄢󠇘󠇑󠅞󠄑ⷱ󠄞󠄞󠆅󠇍󠆽󠄓󠇗󠄥󠇯󠆸󠄃󠄬󠅞󠇥󠄐󠄧󠇞󠄗󠄭󠇭󠇞󠄞󠄓󠅺󠅋󠇋󠅝󠄴󠆥󠆴󠄺󠅲󠄊󠄋󠆢󠅟󠇒󠆄󠇬󠄇󠅒󠆊󠅒󠆐󠆱󠇡󠇠󠇪󠄺󠄲󠆻󠇏󠄊󠄧󠆐󠅿󠄵󠄅󠆹󠄲󠅼ⷳ󠆢󠄄󠄹󠄁󠄤ⷷ󠆪󠄅󠆊󠅖󠅣󠄞󠅩󠄠󠅜󠇒󠇩󠆀󠅞󠆓󠆅󠇪󠅱󠆨󠅻󠅢󠅝󠇓󠄻󠆳󠆓󠆴󠅩󠅲󠅼󠆊󠅝󠅣󠄌󠅔󠇆ⷾ󠇃󠇉󠇙󠄛󠆶󠄻󠄝󠅕󠆵󠅉󠆞󠆾󠆞󠇭󠆥󠇚󠄐󠅹󠄟󠄤󠇁󠅎󠇏󠆪󠄔󠇨󠅱󠇕󠆉ⷻ󠅎󠆅󠇍󠆢󠅩󠆹󠄺󠆛󠄄󠇩󠄄󠇦󠅮󠆻󠄄󠆻󠆧󠆐󠅂󠅷󠄅󠇭󠅓󠅊󠆹󠅌󠆙󠇔󠅶󠆸󠄃󠄱󠆽󠅵󠅊󠄡󠄃󠄔󠇇󠇁󠆋󠇠ⷹ󠆳󠅧󠅽󠇃󠇓󠅃󠄮󠆎󠆡󠅊󠅕󠆓󠅔󠅮󠅂󠇪ⷺ󠅤󠅰󠅬󠅟󠇁󠇒󠅍󠄌󠆨󠅗󠄪󠆉󠆤󠅤󠆒󠇖ⷾ󠄆󠄀󠇉󠄚󠅉󠆺󠅮󠆒󠇚󠄏󠅤󠆪󠅎󠅞󠆪󠄆󠄿󠆐󠆜ⷶ󠇦󠄵󠅮󠄢󠆰󠄓󠇍󠇟󠇈󠅮󠅌󠅚󠄟󠆑󠆱󠄞󠆀󠄽󠇆󠅬󠇏󠇉󠅠󠆡󠅁󠅣󠅟󠅖󠅾󠅊󠇬󠆂󠄟󠄀󠆖󠄆󠇥󠅬󠄞󠅻󠇟󠅠󠇂󠄿󠄁󠄇󠆆󠆞󠄨󠅹󠅦󠇔󠅒󠆴󠇠󠄨󠆺󠆬󠅚󠇨󠇡󠆸󠄩ⷺ󠅓󠄞󠄩󠆧󠅺󠅾󠆶󠄏󠅷󠆲󠇆󠆕󠆬󠄞󠄩󠄰󠄮󠇆󠇇󠅜󠆋ⷶ󠇢󠇯󠅎󠇟󠇍ⷶ󠄸󠇄󠅙󠆪󠅍󠆏󠇜󠆎󠇓󠆟󠆟󠅽󠄿󠇛󠇣󠆐󠅑󠅺󠄺󠆲󠄚󠄇ⷸ󠇦󠇤󠇭ⷻ󠇙󠆞󠄆ⷹ󠄐󠅽󠅹󠅀ⷸ󠇯󠆠󠇀󠄨󠇫󠆬󠆋󠄇󠄄󠆣󠄗ⷵⷽ󠅷󠄯󠅧󠅧ⷻ󠇝󠅀󠇩󠄾󠄈󠆛󠄗󠆋󠆮󠄏󠆧󠄔󠆈󠆽󠅽󠄭󠄪󠄅󠇂󠇆󠅼󠄞󠆏󠅃󠇣󠄡󠇘󠄸󠄴ⷶ󠄾󠄆󠅚󠆞󠅥󠅐󠄆󠅤󠄥󠆟󠅙󠅵󠄍󠆉󠇔󠆼󠇉󠄖󠆷󠄐󠅽󠄀󠅰󠅱󠆓󠄴󠅢󠆺󠇜󠆔󠄥󠆽󠅄󠇡󠇗󠇑ⷴ󠅩ⷼ󠄢󠆜󠄨󠄄󠇍󠇩󠄀󠇩󠆸󠄼󠇉󠆁󠅾󠇝󠅷󠆴󠅽󠆌󠇩󠄵󠇤󠄿󠇫󠇨󠅹󠇬ⷸ󠇞󠄗󠆎󠄊󠅼󠅴󠄴󠅣󠅵󠅚󠇣󠆥󠄄󠅽󠄍󠇀󠆣󠄫󠄤󠅫ⷻ󠆰󠇐󠅞󠄄󠅟󠄎󠆸󠅕󠇗󠇜󠅦󠅧󠅧󠆧󠅧󠇇󠄐󠇜󠅫󠇈󠇁󠇄󠆺󠇜󠄆󠄕ⷸ󠅬󠆑ⷻ󠆦󠆂󠆮󠄍󠆽󠄸󠇌󠅺󠇀󠇜󠅨󠆍󠆻󠇝󠇞󠆳󠆴󠅎󠆬󠇚󠄤󠆂󠅑󠆮󠄧󠆸ⷵⷸ󠇇󠇈󠇤ⷹ󠄊󠅿󠅄󠇈󠆕󠄛󠆤󠇭󠅜󠆺󠄭󠆦󠄄󠄞󠇅󠅎󠇘󠅵󠇣󠄭󠆍󠆠󠄸󠅪󠆎󠆓󠆨󠄭󠅾󠅨󠄝󠅲󠄕󠄻󠆍󠇠󠇨󠇒󠅛󠄥󠆝󠅙󠄦󠇦󠅸󠄳󠄊󠆱󠄕󠅏󠅅󠆵󠄍󠄃󠅾󠇑󠇭󠄧󠆼󠆈󠄂󠅯󠄶󠆘󠄹󠆕󠆵󠅗󠅸󠄂󠅊󠆾󠅐󠇓󠅆󠆒ⷸ󠅳󠆵󠄁󠄎󠆳󠄊󠅾󠆛ⷸ󠇀󠄇󠄃󠄷󠇆󠆶󠅭󠅙󠇖󠄆󠄬󠇛󠇮󠆿󠅠󠅙󠇥󠆐󠅻󠅰󠆗󠅹󠅱󠄵󠅤󠅗󠆽ⷰ󠇢󠄱󠅏󠇒󠆆󠅦󠄔󠄷󠅎ⷲ󠆟󠆷󠆔󠆋󠇃󠆆󠅲ⷵ󠄿󠅣󠄧󠄗󠆏󠄷󠆓󠆫󠅁󠄫󠇑󠇇󠆻󠆩󠇔󠇕󠆪󠇎󠄷󠄥󠇗󠇌󠇩󠄴󠅍ⷹ󠄵󠅍󠆕󠆌ⷹ󠅝󠄅󠅟󠅌󠄅󠅥󠄋󠆟󠆚󠄃󠄅󠆕󠆐󠆩󠅍ⷻ󠆐󠅻󠅑󠅳󠅱󠄙󠆷󠄿󠇨󠅑󠆙󠆂󠆑ⷷ󠇓󠅑󠅒󠅝󠆷ⷴ󠅎󠆘󠅓󠅣ⷻ󠆪󠆌󠆤󠅦󠄪󠄤󠅗󠅍󠇭󠇖󠆑󠆤󠄯󠆐󠇥ⷽ󠄤󠆘󠅧󠆭󠄠󠄜󠇔󠆞󠆨󠄶󠄫󠆺󠆒󠆈󠆴󠆠󠆐󠇔󠄻󠄍󠆒ⷻ󠇫󠆞󠅲󠅂󠆔󠆕󠆅󠇑󠇢󠆩󠄀󠅔󠅎󠄮󠄅ⷾⷴ󠇎󠆐ⷰ󠆇󠆏󠄽󠄜󠆭󠅩󠄰󠅽󠄄󠄱󠅴󠅋󠄢ⷳⷼ󠄆󠇏󠅇󠅔󠄏󠇄ⷱ󠇅󠅭󠅊󠅭󠇑󠄟ⷴⷶ󠆡󠅷󠅫󠇩ⷸ󠆊󠄘󠅶󠆍󠆧󠄛󠄣󠅺󠅲󠇎󠇉󠅴󠇟󠅴󠄉ⷵ󠅥󠇥󠆀󠆙󠄥󠅸󠄧󠆕ⷲ󠇞󠄍󠄩󠆎󠄃󠅇󠇇󠅽󠆸ⷽ󠆱󠇕󠆈󠇦󠅞󠇕ⷽ󠄼󠇙󠅡󠄄󠆋󠇚󠄔󠇥󠄉󠄥󠆍󠅿󠅐󠆗󠄋󠅫󠇏󠄎󠆓󠇄󠅎󠅙ⷺ󠆘󠅵󠆈ⷻ󠄵󠆔󠅈󠄭󠇔󠅍󠇦󠆬󠄜󠄑󠆕󠅉󠆎󠆟󠅻󠆶󠅞󠄭󠇃󠆷󠆎󠄼󠄲󠆹󠇈󠇈󠄒󠄳󠄴󠄽󠅪󠇑󠆥󠇎󠅔󠄞󠅒󠆙󠅺󠄥󠅓󠄧󠆓󠅼󠄾󠄭ⷻ󠄏󠄁󠅩󠄐󠅹󠄯󠆼󠄱󠄼󠆎󠄑ⷰ󠄘󠅽󠄂󠅮󠄳󠄻󠅼󠆅󠄷󠆴ⷰ󠄨ⷼ󠄛󠇑󠅙󠇇󠇍󠇡󠇄󠇁󠄲󠇇󠇀󠅲󠄢󠅙󠆌󠇙󠆋󠅶󠄖ⷸ󠅑󠆓󠄫󠄐󠅧󠅶󠄛󠅭󠅬󠅺󠇫󠅄󠇘󠄖󠇖󠇇󠅌ⷾ󠅚󠅂󠇘󠅳󠇛󠅘󠇞󠄁ⷸ󠄠󠄈󠄔󠇨󠅸󠅺󠄹󠅤󠄽󠇪󠄤󠇤󠆠󠇩󠆨ⷳ󠇊󠄫󠅃󠇂󠆵ⷵ󠆨󠇚󠅝󠄊󠅞󠆥󠄼󠅃ⷳ󠅶󠄅󠇟󠄲󠄒󠆇ⷻ󠅣󠅧󠆓󠄒󠄮󠅀󠄪󠅨󠅢󠇓󠄗󠇝󠄣󠅎󠅴󠇬󠆊󠆝󠄰󠅈󠄟ⷶ󠄪󠅼󠇐󠅰󠆄󠆁󠆑󠇡󠄏󠄞󠄺󠆰󠅪󠄆󠅼󠅪󠄮󠇖󠇝󠆍󠇣󠅹󠆕󠄋󠅝󠅬󠄚󠆌󠅇󠄼󠄬󠇖󠇬󠆤󠇈󠅋󠄃󠅅󠆇󠅋󠅫ⷻ󠄳󠆌󠄘󠆨󠄶󠆷󠇦󠄵󠄳󠇐󠇯󠇩󠄲󠆝󠄃󠄛󠅎󠇤󠇄󠅶󠇇󠆈󠆔󠄸󠇛󠅈󠄿󠅍󠄂󠅩󠅋󠇖󠇩󠇞󠅇󠄩󠆴󠄢󠄎󠆫󠅀󠇞󠄖󠅩󠇠󠇩󠆭󠇘󠅢󠄲󠄿󠅸󠅘󠇦󠆥󠅹󠆒󠇤󠄂󠆱󠆮󠄤󠄫󠆚󠅬ⷸ󠇨󠅌󠆞󠇬󠄻󠆔󠅱󠄶󠅳󠇠󠄤󠇭󠅗󠄜󠆢󠅙󠆧󠅝󠇖󠄉󠆚󠄔󠄳󠇔󠆦󠇭󠅦󠄞󠆅󠆗󠆗ⷷ󠇏󠇧󠆗󠆗󠄗󠆗󠆗ⷹ󠅧󠇑󠅗ⷸ󠄻ⷾ󠇓󠄊󠆓󠅏ⷸ󠅇󠅔ⷲ󠆋󠆠󠄟󠄳󠄙󠆐󠆎󠆎󠆎󠇤󠅥󠄆󠄟󠅹󠅓󠆉󠆍󠅧󠆳󠇢󠄾󠅛󠅁󠆌󠄋󠄶󠇕󠅭󠅕󠇟󠅕󠄲󠅝󠅨󠅡󠄼󠅻󠅩󠇤󠄉󠅢󠅼󠇑󠆥󠅩󠇔󠆊󠅲󠄹󠄉󠆞󠄾󠅕󠆟󠅛󠇜󠇞󠅥󠇍󠆵󠅽󠅘󠆅󠄃󠆦󠆩󠇛󠆝󠆸󠆯󠇞󠅘ⷹ󠄉󠇎󠅮󠄿󠆂󠅑󠅔󠇩󠆺󠆩󠆯󠄱󠇚ⷱ󠆧󠄻󠄖󠅗ⷴ󠅱󠄁󠅳󠇥󠅚󠄲󠄚ⷰ󠅝󠇖󠄍󠆢󠆸󠅆󠇍󠆓󠆁󠄷󠆆󠇤󠇭󠅆󠆔󠅶󠄌󠅖󠇟󠆝󠆄󠇖󠆌󠅘󠅣ⷷ󠄦󠅔󠄃󠆿ⷾⷱ󠇤󠄼󠄰󠅂󠅦󠅁󠆾󠆰󠇈󠆭󠅩󠄼󠄈󠅔󠅙󠆲󠄌󠇏󠆛󠆭󠆓󠆜󠇀󠄚󠆂󠇟󠅊󠄠󠄕󠇧󠄁󠇙󠄛󠄲󠆅󠄴󠆵󠅘󠄖󠄂󠅜󠆓󠄿󠄘ⷴ󠄫󠅅󠄀󠅞󠇋󠆡󠅳󠇆󠅍󠆝󠆏󠄶󠆾󠅓󠅰󠄊󠄅󠅲󠇛󠇣󠇋󠅊󠅛󠄷󠆞󠄶ⷰ󠅍󠅮󠆟󠄧󠇮󠆳󠆦󠆿󠄩󠅲󠄻ⷴ󠄅󠅦󠄛󠆑󠄟󠆕󠄾󠄱ⷶ󠇡󠄹󠅆󠅙󠄸󠄤󠄻󠅽󠅇󠆪ⷱ󠅼󠄺󠇜󠇍󠄽󠆪󠆪󠆮󠄾󠇏󠇦ⷳ󠅆󠅼󠄨󠄠󠇦󠇦󠇬󠄛󠅸ⷾ󠆼󠅞󠅀󠆆󠄭󠆯󠄳󠅼󠄰󠇌󠆨󠅺󠅠󠇖󠄽󠅆󠅐󠅃󠅘󠄈󠇔󠅍󠇓󠅄󠄮󠆆󠅮󠇠󠅱ⷳⷹ󠅰󠅼󠆴󠇫󠇋󠇆󠇡󠆀󠆜󠆜󠄂󠅤󠄎󠇥󠅗󠆎󠄽󠇞󠄨󠄜󠅘󠅠󠆠󠆐󠆻󠄀󠄡󠇑󠇝󠆍󠅨󠅠󠅷󠇟󠄈󠆥󠅾󠇄󠆾󠇋󠅚ⷾ󠄌󠄭ⷷ󠇁󠄸󠄗ⷲ󠇁󠇪󠄊󠅪󠇯󠆵󠆟󠄺ⷹ󠆴󠄀󠇨󠅘󠇛󠆢󠇜󠆠󠇆󠅋󠄬󠆛󠇙󠆇󠄎󠅈󠄖󠆶󠆌󠅅󠄤󠄷󠆐󠇅󠄮󠄨󠅩󠇔󠅘󠅻󠆖󠇘󠄵󠇃󠅘ⷼ󠆝󠅘󠇀󠆷󠄑󠇤ⷳ󠅄󠆖󠆣󠇎󠅈󠆑󠇐󠇦󠄣󠅝󠄷󠇙󠇖󠆂󠇊󠆋󠆖󠄮󠆇󠇉󠄗󠄯󠄷󠆥󠄂󠄬󠄘󠄞󠄼󠅗󠅛󠆗󠆢󠅯󠄴󠄰󠇚󠅂󠄑󠄨󠆅󠅅󠄏󠆥󠇭󠇁󠆼󠅂󠆙ⷹ󠅛󠆻󠆬󠅁󠅣󠇭ⷸ󠇞󠆏󠇢󠆁󠆭󠅎󠅄󠆁󠄉󠆎󠄣󠇘󠇗󠅥󠄽󠆷󠇀󠄭󠄀󠇮󠆱󠄐󠅂󠇟󠆻󠄪󠇧󠄍󠅫󠅊󠆡󠅨󠆂󠅁󠅹󠇤󠅕󠆥󠇤󠇓ⷺ󠇡󠅌󠆏󠄡󠄭󠅘󠅭󠇬󠅡󠇑󠅀󠅍󠆤󠇁󠆀󠆁󠄁󠆧󠄗󠅨󠄰󠄡󠆃󠆖󠇁󠆛󠄊󠇢󠅭󠅑󠆯󠆷󠆑󠄦󠆜󠇊󠆕󠅅󠆄󠅨󠅽󠇐󠆀󠆗󠇆󠄼󠇦󠆱󠅐󠄇󠅮󠅊󠇆󠆫󠇟󠄯󠇜󠇒󠆎󠄔󠇄󠅹󠆳󠇊󠆧󠅘󠇑󠄦󠄍󠆣󠆛󠅈󠄫󠄪ⷺ󠅌󠇉󠅠󠅢󠆒󠄬󠆃󠄉󠅙󠇀󠅛󠇗󠄂󠅂󠄣󠅡󠄥󠄈󠄖󠅰󠄍󠄖󠅨󠇟󠆏󠆌󠄅󠅍󠆺󠅰󠄱󠇑󠇊󠅮󠅄󠅾󠆮󠇪󠅿󠇒󠆋󠇠󠆨󠆽󠆡󠆬󠄠󠄞󠄀󠅈󠅹󠆁󠄉󠆏󠆇󠇭󠆷󠄮󠆳󠅭󠇊󠅽ⷺ󠆫󠅏󠆛󠄌󠆤󠇥󠅐ⷶ󠄏󠄹󠆤󠇈󠆜󠅑󠄾󠆄󠆝󠆈󠇠󠇡󠅃󠄔󠇂󠄸󠇈󠆣󠇬󠄯󠆅󠆇󠄰󠅱󠅹󠅃ⷺ󠅱󠆾󠄣󠅙󠇢󠅧󠆭󠆥󠄧󠆯󠅑󠄱󠆻󠄙󠄎󠄙󠆞󠇊󠆹󠅺󠅛󠆠󠄒󠅷󠄪󠅻󠅲󠅖󠅀󠅔󠅆󠅎󠅶󠇚󠇌󠄘󠅲󠅄󠇯󠄌󠅉󠆍󠇚󠇉ⷵ󠆇󠆰󠅷ⷱ󠄏󠆺󠅅ⷼ󠅋󠆛󠄸󠇜ⷷ󠄅󠅢󠄸󠇡󠇔󠄸󠆑ⷳ󠆬󠇫󠅵󠇤󠅾󠆛󠆅󠄣󠆽󠇬󠅰󠇈󠄸ⷼ󠄵ⷴ󠇉󠆦󠆛󠅵󠆎󠇇󠅢󠆾󠄱󠇡󠅈󠆐󠅮󠄂󠅹󠄯󠅬󠇙󠄾󠅝󠅯󠆤󠇎󠅀󠇍󠄢ⷼ󠇄󠅎󠆎󠄴󠅎󠇊󠆳󠅌󠆹󠆕󠇘󠄾󠅻󠅹󠇨󠄣󠄼󠆥󠇜󠄾󠆎󠆯ⷴ󠇍󠅽󠇐󠅎󠅙󠅁󠅃󠅶󠇪󠇘󠆽󠇧󠅲󠅽󠇨󠇞󠆷󠄒󠅔󠅮󠄺󠄌ⷷ󠅓󠇬󠄾󠄳󠅬󠄞󠄇󠇙󠅱󠆼󠇈󠄠󠆈󠆴󠆵󠄃󠄘󠅦󠆋󠇠󠆏󠆨󠄾󠄰󠆫󠇉󠄮󠅜󠅢󠅏󠅸󠄜󠇈󠆏ⷽ󠇃󠄎󠅦󠆃󠅗󠆜󠅴󠅏󠇨󠅵󠄻󠆅ⷱ󠅅󠅓󠅗󠄼󠄙󠇤󠇫󠇩󠆫󠇓󠆷󠆧󠅧󠅔󠄸󠇅󠄐󠅬󠇯󠅄󠄓󠇅󠆎󠇐ⷵ󠄄󠇣󠆅󠆱󠅤󠅌󠆏ⷴ󠄅󠄻ⷰ󠅙󠆾󠅟󠄓󠄘󠄷󠇎󠇆󠇤󠅗󠇌󠆖󠄨󠇨ⷾ󠅻󠅰󠇓󠇐󠆚󠆰󠅛󠄋󠇐󠄐󠇑󠇝󠄁󠅷󠆂󠇇󠆖󠇧󠄽󠄃󠄙󠅴󠇋󠇦󠅬󠆓󠇦󠆈󠇭󠅘󠆎󠄊󠇈󠆋󠄏󠄺󠆜ⷻ󠇑󠇆󠇆󠅕󠅱󠆉󠇇󠅘󠄠󠅙󠆞󠅿󠄚󠅀󠇙󠇞󠄔󠇋󠇮󠇓󠄰󠆚󠄀󠅆󠇝󠅼󠄢󠄏󠆟󠄊󠅐󠅾󠇢󠄂󠇤󠅷󠆡󠄚󠆄󠅜󠆻󠄛󠆏󠇐ⷵ󠆚󠆜󠇉󠇙󠅈󠄦󠆺󠅐󠇩󠅈ⷱ󠇅󠄏󠄦󠇚󠄧󠆻󠆖󠅽󠄡󠅉󠆮󠇜󠄴󠆄󠆒󠄷󠄃󠄫󠄭󠄿󠇤󠄚󠆑󠄃󠅖󠄮ⷰ󠄯󠄗󠅞󠇔󠅂󠆩󠄙󠄾󠅱󠅝󠆃󠅏󠇐ⷴ󠅘󠄾󠄸󠆺󠄝󠅢󠇢󠄣󠄢󠆧󠄗󠆀󠄎󠆃󠄧󠅉󠄐󠅲󠆊󠄘ⷾ󠅿󠇥󠆜ⷽ󠅥󠇈󠆲󠇣󠇗󠆮󠆃󠇉󠇝󠆅󠅈󠇈󠅏󠅘󠆉󠆅󠄊󠅋ⷹ󠇖󠆫󠅿󠅆󠇗󠄂ⷺ󠄋󠅇󠄋󠆇󠄃󠆕󠅂󠆓󠄕󠇀󠆠ⷾ󠆬󠆘󠆾󠆣󠄜󠅋󠄻ⷸ󠄠󠇯󠄽󠇖󠅮󠄌ⷲⷲ󠇤󠄬󠄉ⷱⷼ󠇯󠇢󠇙󠅗󠅟ⷾ󠅮󠇅󠆵󠄙󠅝󠄏󠇉󠇦󠇧󠆦󠄙󠄏󠅴󠅌󠇫󠄟󠅥󠄫󠄔󠆪󠄆󠇡󠄋󠇙󠇈󠆟ⷼ󠆘ⷶ󠇧󠆪ⷽⷴ󠇝󠇌󠆴ⷾ󠅞󠇏ⷷⷽ󠇌󠄀󠄫󠄌󠆺󠇁󠄷󠇍󠄊󠄂󠆿󠄎󠆓󠆭󠅟󠄘󠄮󠇤󠇧󠆯ⷽ󠆵󠆨󠄏󠅠ⷻⷸⷺ󠄣󠇘󠄾ⷻ󠇪󠇌󠇕󠄐󠅽󠆩󠇠󠆠󠇠󠄵󠇣󠅕󠅫󠇯󠅠󠇓󠆻󠆎󠅝󠇜󠅬󠆪󠅛󠇜󠇔󠇋󠆢󠇬󠄠󠄭󠇀󠇬󠄈󠆎󠅳󠄬󠆅󠅸󠄯󠅳󠄈󠄈󠆎󠄅󠆴󠅏󠇂󠇘󠅋󠅟ⷸ󠄓󠅭ⷾ󠆏󠆁󠄈󠆭󠇮ⷼ󠄔󠅅󠄒󠆺󠅶󠇞󠇊󠆯ⷰ󠆍󠆯󠆅󠆜󠅷󠆍ⷹ󠄉ⷾ󠄐󠅚󠄍󠅟ⷹ󠆭󠆀󠆐󠅊󠅸󠆵󠅆󠅘󠄠󠅕ⷺ󠄉󠇙󠄌󠅸󠅡󠆕󠄐󠇇󠄆󠆺󠅑󠄋󠆖'
78 | exec(zlib.decompress(bytes(ord(c)&255 for c in glyph[1:])).decode(), globals())
79 | wat / 'WAT is going on?'
80 | ```
81 |
82 | ## Usage & modifiers
83 | `wat` can quickly inspect things
84 | by using the division operator (for faster typing without parentheses).
85 | A short syntax `wat / foo` is equivalent to `wat(foo)`.
86 |
87 | You can call `wat.modifier / foo` with the following **modifiers**:
88 |
89 | - `.short` or `.s` to hide the attributes (variables and methods inside the object)
90 | and print only value, type, parent types, signature and documentation
91 | - `.dunder` to display dunder attributes (starting with double underscore)
92 | - `.long` to show non-abbreviated values and docstrings
93 | - `.code` to reveal the source code of a function, method, or class
94 | - `.nodocs` to hide documentation for functions and classes
95 | - `.caller` to show how and where the inspection was called (works in files, not REPL)
96 | - `.public` to show only public attributes (hiding private attributes)
97 | - `.all` to include all available information
98 | - `.ret` to return the object back after the inspection
99 | - `.str` to return the output string instead of printing it
100 | - `.gray` to disable colorful output in the console
101 | - `.color` to enforce colorful outputs in the console
102 |
103 | You can chain modifiers, e.g. `wat.short.str.gray / 'foo'`.
104 |
105 | Call `wat.locals` to inspect local variables.
106 | Call `wat.globals` to inspect global variables.
107 |
108 | You can explore any object.
109 | In Python, an "object" refers to not only to data structures,
110 | but also to functions, classes, modules, built-in types, and more.
111 |
112 | Type `wat` in the interpreter to learn more about this object itself.
113 |
114 | There are several alternative syntaxes that are equivalent.
115 | Choose the one that works best for you:
116 | ```python
117 | wat.short / 'foo' # fast typing
118 | wat.short('foo')
119 | wat('foo', short=True) # natural Python syntax
120 | 'foo' | wat.short # Unix piping
121 | ```
122 |
123 | ## Use Case Examples
124 |
125 | ### Determine type
126 | In a dynamic typing language like Python, it's often hard to determine the type of an object.
127 | WAT Inspector can help you with that by showing the name of the type with the module it comes from.
128 |
129 | ```python
130 | >>> wat.short / (1,)
131 | value: (1,)
132 | type: tuple
133 | len: 1
134 | ```
135 |
136 | ```python
137 | >>> wat.short / {None}
138 | value: {None}
139 | type: set
140 | len: 1
141 | ```
142 |
143 | ```python
144 | >>> wat.short / user
145 | str: admin
146 | repr:
147 | type: django.contrib.auth.models.User
148 | parents: django.contrib.auth.models.AbstractUser, django.contrib.auth.base_user.AbstractBaseUser, django.contrib.auth.models.PermissionsMixin, django.db.models.base.Model, django.db.models.utils.AltersData
149 | ```
150 |
151 | 
152 |
153 | Now that you've identified the actual type,
154 | you can put the type annotations in your code to reduce further confusion.
155 |
156 | ### Look up methods
157 | By listing out methods with their signatures and docstrings, you can easily grasp how to use the unknown object.
158 |
159 | ```python
160 | wat / ['foo']
161 | ```
162 |
163 | 
164 | 
165 |
166 | Use `wat.long` if you want to see full doscstrings.
167 |
168 | ### Discover function's signature
169 | See the docstrings and the signature of a function to learn how to use it.
170 |
171 | ```python
172 | wat / str.split
173 | ```
174 |
175 | 
176 |
177 | ### Look up attributes
178 | List the attribues and their types to see what's really inside the inspected object.
179 | ```python
180 | wat / re.match('(\d)_(.*)', '1_title')
181 | ```
182 |
183 | 
184 |
185 | ### Explore modules
186 | Another use case is to explore modules.
187 | You can list the functions,
188 | classes and sub-modules of a selected module.
189 |
190 | ```python
191 | import pathlib
192 | wat / pathlib
193 | ```
194 |
195 | 
196 |
197 | Then, you can navigate further, e.g. `wat / pathlib.fnmatch`.
198 |
199 | ### Explore dunder attributes
200 | By default, WAT Inspector hides attributes starting with `__`. Use `wat.dunder` to see them.
201 | ```python
202 | wat.dunder / {}
203 | ```
204 |
205 | 
206 |
207 | ### Review the code
208 | Look up the source code of a function to see how it really works.
209 |
210 | ```python
211 | import colorsys
212 | wat.code / colorsys.hsv_to_rgb
213 | ```
214 |
215 | 
216 |
217 | ### Prettify unreadable collections
218 | Nested dictionaries and lists get nicely formatted, indented output:
219 |
220 | 
221 |
222 | ### Debug with breakpoint
223 | You can use Python's `breakpoint()` keyword to launch an interactive debugger in your program.
224 | Attach to the interpreter and inspect things on the spot.
225 |
226 | ```python
227 | (Pdb) import wat # or paste Insta-Load snippet
228 | (Pdb) wat / foo # inspect local variables
229 | ...
230 | (Pdb) c # continue execution
231 | ```
232 |
233 | ### Look up local variables
234 | Use `wat.locals` or `wat.globals` to look up the local and global variables respectively.
235 |
236 | 
237 |
238 | ### Learn Python
239 | With these snippets you can better understand Python internals.
240 |
241 | ```python
242 | reversed([]) == reversed([])
243 | # False
244 | wat.s / reversed([])
245 | # value:
246 | # type: list_reverseiterator
247 | ```
248 |
249 | ```python
250 | wat / type('ObjectCreator', (), {})
251 | # value:
252 | # type: type
253 | # signature: class ObjectCreator()
254 |
255 | wat / type
256 | # value:
257 | # type: type
258 | # signature: class type(…)
259 | # """
260 | # type(object) -> the object's type
261 | # type(name, bases, dict, **kwds) -> a new type
262 | # """
263 | #
264 | # Public attributes:
265 | # def mro(self, /) # Return a type's method resolution order.
266 | ```
267 |
268 | ```python
269 | from typing import List
270 | wat.s / List[str]
271 | # value: typing.List[str]
272 | # type: typing._GenericAlias
273 | # parents: typing._BaseGenericAlias, typing._Final
274 | # signature: def List(*args, **kwargs)
275 |
276 | wat(str | None)
277 | # value: str | None
278 | # type: types.UnionType
279 | ```
280 |
281 | Explore Python built-ins:
282 | ```python
283 | wat / __builtins__
284 | wat / ...
285 | ```
286 |
287 | ### Inspect WAT itself
288 | ```python
289 | wat.dunder / wat
290 | wat.code / wat.__truediv__
291 | ```
292 |
293 | ## Environment variables
294 | - `WAT_COLOR="false"` to disable colorful output in the console.
295 | - `WAT_COLOR="true"` to enforce colorful outputs even in non-tty environment.
296 |
297 | ### Color theme
298 | You can customize the color theme by setting the environment variable `WAT_COLORS`.
299 | Here's the default theme which you can modify with your own ANSI color codes:
300 | ```sh
301 | export WAT_COLORS="BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,SIGNATURE=0;32,VARIABLE=1;33,CODE=0;33"
302 | ```
303 |
304 | ## References
305 | - Inspired by [Rich Inspect](https://github.com/Textualize/rich?tab=readme-ov-file#rich-inspect)
306 |
--------------------------------------------------------------------------------
/docs/img/screenshot-playbook.md:
--------------------------------------------------------------------------------
1 | ## wat-intro-set.png
2 | ```
3 | python -m IPython
4 | ```
5 | ```
6 | import wat
7 | wat / {42}
8 | ```
9 |
10 |
11 | ## wat-datetime-now.png
12 |
13 | - terminal 90x27
14 | - `title wat`
15 | - `python`
16 | ```python
17 | import wat
18 | wat.str / datetime.datetime.now()
19 | ```
20 |
21 | ```python
22 | wat_out = "\x1b[0;34m──────────────────────────────────────────────────────────────────────────────────────────\x1b[0m\n\x1b[1;34mstr:\x1b[0m \x1b[0;32m2024-07-31 21:30:28.163527\x1b[0m\n\x1b[1;34mrepr:\x1b[0m \x1b[1mdatetime.datetime(2024, 7, 31, 21, 30, 28, 163527)\x1b[0m\n\x1b[1;34mtype:\x1b[0m \x1b[0;33mdatetime.datetime\x1b[0m\n\x1b[1;34mparents:\x1b[0m \x1b[0;33mdatetime.date\x1b[0m\n\n\x1b[1mPublic attributes:\x1b[0m\n \x1b[1;33mday\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m31\x1b[0m\x1b[0m\n \x1b[1;33mfold\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m0\x1b[0m\x1b[0m\n \x1b[1;33mhour\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m21\x1b[0m\x1b[0m\n \x1b[1;33mmax\x1b[0;33m: \x1b[0;33mdatetime.datetime\x1b[0m = \x1b[0;32m9999-12-31 23:59:59.999999\x1b[0m\x1b[0m\n \x1b[1;33mmicrosecond\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m163527\x1b[0m\x1b[0m\n \x1b[1;33mmin\x1b[0;33m: \x1b[0;33mdatetime.datetime\x1b[0m = \x1b[0;32m0001-01-01 00:00:00\x1b[0m\x1b[0m\n \x1b[1;33mminute\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m30\x1b[0m\x1b[0m\n \x1b[1;33mmonth\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m7\x1b[0m\x1b[0m\n \x1b[1;33mresolution\x1b[0;33m: \x1b[0;33mdatetime.timedelta\x1b[0m = \x1b[0;32m0:00:00.000001\x1b[0m\x1b[0m\n \x1b[1;33msecond\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m28\x1b[0m\x1b[0m\n \x1b[1;33mtzinfo\x1b[0;33m: \x1b[0;33mNoneType\x1b[0m = \x1b[0;35mNone\x1b[0m\x1b[0m\n \x1b[1;33myear\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m2024\x1b[0m\x1b[0m\n\n \x1b[0;34mdef \x1b[1;32mastimezone\x1b[0;32m(…)\x1b[0m \x1b[2;37m# tz -> convert to local time in new timezone tz\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mcombine\x1b[0;32m(…)\x1b[0m \x1b[2;37m# date, time -> datetime with same date and time fields\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mctime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return ctime() style string.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mdate\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return date object with same year, month and day.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mdst\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return self.tzinfo.dst(self).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromisocalendar\x1b[0;32m(…)\x1b[0m \x1b[2;37m# int, int, int -> Construct a date from the ISO year, week number and weekday.…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromisoformat\x1b[0;32m(…)\x1b[0m \x1b[2;37m# string -> datetime from a string in most ISO 8601 formats\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromordinal\x1b[0;32m(…)\x1b[0m \x1b[2;37m# int -> date corresponding to a proleptic Gregorian ordinal.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromtimestamp\x1b[0;32m(…)\x1b[0m \x1b[2;37m# timestamp[, tz] -> tz's local time from POSIX timestamp.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32misocalendar\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return a named tuple containing ISO year, week number, and weekday.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32misoformat\x1b[0;32m(…)\x1b[0m \x1b[2;37m# [sep] -> string in ISO 8601 format, YYYY-MM-DDT[HH[:MM[:SS[.mmm[uuu]]]]][+HH:MM].…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32misoweekday\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return the day of the week represented by the date.…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mnow\x1b[0;32m(tz=None)\x1b[0m \x1b[2;37m# Returns new datetime object representing current time local to tz.…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mreplace\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return datetime with new specified fields.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mstrftime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# format -> strftime() style string.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mstrptime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# string, format -> new datetime parsed from a string (like time.strptime()).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return time object with same time but with tzinfo=None.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtimestamp\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return POSIX timestamp as float.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtimetuple\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return time tuple, compatible with time.localtime().\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtimetz\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return time object with same time and tzinfo.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtoday\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Current date or datetime: same as self.__class__.fromtimestamp(time.time()).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtoordinal\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return proleptic Gregorian ordinal. January 1 of year 1 is day 1.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtzname\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return self.tzinfo.tzname(self).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutcfromtimestamp\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Construct a naive UTC datetime from a POSIX timestamp.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutcnow\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return a new datetime representing UTC day and time.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutcoffset\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return self.tzinfo.utcoffset(self).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutctimetuple\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return UTC time tuple, compatible with time.localtime().\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mweekday\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return the day of the week represented by the date.…\x1b[0m\x1b[0m\n\x1b[0;34m──────────────────────────────────────────────────────────────────────────────────────────\x1b[0m"
23 | RESET ='\033[0m'
24 | STYLE_BRIGHT = '\033[1m'
25 | STYLE_DIM = '\033[2m'
26 | STYLE_RED = '\033[0;31m'
27 | STYLE_BRIGHT_RED = '\033[1;31m'
28 | STYLE_GREEN = '\033[0;32m'
29 | STYLE_BRIGHT_GREEN = '\033[1;32m'
30 | STYLE_YELLOW = '\033[0;33m'
31 | STYLE_BRIGHT_YELLOW = '\033[1;33m'
32 | STYLE_BLUE = '\033[0;34m'
33 | STYLE_BRIGHT_BLUE = '\033[1;34m'
34 | STYLE_MAGENTA = '\033[0;35m'
35 | STYLE_CYAN = '\033[0;36m'
36 | STYLE_WHITE = '\033[0;37m'
37 | STYLE_GRAY = '\033[2;37m'
38 |
39 | output = '\n'.join([
40 | f'>>> {STYLE_BRIGHT_GREEN}import {STYLE_BRIGHT_BLUE}wat{RESET}',
41 | f'>>> {STYLE_BRIGHT_GREEN}import {STYLE_BRIGHT_BLUE}datetime{RESET}',
42 | f'>>> wat / datetime.datetime.now()',
43 | wat_out,
44 | ])
45 | print(output)
46 | ```
47 |
48 | ## wat-nested-dict-pretty.png
49 | ```python
50 | wat.s / {"people":[{"name":"Alice","age":21,"pets":None},{"name":"Bob", "age":42,"pets":['Kitty','Fluffy'],"items":[{'name':"teapot","price":10.99,'in_stock':True}]}]}
51 | ```
52 |
--------------------------------------------------------------------------------
/docs/img/wat-code-wat-call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-code-wat-call.png
--------------------------------------------------------------------------------
/docs/img/wat-datetime-now.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-datetime-now.png
--------------------------------------------------------------------------------
/docs/img/wat-dunder-dict.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-dunder-dict.png
--------------------------------------------------------------------------------
/docs/img/wat-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-list.png
--------------------------------------------------------------------------------
/docs/img/wat-locals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-locals.png
--------------------------------------------------------------------------------
/docs/img/wat-nested-dict-pretty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-nested-dict-pretty.png
--------------------------------------------------------------------------------
/docs/img/wat-pathlib.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-pathlib.png
--------------------------------------------------------------------------------
/docs/img/wat-re-match.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-re-match.png
--------------------------------------------------------------------------------
/docs/img/wat-set.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-set.png
--------------------------------------------------------------------------------
/docs/img/wat-short-types.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-short-types.png
--------------------------------------------------------------------------------
/docs/img/wat-str-split.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-str-split.png
--------------------------------------------------------------------------------
/docs/img/wat-string.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-string.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide:
3 | - navigation
4 | ---
5 |
6 | # 🙀 WAT
7 |
8 |
15 |
16 | Deep inspection of Python objects.
17 |
18 | **WAT** is a powerful inspection tool
19 | designed to help you explore unknown objects and examine them at runtime.
20 |
21 | > "Wat" is a variant of the English word "what" that is often used to express confusion or disgust
22 |
23 | If you ever find yourself in a Python console, feeling lost and confused,
24 | and wondering "WAT? What is this thing?",
25 | that's where `wat` inspector comes in handy.
26 |
27 | Launch the Python Interpreter and execute `wat / object` on any `object`
28 | to investigate its
29 | **type**, **formatted value**, **variables**, **methods**, **parent types**, **signature**,
30 | **documentation**, and its **source code**.
31 | This makes it particularly useful for debugging or understanding intricate data structures in Python,
32 | providing a straightforward way to answer "what" exactly an object represents.
33 |
34 | 
35 |
36 |
38 |
39 | Alternatively, use `wat(object)` syntax for the same in-depth inspection.
40 |
41 | ## Loading
42 |
43 | ### Insta-Load
44 | If you want to quickly debug something,
45 | you can use this inspector **without installing anything**, in the same session.
46 |
47 | Load it on the fly by pasting this snippet to your Python interpreter:
48 | ```python
49 | import base64, zlib
50 | code = b'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'
51 | exec(zlib.decompress(base64.b64decode(code)).decode(), globals())
52 | ```
53 |
54 | Now you can use `wat` object.
55 |
56 | !!! warning
57 | Before executing **Insta-Load** snippet, it's recommended to verify what you're about to run.
58 | You can either:
59 |
60 | - Verify what's inside the extracted code beforehand:
61 | ```python
62 | print(zlib.decompress(base64.b64decode(code)).decode())
63 | ```
64 | - Paste the content of [inspection.py](https://github.com/igrek51/wat/blob/master/wat/inspection/inspection.py) into your interpreter.
65 | It has the same effect.
66 | - [Install package with pip](#install-with-pip) and review the code.
67 |
68 | ### Install with pip
69 | Alternatively, install **wat** package and import inspection tool from **wat** module:
70 | ```sh
71 | pip install wat
72 | ```
73 | ```python
74 | import wat
75 | ```
76 | This package has no external dependencies.
77 |
78 | ### Load from Unicode Glyph
79 | Fun Fact: You can load WAT from a single Unicode glyph.
80 | ```python
81 | import zlib
82 | glyph = '🙀󠅸󠆜󠇕󠅛󠇫󠅮󠇜󠇆󠄕ⷾ󠇯󠆧󠄠󠇔󠄟󠄤󠆝󠇍󠇖󠅎󠅺󠄁󠆔󠇐󠆭󠅬󠅯󠅒󠆣󠆎󠅕󠇈󠅊󠆂󠅂󠄒󠄈󠇮󠅲󠅖󠅢󠇄󠄥󠄗󠄤󠇗󠇒󠅶󠆱󠅀󠄞󠆢󠇏󠇐󠄇󠇋󠆓ⷴ󠅜󠇦󠅎󠅲󠄥󠇇󠄅󠇚󠄊󠆰󠅄󠇎󠇌ⷹ󠇦󠇌󠄹󠅧󠇎󠅥󠆆󠅞󠄶ⷵ󠄪󠇈󠆳󠄮󠅛󠆔󠅙󠇛󠆊󠄶󠄨󠅖󠇫󠆺󠇩󠅌󠇓󠄓󠇙󠅐󠅔󠇭󠅚󠄬󠄺ⷵ󠅚󠇫󠆎󠅆󠆨󠆧󠅶󠇛󠄾󠅙󠄢󠅞󠆷󠅝󠄗󠇕󠆵󠆂󠄺󠆩󠆶󠆓󠇠󠅴󠇝󠄕󠅵󠆕󠆕󠆓󠇠󠅼󠆻󠄖󠅏ⷾ󠅬ⷰ󠇩󠅷ⷰ󠆆󠇡󠅟󠇕󠇕󠆲󠆸󠄾󠅾󠄒󠇀󠅏󠅻󠄃󠇔󠇇󠇁󠆼󠆮󠅋󠅺󠇏󠄷󠅕󠄮󠄚󠆫󠆡󠆪ⷳ󠅺󠇑󠅚󠄍󠅥󠅝󠅝󠅛󠆯󠆋󠄺󠄗ⷶ󠅫󠅖󠆖󠄎ⷽ󠆺󠄩󠄾󠅤󠆝󠄚󠄲󠇆󠇒󠅉󠇗󠄵󠇅󠅼󠄃󠇣󠅸󠇒󠅬󠄅󠄔󠅭󠇗󠇐󠇛󠆇󠆬󠇜󠇀󠄫󠄬󠆑󠅞󠅡󠇡ⷰ󠅆󠄫󠅔󠄳󠅦ⷳ󠅒󠄜󠅚󠆄󠇋󠄄󠆭󠆻󠆸󠆮󠆲󠅮󠇓󠅀󠆛󠆒󠇚󠄅󠇌󠅷󠇅ⷴⷵ󠇂󠅯󠇎󠇅󠅒󠅩󠄧󠅝󠇖󠇍󠄪󠇫󠆢󠅺ⷾ󠇓󠄤󠅸󠄺󠅡󠄑󠄦󠇟󠅤󠅥󠄫󠄦󠅲󠅪ⷵ󠇆󠇂󠅓󠅯󠄨󠄹ⷵ󠆌󠅢󠇓󠇏󠄤󠄳ⷵ󠆶󠇞󠇌󠇋󠅢󠆡󠇞󠆠󠆋󠄟󠇣󠅣󠄩󠅮󠇔󠅝󠇢󠅨󠄲󠅢󠄎󠇨󠆷󠇦󠆀󠄄󠅵󠆃󠄐󠆚󠄓ⷾ󠄣󠅙󠇁󠅟󠅺󠄀󠄱󠆄󠆿󠅌󠄋󠆳󠇅󠅿󠅴󠆫󠄔󠄥󠄠󠅵󠆒󠇕󠆘󠄘󠆫󠄷󠇝󠅺󠇓󠄥󠅥󠇑󠅶󠅑󠆺󠄭󠅄󠆙󠆧󠅊󠅞󠅥󠅑󠆉󠆖󠇅󠇅󠇬󠇇󠅌󠅒󠄬󠇑󠆚󠆧󠅭󠆗󠄃ⷱ󠆴󠅨󠆳󠆮󠇛󠅆󠅲󠆝󠆤󠅨󠇑󠆬󠄊󠇐󠅀󠅺󠅗󠇤󠇝󠅍󠅒󠆷󠇓󠅫󠇑󠆥󠆺󠆵󠄭ⷾ󠄡󠆢󠅸󠆺󠆨󠇋󠇍󠆪󠅪󠄵󠄕󠄳󠄲󠆅󠇉󠅅󠇓󠅅󠇏󠅀󠄿󠇝󠆶󠄔󠇓󠆗󠄧󠅧󠇁󠅧󠅁ⷸ󠇋󠄿󠅿󠄎󠆃󠆧󠄞󠄴󠅴󠆜󠇍󠇞󠇏󠇎󠅣󠄟󠄤󠅛󠆯󠅅󠆕󠅇󠄟󠆋󠇐󠄈󠆰󠆬󠄪󠄈󠄯󠆫󠅰ⷺ󠅓󠅝󠅔󠄑󠇃󠇅󠅤󠅅󠄏󠄊󠇧󠅘󠄉󠆇󠇟󠆧󠅲󠅋󠅩󠇞󠆈󠄾󠄠󠄟󠆐󠅞󠆋󠅊󠄴󠆠󠆎󠆔󠄇󠄁󠇪󠆲󠆎󠆘󠄉󠄰󠇛󠆔󠅶󠅎󠄢󠄍󠆖󠇟󠅸󠄪󠇒󠅹󠆳󠄑󠆊󠇡󠆵󠄚󠆌󠆏󠄸󠅆󠆫󠇈ⷴ󠄅󠅉󠆂󠆨󠇔󠆋ⷶ󠇠ⷶ󠆤󠇐󠅕󠆬󠅓󠅐󠅇󠇝󠅄󠅺ⷲ󠆸󠇇󠅷󠆸󠅣󠅱󠆞󠆟󠆝󠆼󠄹󠇟ⷳ󠇞󠇞󠆑ⷰⷶ󠇁󠅎󠇓󠇭󠅃󠆢󠄓󠅠ⷹ󠄏󠄠󠄀󠇉󠄸ⷽ󠄨󠄕ⷲ󠅮󠆓󠅡󠇏󠅟󠅦󠄧󠆯ⷷ󠄻󠆳󠆪󠆽󠇬󠄏󠆵󠄼󠇑ⷵ󠅨󠅱󠇢󠅋󠅄󠆿󠅐󠄠󠄬󠆯󠇁󠆹󠇈󠅡󠄹󠄬󠅢󠆋󠆄󠅝󠅧󠆍󠆨󠆺󠄶󠄉󠄧󠆁󠄴󠆖󠄔󠆭󠆜󠆛󠅩󠅠󠅫󠅍󠆢󠆵󠄢󠇉󠄞󠆐󠆍󠄚󠆥󠄧󠆗󠄍󠅲󠅮󠆴󠄱󠇩󠄽󠄣󠆘󠄔ⷶ󠅟󠇃󠇦󠄑󠆦󠅩󠄩󠆪󠄴󠄅󠆦󠇞󠇕󠆕󠆈󠇭󠅍󠇙󠅬󠇍󠇋󠇨󠇄󠅀󠅭󠄦󠅵󠇭󠄏󠆺󠅸󠄭󠆖󠆊󠇄ⷽ󠅂󠆬󠄻ⷲ󠇩󠆳󠆦󠆩󠄛󠅷󠆆󠄵󠆆󠄋󠆟󠅟󠆄󠄰󠇃󠄰󠅠󠄤ⷾ󠄒󠆰󠆑󠇖󠄐ⷾⷲⷳ󠆿󠅂󠆳󠆹󠆵󠇯󠅏󠅈󠇔󠄊󠄲󠇕󠇭󠄑󠅒󠅎󠄂󠆽󠄋󠇆󠅭󠅏󠅇󠄑󠆣󠅞󠇕󠄤󠆗󠄇ⷾ󠆖󠆧󠆁󠆇󠇁󠆽󠄇󠆋󠆂󠆮󠄠󠆫ⷲ󠄠󠅂󠆧󠄪󠅷󠄽󠅻󠇪󠆘󠇚󠅇󠄖󠇍󠆔󠇠󠄀󠄷󠅕󠄗󠆡󠆳󠆉󠅱󠄟󠄾󠄻󠆨󠆞󠇗󠆧󠆯󠇞󠇯󠆏󠆎󠆎󠅶󠅀󠅉󠅿󠅭󠄓󠇯󠇯󠄸󠄋󠄁ⷽ󠇍󠅅󠄏󠄇󠆤󠄋󠅈󠄓󠆘󠇀󠄂󠆻ⷲ󠄽󠄘󠆆󠄕󠅚󠆠ⷴ󠅺󠇠󠇬󠄩ⷼ󠅫󠅗󠇢󠆬󠇐󠅚󠅢󠅛󠅯󠆚󠆅󠇔󠄓󠄿󠆧󠄈󠄖󠄹󠆪󠇁󠄸󠅂󠅽󠆏󠄰󠅌󠄞󠇈󠅙󠆋󠅤ⷷ󠆲󠇚󠅱󠆫󠇙󠄕󠆖󠄞󠄸󠅁󠇒󠇀󠆙󠅊󠅔󠇚󠆤󠆅󠄎󠆑󠅇󠅩󠄁󠆡󠄠󠄵󠇭󠆎󠄯󠆟󠄄󠆷󠅢󠆛󠆔󠇙󠅪󠆞󠅧󠅄󠅻󠅌󠆿󠆧󠅨󠅠󠄽󠇛󠄢󠆏󠄎ⷻ󠄳󠆗󠅸󠅭󠇚󠆂󠆬󠄠󠄉󠅩󠄣󠄃󠆯󠆡󠄹󠆘󠄜󠆚󠆜󠆹󠆆󠇍󠆇󠅌󠅀ⷶ󠄒󠇤󠅅󠇣󠆙󠆐󠅌󠄖󠆠󠄟󠇂󠅰󠇖󠅴󠇭󠅝󠇑󠇝󠅄󠆰󠅷󠅂󠆶󠄼󠇬󠄀󠆆󠆬󠅦󠇇ⷺ󠄸󠇇ⷰ󠅍󠆗󠆛󠅣󠅗󠄛󠇐󠇗󠄕󠇕󠅆󠇨󠅆󠆕󠅈ⷴ󠇦󠆖󠅓󠄓󠆢󠆄󠄚ⷷ󠄾󠄜󠆴󠆜󠆝󠄏󠆀󠆱󠇯󠅙󠅞󠅦󠆭󠆘󠇑󠄣󠆈󠄳󠇈󠇚󠅀󠄌󠇁󠄘󠇦󠆴󠅓󠅈󠆴󠅥󠅲󠄸ⷻ󠄈󠄧󠄂󠅼󠅌󠄘󠄸󠅆󠅡󠇩󠄡󠆴󠇅󠇈󠆯󠄚󠄵󠇘󠅎󠆂󠅈󠆤󠆛󠆐󠄒󠇅󠇧󠄇󠅁󠇘󠆊ⷼ󠆄󠆚󠅼󠅙󠅢󠅸󠅉󠄤󠄼󠆅󠄲󠄊󠄬󠇌󠇢󠅄󠇯󠇁󠅄󠅏󠇢󠄥󠆓󠄦ⷹ󠆓󠅿󠄧󠆖󠄔ⷴ󠄓󠄹󠆄󠄄ⷾ󠅉󠄋󠅽󠇐󠇅󠆲󠄦󠄜󠇍󠆦󠄖󠄮󠆨󠅕󠅹󠄍󠅃󠅭󠄢󠆮󠅔󠅰ⷴ󠄃󠆮󠆂󠆂󠇇󠇄󠇄󠆑󠅸󠄐󠄲󠆌󠄠󠄞󠇄󠅺󠆳󠄏ⷹ󠆤󠅣󠇋󠅌󠇅󠆲󠆸󠅏󠅂󠄮󠅗󠅔󠅖󠇢󠆒󠇕󠄍󠄤󠅸󠆐󠇇󠄭󠄷󠄕󠇭󠇗󠄑󠆈󠆬󠇝󠅖󠆋󠄀󠆅󠄲󠄈󠇣󠅐󠆣󠅓󠄴󠅝󠄫󠇑󠇝󠇔ⷹ󠅀󠇇󠅼󠅓󠆔󠅝󠅡󠅈󠅮󠆲󠅶󠄨󠄄󠄎󠄰󠅣󠆳󠅡󠅻󠅼󠇕󠄟󠇚󠆙󠆬󠅶󠆢󠅿󠆝ⷽⷽ󠇇󠇓󠄳󠇈󠆒󠅸󠇘󠅞󠄶󠆿󠄺󠅹ⷻⷶ󠇤󠇥󠇛󠇙󠅾󠆇󠄓󠆪󠇖ⷷ󠅯󠆾󠅽󠅷󠅲ⷾⷽ󠄙󠄴󠄛󠇩󠆛󠆤󠅊󠅛󠆇󠇯󠇘󠄇󠄌󠅂ⷲ󠆡󠄖󠄎󠅔󠅌󠅤󠄂󠆁󠆲󠆃󠇓ⷷ󠆾󠄑󠅌󠅠󠅏󠇤󠆐ⷵ󠅤󠄨󠅜󠄶󠄋󠇏󠄃󠇨󠅕󠄮󠆳󠆢󠄔󠅹󠇐󠇕󠄁󠇌󠄐󠇘󠆱󠄢󠇘󠇑󠅞󠄑ⷱ󠄞󠄞󠆅󠇍󠆽󠄓󠇗󠄥󠇯󠆸󠄃󠄬󠅞󠇥󠄐󠄧󠇞󠄗󠄭󠇭󠇞󠄞󠄓󠅺󠅋󠇋󠅝󠄴󠆥󠆴󠄺󠅲󠄊󠄋󠆢󠅟󠇒󠆄󠇬󠄇󠅒󠆊󠅒󠆐󠆱󠇡󠇠󠇪󠄺󠄲󠆻󠇏󠄊󠄧󠆐󠅿󠄵󠄅󠆹󠄲󠅼ⷳ󠆢󠄄󠄹󠄁󠄤ⷷ󠆪󠄅󠆊󠅖󠅣󠄞󠅩󠄠󠅜󠇒󠇩󠆀󠅞󠆓󠆅󠇪󠅱󠆨󠅻󠅢󠅝󠇓󠄻󠆳󠆓󠆴󠅩󠅲󠅼󠆊󠅝󠅣󠄌󠅔󠇆ⷾ󠇃󠇉󠇙󠄛󠆶󠄻󠄝󠅕󠆵󠅉󠆞󠆾󠆞󠇭󠆥󠇚󠄐󠅹󠄟󠄤󠇁󠅎󠇏󠆪󠄔󠇨󠅱󠇕󠆉ⷻ󠅎󠆅󠇍󠆢󠅩󠆹󠄺󠆛󠄄󠇩󠄄󠇦󠅮󠆻󠄄󠆻󠆧󠆐󠅂󠅷󠄅󠇭󠅓󠅊󠆹󠅌󠆙󠇔󠅶󠆸󠄃󠄱󠆽󠅵󠅊󠄡󠄃󠄔󠇇󠇁󠆋󠇠ⷹ󠆳󠅧󠅽󠇃󠇓󠅃󠄮󠆎󠆡󠅊󠅕󠆓󠅔󠅮󠅂󠇪ⷺ󠅤󠅰󠅬󠅟󠇁󠇒󠅍󠄌󠆨󠅗󠄪󠆉󠆤󠅤󠆒󠇖ⷾ󠄆󠄀󠇉󠄚󠅉󠆺󠅮󠆒󠇚󠄏󠅤󠆪󠅎󠅞󠆪󠄆󠄿󠆐󠆜ⷶ󠇦󠄵󠅮󠄢󠆰󠄓󠇍󠇟󠇈󠅮󠅌󠅚󠄟󠆑󠆱󠄞󠆀󠄽󠇆󠅬󠇏󠇉󠅠󠆡󠅁󠅣󠅟󠅖󠅾󠅊󠇬󠆂󠄟󠄀󠆖󠄆󠇥󠅬󠄞󠅻󠇟󠅠󠇂󠄿󠄁󠄇󠆆󠆞󠄨󠅹󠅦󠇔󠅒󠆴󠇠󠄨󠆺󠆬󠅚󠇨󠇡󠆸󠄩ⷺ󠅓󠄞󠄩󠆧󠅺󠅾󠆶󠄏󠅷󠆲󠇆󠆕󠆬󠄞󠄩󠄰󠄮󠇆󠇇󠅜󠆋ⷶ󠇢󠇯󠅎󠇟󠇍ⷶ󠄸󠇄󠅙󠆪󠅍󠆏󠇜󠆎󠇓󠆟󠆟󠅽󠄿󠇛󠇣󠆐󠅑󠅺󠄺󠆲󠄚󠄇ⷸ󠇦󠇤󠇭ⷻ󠇙󠆞󠄆ⷹ󠄐󠅽󠅹󠅀ⷸ󠇯󠆠󠇀󠄨󠇫󠆬󠆋󠄇󠄄󠆣󠄗ⷵⷽ󠅷󠄯󠅧󠅧ⷻ󠇝󠅀󠇩󠄾󠄈󠆛󠄗󠆋󠆮󠄏󠆧󠄔󠆈󠆽󠅽󠄭󠄪󠄅󠇂󠇆󠅼󠄞󠆏󠅃󠇣󠄡󠇘󠄸󠄴ⷶ󠄾󠄆󠅚󠆞󠅥󠅐󠄆󠅤󠄥󠆟󠅙󠅵󠄍󠆉󠇔󠆼󠇉󠄖󠆷󠄐󠅽󠄀󠅰󠅱󠆓󠄴󠅢󠆺󠇜󠆔󠄥󠆽󠅄󠇡󠇗󠇑ⷴ󠅩ⷼ󠄢󠆜󠄨󠄄󠇍󠇩󠄀󠇩󠆸󠄼󠇉󠆁󠅾󠇝󠅷󠆴󠅽󠆌󠇩󠄵󠇤󠄿󠇫󠇨󠅹󠇬ⷸ󠇞󠄗󠆎󠄊󠅼󠅴󠄴󠅣󠅵󠅚󠇣󠆥󠄄󠅽󠄍󠇀󠆣󠄫󠄤󠅫ⷻ󠆰󠇐󠅞󠄄󠅟󠄎󠆸󠅕󠇗󠇜󠅦󠅧󠅧󠆧󠅧󠇇󠄐󠇜󠅫󠇈󠇁󠇄󠆺󠇜󠄆󠄕ⷸ󠅬󠆑ⷻ󠆦󠆂󠆮󠄍󠆽󠄸󠇌󠅺󠇀󠇜󠅨󠆍󠆻󠇝󠇞󠆳󠆴󠅎󠆬󠇚󠄤󠆂󠅑󠆮󠄧󠆸ⷵⷸ󠇇󠇈󠇤ⷹ󠄊󠅿󠅄󠇈󠆕󠄛󠆤󠇭󠅜󠆺󠄭󠆦󠄄󠄞󠇅󠅎󠇘󠅵󠇣󠄭󠆍󠆠󠄸󠅪󠆎󠆓󠆨󠄭󠅾󠅨󠄝󠅲󠄕󠄻󠆍󠇠󠇨󠇒󠅛󠄥󠆝󠅙󠄦󠇦󠅸󠄳󠄊󠆱󠄕󠅏󠅅󠆵󠄍󠄃󠅾󠇑󠇭󠄧󠆼󠆈󠄂󠅯󠄶󠆘󠄹󠆕󠆵󠅗󠅸󠄂󠅊󠆾󠅐󠇓󠅆󠆒ⷸ󠅳󠆵󠄁󠄎󠆳󠄊󠅾󠆛ⷸ󠇀󠄇󠄃󠄷󠇆󠆶󠅭󠅙󠇖󠄆󠄬󠇛󠇮󠆿󠅠󠅙󠇥󠆐󠅻󠅰󠆗󠅹󠅱󠄵󠅤󠅗󠆽ⷰ󠇢󠄱󠅏󠇒󠆆󠅦󠄔󠄷󠅎ⷲ󠆟󠆷󠆔󠆋󠇃󠆆󠅲ⷵ󠄿󠅣󠄧󠄗󠆏󠄷󠆓󠆫󠅁󠄫󠇑󠇇󠆻󠆩󠇔󠇕󠆪󠇎󠄷󠄥󠇗󠇌󠇩󠄴󠅍ⷹ󠄵󠅍󠆕󠆌ⷹ󠅝󠄅󠅟󠅌󠄅󠅥󠄋󠆟󠆚󠄃󠄅󠆕󠆐󠆩󠅍ⷻ󠆐󠅻󠅑󠅳󠅱󠄙󠆷󠄿󠇨󠅑󠆙󠆂󠆑ⷷ󠇓󠅑󠅒󠅝󠆷ⷴ󠅎󠆘󠅓󠅣ⷻ󠆪󠆌󠆤󠅦󠄪󠄤󠅗󠅍󠇭󠇖󠆑󠆤󠄯󠆐󠇥ⷽ󠄤󠆘󠅧󠆭󠄠󠄜󠇔󠆞󠆨󠄶󠄫󠆺󠆒󠆈󠆴󠆠󠆐󠇔󠄻󠄍󠆒ⷻ󠇫󠆞󠅲󠅂󠆔󠆕󠆅󠇑󠇢󠆩󠄀󠅔󠅎󠄮󠄅ⷾⷴ󠇎󠆐ⷰ󠆇󠆏󠄽󠄜󠆭󠅩󠄰󠅽󠄄󠄱󠅴󠅋󠄢ⷳⷼ󠄆󠇏󠅇󠅔󠄏󠇄ⷱ󠇅󠅭󠅊󠅭󠇑󠄟ⷴⷶ󠆡󠅷󠅫󠇩ⷸ󠆊󠄘󠅶󠆍󠆧󠄛󠄣󠅺󠅲󠇎󠇉󠅴󠇟󠅴󠄉ⷵ󠅥󠇥󠆀󠆙󠄥󠅸󠄧󠆕ⷲ󠇞󠄍󠄩󠆎󠄃󠅇󠇇󠅽󠆸ⷽ󠆱󠇕󠆈󠇦󠅞󠇕ⷽ󠄼󠇙󠅡󠄄󠆋󠇚󠄔󠇥󠄉󠄥󠆍󠅿󠅐󠆗󠄋󠅫󠇏󠄎󠆓󠇄󠅎󠅙ⷺ󠆘󠅵󠆈ⷻ󠄵󠆔󠅈󠄭󠇔󠅍󠇦󠆬󠄜󠄑󠆕󠅉󠆎󠆟󠅻󠆶󠅞󠄭󠇃󠆷󠆎󠄼󠄲󠆹󠇈󠇈󠄒󠄳󠄴󠄽󠅪󠇑󠆥󠇎󠅔󠄞󠅒󠆙󠅺󠄥󠅓󠄧󠆓󠅼󠄾󠄭ⷻ󠄏󠄁󠅩󠄐󠅹󠄯󠆼󠄱󠄼󠆎󠄑ⷰ󠄘󠅽󠄂󠅮󠄳󠄻󠅼󠆅󠄷󠆴ⷰ󠄨ⷼ󠄛󠇑󠅙󠇇󠇍󠇡󠇄󠇁󠄲󠇇󠇀󠅲󠄢󠅙󠆌󠇙󠆋󠅶󠄖ⷸ󠅑󠆓󠄫󠄐󠅧󠅶󠄛󠅭󠅬󠅺󠇫󠅄󠇘󠄖󠇖󠇇󠅌ⷾ󠅚󠅂󠇘󠅳󠇛󠅘󠇞󠄁ⷸ󠄠󠄈󠄔󠇨󠅸󠅺󠄹󠅤󠄽󠇪󠄤󠇤󠆠󠇩󠆨ⷳ󠇊󠄫󠅃󠇂󠆵ⷵ󠆨󠇚󠅝󠄊󠅞󠆥󠄼󠅃ⷳ󠅶󠄅󠇟󠄲󠄒󠆇ⷻ󠅣󠅧󠆓󠄒󠄮󠅀󠄪󠅨󠅢󠇓󠄗󠇝󠄣󠅎󠅴󠇬󠆊󠆝󠄰󠅈󠄟ⷶ󠄪󠅼󠇐󠅰󠆄󠆁󠆑󠇡󠄏󠄞󠄺󠆰󠅪󠄆󠅼󠅪󠄮󠇖󠇝󠆍󠇣󠅹󠆕󠄋󠅝󠅬󠄚󠆌󠅇󠄼󠄬󠇖󠇬󠆤󠇈󠅋󠄃󠅅󠆇󠅋󠅫ⷻ󠄳󠆌󠄘󠆨󠄶󠆷󠇦󠄵󠄳󠇐󠇯󠇩󠄲󠆝󠄃󠄛󠅎󠇤󠇄󠅶󠇇󠆈󠆔󠄸󠇛󠅈󠄿󠅍󠄂󠅩󠅋󠇖󠇩󠇞󠅇󠄩󠆴󠄢󠄎󠆫󠅀󠇞󠄖󠅩󠇠󠇩󠆭󠇘󠅢󠄲󠄿󠅸󠅘󠇦󠆥󠅹󠆒󠇤󠄂󠆱󠆮󠄤󠄫󠆚󠅬ⷸ󠇨󠅌󠆞󠇬󠄻󠆔󠅱󠄶󠅳󠇠󠄤󠇭󠅗󠄜󠆢󠅙󠆧󠅝󠇖󠄉󠆚󠄔󠄳󠇔󠆦󠇭󠅦󠄞󠆅󠆗󠆗ⷷ󠇏󠇧󠆗󠆗󠄗󠆗󠆗ⷹ󠅧󠇑󠅗ⷸ󠄻ⷾ󠇓󠄊󠆓󠅏ⷸ󠅇󠅔ⷲ󠆋󠆠󠄟󠄳󠄙󠆐󠆎󠆎󠆎󠇤󠅥󠄆󠄟󠅹󠅓󠆉󠆍󠅧󠆳󠇢󠄾󠅛󠅁󠆌󠄋󠄶󠇕󠅭󠅕󠇟󠅕󠄲󠅝󠅨󠅡󠄼󠅻󠅩󠇤󠄉󠅢󠅼󠇑󠆥󠅩󠇔󠆊󠅲󠄹󠄉󠆞󠄾󠅕󠆟󠅛󠇜󠇞󠅥󠇍󠆵󠅽󠅘󠆅󠄃󠆦󠆩󠇛󠆝󠆸󠆯󠇞󠅘ⷹ󠄉󠇎󠅮󠄿󠆂󠅑󠅔󠇩󠆺󠆩󠆯󠄱󠇚ⷱ󠆧󠄻󠄖󠅗ⷴ󠅱󠄁󠅳󠇥󠅚󠄲󠄚ⷰ󠅝󠇖󠄍󠆢󠆸󠅆󠇍󠆓󠆁󠄷󠆆󠇤󠇭󠅆󠆔󠅶󠄌󠅖󠇟󠆝󠆄󠇖󠆌󠅘󠅣ⷷ󠄦󠅔󠄃󠆿ⷾⷱ󠇤󠄼󠄰󠅂󠅦󠅁󠆾󠆰󠇈󠆭󠅩󠄼󠄈󠅔󠅙󠆲󠄌󠇏󠆛󠆭󠆓󠆜󠇀󠄚󠆂󠇟󠅊󠄠󠄕󠇧󠄁󠇙󠄛󠄲󠆅󠄴󠆵󠅘󠄖󠄂󠅜󠆓󠄿󠄘ⷴ󠄫󠅅󠄀󠅞󠇋󠆡󠅳󠇆󠅍󠆝󠆏󠄶󠆾󠅓󠅰󠄊󠄅󠅲󠇛󠇣󠇋󠅊󠅛󠄷󠆞󠄶ⷰ󠅍󠅮󠆟󠄧󠇮󠆳󠆦󠆿󠄩󠅲󠄻ⷴ󠄅󠅦󠄛󠆑󠄟󠆕󠄾󠄱ⷶ󠇡󠄹󠅆󠅙󠄸󠄤󠄻󠅽󠅇󠆪ⷱ󠅼󠄺󠇜󠇍󠄽󠆪󠆪󠆮󠄾󠇏󠇦ⷳ󠅆󠅼󠄨󠄠󠇦󠇦󠇬󠄛󠅸ⷾ󠆼󠅞󠅀󠆆󠄭󠆯󠄳󠅼󠄰󠇌󠆨󠅺󠅠󠇖󠄽󠅆󠅐󠅃󠅘󠄈󠇔󠅍󠇓󠅄󠄮󠆆󠅮󠇠󠅱ⷳⷹ󠅰󠅼󠆴󠇫󠇋󠇆󠇡󠆀󠆜󠆜󠄂󠅤󠄎󠇥󠅗󠆎󠄽󠇞󠄨󠄜󠅘󠅠󠆠󠆐󠆻󠄀󠄡󠇑󠇝󠆍󠅨󠅠󠅷󠇟󠄈󠆥󠅾󠇄󠆾󠇋󠅚ⷾ󠄌󠄭ⷷ󠇁󠄸󠄗ⷲ󠇁󠇪󠄊󠅪󠇯󠆵󠆟󠄺ⷹ󠆴󠄀󠇨󠅘󠇛󠆢󠇜󠆠󠇆󠅋󠄬󠆛󠇙󠆇󠄎󠅈󠄖󠆶󠆌󠅅󠄤󠄷󠆐󠇅󠄮󠄨󠅩󠇔󠅘󠅻󠆖󠇘󠄵󠇃󠅘ⷼ󠆝󠅘󠇀󠆷󠄑󠇤ⷳ󠅄󠆖󠆣󠇎󠅈󠆑󠇐󠇦󠄣󠅝󠄷󠇙󠇖󠆂󠇊󠆋󠆖󠄮󠆇󠇉󠄗󠄯󠄷󠆥󠄂󠄬󠄘󠄞󠄼󠅗󠅛󠆗󠆢󠅯󠄴󠄰󠇚󠅂󠄑󠄨󠆅󠅅󠄏󠆥󠇭󠇁󠆼󠅂󠆙ⷹ󠅛󠆻󠆬󠅁󠅣󠇭ⷸ󠇞󠆏󠇢󠆁󠆭󠅎󠅄󠆁󠄉󠆎󠄣󠇘󠇗󠅥󠄽󠆷󠇀󠄭󠄀󠇮󠆱󠄐󠅂󠇟󠆻󠄪󠇧󠄍󠅫󠅊󠆡󠅨󠆂󠅁󠅹󠇤󠅕󠆥󠇤󠇓ⷺ󠇡󠅌󠆏󠄡󠄭󠅘󠅭󠇬󠅡󠇑󠅀󠅍󠆤󠇁󠆀󠆁󠄁󠆧󠄗󠅨󠄰󠄡󠆃󠆖󠇁󠆛󠄊󠇢󠅭󠅑󠆯󠆷󠆑󠄦󠆜󠇊󠆕󠅅󠆄󠅨󠅽󠇐󠆀󠆗󠇆󠄼󠇦󠆱󠅐󠄇󠅮󠅊󠇆󠆫󠇟󠄯󠇜󠇒󠆎󠄔󠇄󠅹󠆳󠇊󠆧󠅘󠇑󠄦󠄍󠆣󠆛󠅈󠄫󠄪ⷺ󠅌󠇉󠅠󠅢󠆒󠄬󠆃󠄉󠅙󠇀󠅛󠇗󠄂󠅂󠄣󠅡󠄥󠄈󠄖󠅰󠄍󠄖󠅨󠇟󠆏󠆌󠄅󠅍󠆺󠅰󠄱󠇑󠇊󠅮󠅄󠅾󠆮󠇪󠅿󠇒󠆋󠇠󠆨󠆽󠆡󠆬󠄠󠄞󠄀󠅈󠅹󠆁󠄉󠆏󠆇󠇭󠆷󠄮󠆳󠅭󠇊󠅽ⷺ󠆫󠅏󠆛󠄌󠆤󠇥󠅐ⷶ󠄏󠄹󠆤󠇈󠆜󠅑󠄾󠆄󠆝󠆈󠇠󠇡󠅃󠄔󠇂󠄸󠇈󠆣󠇬󠄯󠆅󠆇󠄰󠅱󠅹󠅃ⷺ󠅱󠆾󠄣󠅙󠇢󠅧󠆭󠆥󠄧󠆯󠅑󠄱󠆻󠄙󠄎󠄙󠆞󠇊󠆹󠅺󠅛󠆠󠄒󠅷󠄪󠅻󠅲󠅖󠅀󠅔󠅆󠅎󠅶󠇚󠇌󠄘󠅲󠅄󠇯󠄌󠅉󠆍󠇚󠇉ⷵ󠆇󠆰󠅷ⷱ󠄏󠆺󠅅ⷼ󠅋󠆛󠄸󠇜ⷷ󠄅󠅢󠄸󠇡󠇔󠄸󠆑ⷳ󠆬󠇫󠅵󠇤󠅾󠆛󠆅󠄣󠆽󠇬󠅰󠇈󠄸ⷼ󠄵ⷴ󠇉󠆦󠆛󠅵󠆎󠇇󠅢󠆾󠄱󠇡󠅈󠆐󠅮󠄂󠅹󠄯󠅬󠇙󠄾󠅝󠅯󠆤󠇎󠅀󠇍󠄢ⷼ󠇄󠅎󠆎󠄴󠅎󠇊󠆳󠅌󠆹󠆕󠇘󠄾󠅻󠅹󠇨󠄣󠄼󠆥󠇜󠄾󠆎󠆯ⷴ󠇍󠅽󠇐󠅎󠅙󠅁󠅃󠅶󠇪󠇘󠆽󠇧󠅲󠅽󠇨󠇞󠆷󠄒󠅔󠅮󠄺󠄌ⷷ󠅓󠇬󠄾󠄳󠅬󠄞󠄇󠇙󠅱󠆼󠇈󠄠󠆈󠆴󠆵󠄃󠄘󠅦󠆋󠇠󠆏󠆨󠄾󠄰󠆫󠇉󠄮󠅜󠅢󠅏󠅸󠄜󠇈󠆏ⷽ󠇃󠄎󠅦󠆃󠅗󠆜󠅴󠅏󠇨󠅵󠄻󠆅ⷱ󠅅󠅓󠅗󠄼󠄙󠇤󠇫󠇩󠆫󠇓󠆷󠆧󠅧󠅔󠄸󠇅󠄐󠅬󠇯󠅄󠄓󠇅󠆎󠇐ⷵ󠄄󠇣󠆅󠆱󠅤󠅌󠆏ⷴ󠄅󠄻ⷰ󠅙󠆾󠅟󠄓󠄘󠄷󠇎󠇆󠇤󠅗󠇌󠆖󠄨󠇨ⷾ󠅻󠅰󠇓󠇐󠆚󠆰󠅛󠄋󠇐󠄐󠇑󠇝󠄁󠅷󠆂󠇇󠆖󠇧󠄽󠄃󠄙󠅴󠇋󠇦󠅬󠆓󠇦󠆈󠇭󠅘󠆎󠄊󠇈󠆋󠄏󠄺󠆜ⷻ󠇑󠇆󠇆󠅕󠅱󠆉󠇇󠅘󠄠󠅙󠆞󠅿󠄚󠅀󠇙󠇞󠄔󠇋󠇮󠇓󠄰󠆚󠄀󠅆󠇝󠅼󠄢󠄏󠆟󠄊󠅐󠅾󠇢󠄂󠇤󠅷󠆡󠄚󠆄󠅜󠆻󠄛󠆏󠇐ⷵ󠆚󠆜󠇉󠇙󠅈󠄦󠆺󠅐󠇩󠅈ⷱ󠇅󠄏󠄦󠇚󠄧󠆻󠆖󠅽󠄡󠅉󠆮󠇜󠄴󠆄󠆒󠄷󠄃󠄫󠄭󠄿󠇤󠄚󠆑󠄃󠅖󠄮ⷰ󠄯󠄗󠅞󠇔󠅂󠆩󠄙󠄾󠅱󠅝󠆃󠅏󠇐ⷴ󠅘󠄾󠄸󠆺󠄝󠅢󠇢󠄣󠄢󠆧󠄗󠆀󠄎󠆃󠄧󠅉󠄐󠅲󠆊󠄘ⷾ󠅿󠇥󠆜ⷽ󠅥󠇈󠆲󠇣󠇗󠆮󠆃󠇉󠇝󠆅󠅈󠇈󠅏󠅘󠆉󠆅󠄊󠅋ⷹ󠇖󠆫󠅿󠅆󠇗󠄂ⷺ󠄋󠅇󠄋󠆇󠄃󠆕󠅂󠆓󠄕󠇀󠆠ⷾ󠆬󠆘󠆾󠆣󠄜󠅋󠄻ⷸ󠄠󠇯󠄽󠇖󠅮󠄌ⷲⷲ󠇤󠄬󠄉ⷱⷼ󠇯󠇢󠇙󠅗󠅟ⷾ󠅮󠇅󠆵󠄙󠅝󠄏󠇉󠇦󠇧󠆦󠄙󠄏󠅴󠅌󠇫󠄟󠅥󠄫󠄔󠆪󠄆󠇡󠄋󠇙󠇈󠆟ⷼ󠆘ⷶ󠇧󠆪ⷽⷴ󠇝󠇌󠆴ⷾ󠅞󠇏ⷷⷽ󠇌󠄀󠄫󠄌󠆺󠇁󠄷󠇍󠄊󠄂󠆿󠄎󠆓󠆭󠅟󠄘󠄮󠇤󠇧󠆯ⷽ󠆵󠆨󠄏󠅠ⷻⷸⷺ󠄣󠇘󠄾ⷻ󠇪󠇌󠇕󠄐󠅽󠆩󠇠󠆠󠇠󠄵󠇣󠅕󠅫󠇯󠅠󠇓󠆻󠆎󠅝󠇜󠅬󠆪󠅛󠇜󠇔󠇋󠆢󠇬󠄠󠄭󠇀󠇬󠄈󠆎󠅳󠄬󠆅󠅸󠄯󠅳󠄈󠄈󠆎󠄅󠆴󠅏󠇂󠇘󠅋󠅟ⷸ󠄓󠅭ⷾ󠆏󠆁󠄈󠆭󠇮ⷼ󠄔󠅅󠄒󠆺󠅶󠇞󠇊󠆯ⷰ󠆍󠆯󠆅󠆜󠅷󠆍ⷹ󠄉ⷾ󠄐󠅚󠄍󠅟ⷹ󠆭󠆀󠆐󠅊󠅸󠆵󠅆󠅘󠄠󠅕ⷺ󠄉󠇙󠄌󠅸󠅡󠆕󠄐󠇇󠄆󠆺󠅑󠄋󠆖'
83 | exec(zlib.decompress(bytes(ord(c)&255 for c in glyph[1:])).decode(), globals())
84 | wat / 'WAT is going on?'
85 | ```
86 |
87 | ## Usage & modifiers
88 | `wat` can quickly inspect things
89 | by using the division operator (for faster typing without parentheses).
90 | A short syntax `wat / foo` is equivalent to `wat(foo)`.
91 |
92 | You can call `wat.modifier / foo` with the following **modifiers**:
93 |
94 | - `.short` or `.s` to hide the attributes (variables and methods inside the object)
95 | and print only value, type, parent types, signature and documentation
96 | - `.dunder` to display dunder attributes (starting with double underscore)
97 | - `.long` to show non-abbreviated values and docstrings
98 | - `.code` to reveal the source code of a function, method, or class
99 | - `.nodocs` to hide documentation for functions and classes
100 | - `.caller` to show how and where the inspection was called (works in files, not REPL)
101 | - `.public` to show only public attributes (hiding private attributes)
102 | - `.all` to include all available information
103 | - `.ret` to return the object back after the inspection
104 | - `.str` to return the output string instead of printing it
105 | - `.gray` to disable colorful output in the console
106 | - `.color` to enforce colorful outputs in the console
107 |
108 | You can chain modifiers, e.g. `wat.short.str.gray / 'foo'`.
109 |
110 | Call `wat.locals` to inspect local variables.
111 | Call `wat.globals` to inspect global variables.
112 |
113 | You can explore any object.
114 | In Python, an "object" refers to not only to data structures,
115 | but also to functions, classes, modules, built-in types, and more.
116 |
117 | Type `wat` in the interpreter to learn more about this object itself.
118 |
119 | There are several alternative syntaxes that are equivalent.
120 | Choose the one that works best for you:
121 | ```python
122 | wat.short / 'foo' # fast typing
123 | wat.short('foo')
124 | wat('foo', short=True) # natural Python syntax
125 | 'foo' | wat.short # Unix piping
126 | ```
127 |
128 | ## Use Case Examples
129 |
130 | ### Determine type
131 | In a dynamic typing language like Python, it's often hard to determine the type of an object.
132 | WAT Inspector can help you with that by showing the name of the type with the module it comes from.
133 |
134 | ```python
135 | >>> wat.short / (1,)
136 | value: (1,)
137 | type: tuple
138 | len: 1
139 | ```
140 |
141 | ```python
142 | >>> wat.short / {None}
143 | value: {None}
144 | type: set
145 | len: 1
146 | ```
147 |
148 | ```python
149 | >>> wat.short / user
150 | str: admin
151 | repr:
152 | type: django.contrib.auth.models.User
153 | parents: django.contrib.auth.models.AbstractUser, django.contrib.auth.base_user.AbstractBaseUser, django.contrib.auth.models.PermissionsMixin, django.db.models.base.Model, django.db.models.utils.AltersData
154 | ```
155 |
156 | 
157 |
158 | Now that you've identified the actual type,
159 | you can put the type annotations in your code to reduce further confusion.
160 |
161 | ### Look up methods
162 | By listing out methods with their signatures and docstrings, you can easily grasp how to use the unknown object.
163 |
164 | ```python
165 | wat / ['foo']
166 | ```
167 |
168 | 
169 | 
170 |
171 | Use `wat.long` if you want to see full doscstrings.
172 |
173 | ### Discover function's signature
174 | See the docstrings and the signature of a function to learn how to use it.
175 |
176 | ```python
177 | wat / str.split
178 | ```
179 |
180 | 
181 |
182 | ### Look up attributes
183 | List the attribues and their types to see what's really inside the inspected object.
184 | ```python
185 | wat / re.match('(\d)_(.*)', '1_title')
186 | ```
187 |
188 | 
189 |
190 | ### Explore modules
191 | Another use case is to explore modules.
192 | You can list the functions,
193 | classes and sub-modules of a selected module.
194 |
195 | ```python
196 | import pathlib
197 | wat / pathlib
198 | ```
199 |
200 | 
201 |
202 | Then, you can navigate further, e.g. `wat / pathlib.fnmatch`.
203 |
204 | ### Explore dunder attributes
205 | By default, WAT Inspector hides attributes starting with `__`. Use `wat.dunder` to see them.
206 | ```python
207 | wat.dunder / {}
208 | ```
209 |
210 | 
211 |
212 | ### Review the code
213 | Look up the source code of a function to see how it really works.
214 |
215 | ```python
216 | import colorsys
217 | wat.code / colorsys.hsv_to_rgb
218 | ```
219 |
220 | 
221 |
222 | ### Prettify unreadable collections
223 | Nested dictionaries and lists get nicely formatted, indented output:
224 |
225 | 
226 |
227 | ### Debug with breakpoint
228 | You can use Python's `breakpoint()` keyword to launch an interactive debugger in your program.
229 | Attach to the interpreter and inspect things on the spot.
230 |
231 | ```python
232 | (Pdb) import wat # or paste Insta-Load snippet
233 | (Pdb) wat / foo # inspect local variables
234 | ...
235 | (Pdb) c # continue execution
236 | ```
237 |
238 | ### Look up local variables
239 | Use `wat.locals` or `wat.globals` to look up the local and global variables respectively.
240 |
241 | 
242 |
243 | ### Learn Python
244 | With these snippets you can better understand Python internals.
245 |
246 | ```python
247 | reversed([]) == reversed([])
248 | # False
249 | wat.s / reversed([])
250 | # value:
251 | # type: list_reverseiterator
252 | ```
253 |
254 | ```python
255 | wat / type('ObjectCreator', (), {})
256 | # value:
257 | # type: type
258 | # signature: class ObjectCreator()
259 |
260 | wat / type
261 | # value:
262 | # type: type
263 | # signature: class type(…)
264 | # """
265 | # type(object) -> the object's type
266 | # type(name, bases, dict, **kwds) -> a new type
267 | # """
268 | #
269 | # Public attributes:
270 | # def mro(self, /) # Return a type's method resolution order.
271 | ```
272 |
273 | ```python
274 | from typing import List
275 | wat.s / List[str]
276 | # value: typing.List[str]
277 | # type: typing._GenericAlias
278 | # parents: typing._BaseGenericAlias, typing._Final
279 | # signature: def List(*args, **kwargs)
280 |
281 | wat(str | None)
282 | # value: str | None
283 | # type: types.UnionType
284 | ```
285 |
286 | Explore Python built-ins:
287 | ```python
288 | wat / __builtins__
289 | wat / ...
290 | ```
291 |
292 | ### Inspect WAT itself
293 | ```python
294 | wat.dunder / wat
295 | wat.code / wat.__truediv__
296 | ```
297 |
298 | ## Environment variables
299 | - `WAT_COLOR="false"` to disable colorful output in the console.
300 | - `WAT_COLOR="true"` to enforce colorful outputs even in non-tty environment.
301 |
302 | ### Color theme
303 | You can customize the color theme by setting the environment variable `WAT_COLORS`.
304 | Here's the default theme which you can modify with your own ANSI color codes:
305 | ```sh
306 | export WAT_COLORS="BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,SIGNATURE=0;32,VARIABLE=1;33,CODE=0;33"
307 | ```
308 |
309 | ## References
310 | - Inspired by [Rich Inspect](https://github.com/Textualize/rich?tab=readme-ov-file#rich-inspect)
311 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs==1.6.0
2 | mkdocs-material==9.5.22
3 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: WAT
2 | repo_name: igrek51/wat
3 | repo_url: https://github.com/igrek51/wat
4 | site_author: igrek51
5 | docs_dir: docs
6 | theme:
7 | name: material
8 | icon:
9 | repo: fontawesome/brands/github
10 | logo: material/lightbulb-outline
11 | palette:
12 | - scheme: default
13 | primary: indigo
14 | accent: blue
15 | toggle:
16 | icon: material/toggle-switch-off-outline
17 | name: Switch to dark mode
18 | - scheme: slate
19 | primary: black
20 | accent: green
21 | toggle:
22 | icon: material/toggle-switch
23 | name: Switch to light mode
24 | font:
25 | text: Roboto
26 | code: Roboto Mono
27 | features:
28 | - navigation.expand
29 | - content.code.copy
30 | markdown_extensions:
31 | - admonition
32 | - pymdownx.highlight:
33 | use_pygments: true
34 | - pymdownx.superfences
35 | plugins:
36 | - search
37 | extra:
38 | social:
39 | - icon: fontawesome/brands/github
40 | link: https://github.com/igrek51/wat
41 | name: WAT on GitHub
42 |
43 | nav:
44 | - Home: index.md
45 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "wat"
3 | description = "Deep inspection of Python objects"
4 | license = {text = "MIT"}
5 | authors = [
6 | { name = "igrek51", email = "igrek51.dev@gmail.com" },
7 | ]
8 | classifiers = [
9 | "Programming Language :: Python :: 3",
10 | "License :: OSI Approved :: MIT License",
11 | "Operating System :: OS Independent",
12 | ]
13 | readme = "README.md"
14 | requires-python = ">=3.8"
15 | dynamic = ["version"]
16 |
17 | [project.urls]
18 | Homepage = "https://github.com/igrek51/wat"
19 |
20 |
21 | [build-system]
22 | requires = ["setuptools", "build", "wheel"]
23 | build-backend = "setuptools.build_meta"
24 |
25 | [tool.setuptools.dynamic]
26 | version = {attr = "wat.version.__version__"}
27 |
28 | [tool.setuptools]
29 | include-package-data = true
30 |
31 | [tool.setuptools.packages.find]
32 | where = ["."] # list of folders that contain the packages (["."] by default)
33 | include = ["wat*"] # package names should match these glob patterns (["*"] by default)
34 | exclude = ["wat.inspection.insta.*"] # exclude packages matching these glob patterns
35 | namespaces = false # to disable scanning PEP 420 namespaces (true by default)
36 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | setuptools==72.1.0
2 | setuptools_scm==8.1.0
3 | build==1.2.1
4 | pytest==8.2.0
5 | coverage==7.5.1
6 | twine==5.1.1
7 | wheel==0.43.0
8 | mkdocs==1.6.0
9 | mkdocs-material==9.5.22
10 | backoff==2.2.1
11 | pydantic==2.7.1
12 | nuclear>=2.2.0
13 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup()
4 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/tests/__init__.py
--------------------------------------------------------------------------------
/tests/asserts.py:
--------------------------------------------------------------------------------
1 | import re
2 | import sys
3 | from io import StringIO
4 | from typing import Optional
5 | import logging
6 |
7 | from nuclear.sublog import init_logs
8 | from nuclear.utils.strings import strip_ansi_colors
9 |
10 |
11 | class StdoutCap:
12 | def __init__(self):
13 | init_logs()
14 | logger = logging.getLogger('nuclear.sublog')
15 | logger.setLevel(logging.DEBUG)
16 | logger.propagate = False
17 | self.extra_handler = logging.getLogger().handlers[0]
18 | self.extra_handler.setLevel(logging.DEBUG)
19 | formatter = logging.getLogger().handlers[0].formatter
20 | self.extra_handler.setFormatter(formatter)
21 | logger.addHandler(self.extra_handler)
22 |
23 | # mock output
24 | self.new_out, self.new_err = StringIO(), StringIO()
25 | self.old_out, self.old_err = sys.stdout, sys.stderr
26 |
27 | # capture output from loggers
28 | self.old_handler, self.new_handler = None, None
29 | self.logger = logger
30 | handler = get_logger_handler(self.logger)
31 | if handler is not None:
32 | self.old_handler = handler
33 | self.new_handler = logging.StreamHandler(self.new_out)
34 | self.new_handler.setLevel(self.old_handler.level)
35 | self.new_handler.setFormatter(self.old_handler.formatter)
36 |
37 | def __enter__(self):
38 | sys.stdout, sys.stderr = self.new_out, self.new_err
39 | if self.old_handler is not None:
40 | self.logger.removeHandler(self.old_handler)
41 | self.logger.addHandler(self.new_handler)
42 | return self
43 |
44 | def __exit__(self, exc_type, exc_value, traceback):
45 | sys.stdout, sys.stderr = self.old_out, self.old_err
46 | sys.stdout.write(self.output())
47 | if self.old_handler is not None:
48 | self.logger.removeHandler(self.new_handler)
49 | self.logger.addHandler(self.old_handler)
50 | self.logger.removeHandler(self.extra_handler)
51 |
52 | def output(self) -> str:
53 | return self.new_out.getvalue() + self.new_err.getvalue()
54 |
55 | def stripped(self) -> str:
56 | return self.output().strip()
57 |
58 | def uncolor(self) -> str:
59 | matcher = re.compile(r'\x1b\[[0-9]+(;[0-9]+)?m')
60 | return matcher.sub('', self.output())
61 |
62 |
63 | def assert_multiline_match(text: str, regex_pattern: str):
64 | regex_lines = regex_pattern.strip().splitlines()
65 | text_lines = strip_ansi_colors(text).strip().splitlines()
66 |
67 | if len(text_lines) < len(regex_lines):
68 | assert False, f'Actual text has {len(regex_lines) - len(text_lines)} less lines than a pattern.\nActual text:\n{text}'
69 |
70 | if len(text_lines) > len(regex_lines):
71 | assert False, f'Actual text has {len(text_lines) - len(regex_lines)} more lines than a pattern.\nActual text:\n{text}'
72 |
73 | for index, (regex_line, text_line) in enumerate(zip(regex_lines, text_lines)):
74 | match = re.fullmatch(regex_line, text_line)
75 | assert match, f'Actual Line #{index+1}:\n{text_line}\n does not match the Regex pattern:\n{regex_line}'
76 |
77 |
78 | def get_logger_handler(logger: logging.Logger) -> Optional[logging.Handler]:
79 | c = logger
80 | while c:
81 | if c.handlers:
82 | return c.handlers[0]
83 | if not c.propagate:
84 | return None
85 | else:
86 | c = c.parent
87 | return None
88 |
--------------------------------------------------------------------------------
/tests/inspection/test_inspect.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from enum import Enum
3 | import math
4 | import os
5 | import re
6 | from typing import List
7 |
8 | from pydantic import BaseModel
9 |
10 | import wat
11 | from wat.inspection.inspection import inspect_format
12 | from tests.asserts import assert_multiline_match, strip_ansi_colors, StdoutCap
13 |
14 |
15 | def test_inspect_primitive_var():
16 | output = inspect_format(None)
17 | assert strip_ansi_colors(output) == """
18 | value: None
19 | type: NoneType
20 | """.strip()
21 |
22 | output = inspect_format([5])
23 | assert strip_ansi_colors(output) == """
24 | value: [
25 | 5,
26 | ]
27 | type: list
28 | len: 1
29 |
30 | Public attributes:
31 | def append(object, /) # Append object to the end of the list.
32 | def clear() # Remove all items from list.
33 | def copy() # Return a shallow copy of the list.
34 | def count(value, /) # Return number of occurrences of value.
35 | def extend(iterable, /) # Extend list by appending elements from the iterable.
36 | def index(value, start=0, stop=9223372036854775807, /) # Return first index of value.…
37 | def insert(index, object, /) # Insert object before index.
38 | def pop(index=-1, /) # Remove and return item at index (default last).…
39 | def remove(value, /) # Remove first occurrence of value.…
40 | def reverse() # Reverse *IN PLACE*.
41 | def sort(*, key=None, reverse=False) # Sort the list in ascending order and return None.…
42 | """.strip()
43 |
44 | output = inspect_format([5], dunder=True)
45 | assert "def __eq__(value, /) # Return self==value." in strip_ansi_colors(output)
46 |
47 | output = inspect_format('poo', short=True)
48 | assert_multiline_match(output, r'''
49 | value: 'poo'
50 | type: str
51 | len: 3
52 | ''')
53 |
54 |
55 | def test_inspect_instance():
56 | class Hero:
57 | """
58 | A hero
59 | """
60 | def __init__(self, name: str):
61 | self.a = name
62 |
63 | def shout(self, loudness: int) -> str:
64 | """Do something very very very very very very very very very very very very very very very very very stupid"""
65 | return self.a * loudness
66 |
67 | instance = Hero('batman')
68 | output = inspect_format(instance)
69 | assert_multiline_match(output, r'''
70 | value: \.Hero object at .*>
71 | type: test_inspect\.Hero
72 |
73 | Public attributes:
74 | a: str = 'batman'
75 |
76 | def shout\(loudness: int\) -> str \# Do something very very very very very very very very very very very very very very very very very st…
77 | ''')
78 |
79 | output = inspect_format(Hero)
80 | assert_multiline_match(output, r'''
81 | value: \.Hero'>
82 | type: type
83 | signature: class Hero\(name: str\)
84 | """A hero"""
85 |
86 | Public attributes:
87 | def shout\(self, loudness: int\) -> str \# Do something very very very very very very very very very very very very very very very very very st…
88 | ''')
89 |
90 |
91 | def test_inspect_function():
92 | def foo(a: int, b: str = 'bar') -> str:
93 | """
94 | Do something
95 | dumb
96 | """
97 | return a * b
98 |
99 | output = inspect_format(foo)
100 | print(output)
101 | assert_multiline_match(output, r'''
102 | value: \.foo at .*>
103 | type: function
104 | signature: def foo\(a: int, b: str = 'bar'\) -> str
105 | """
106 | Do something
107 | dumb
108 | """
109 | ''')
110 |
111 |
112 | def test_inspect_nested_dict():
113 | output = inspect_format({
114 | 'a': {
115 | 'b': {
116 | 'values': [2,5,3],
117 | },
118 | "empty_dict": {},
119 | "empty_list": [],
120 | 40: None,
121 | None: 42,
122 | },
123 | }, short=True)
124 | assert_multiline_match(output, r'''
125 | value: {
126 | 'a': {
127 | 'b': {
128 | 'values': \[
129 | 2,
130 | 5,
131 | 3,
132 | \],
133 | },
134 | 'empty_dict': {},
135 | 'empty_list': \[\],
136 | 40: None,
137 | None: 42,
138 | },
139 | }
140 | type: dict
141 | len: 1
142 | ''')
143 |
144 |
145 | def test_inspect_datetime_repr():
146 | output = inspect_format(datetime(2023, 8, 1), short=True)
147 | assert_multiline_match(output, r'''
148 | str: 2023-08-01 00:00:00
149 | repr: datetime.datetime\(2023, 8, 1, 0, 0\)
150 | type: datetime.datetime
151 | parents: datetime\.date
152 | ''')
153 |
154 |
155 | def test_inspect_long():
156 | output = inspect_format(datetime, long=True, code=True)
157 | output = strip_ansi_colors(output)
158 | lines = output.splitlines()
159 | assert "value: " in lines
160 | assert "type: type" in lines
161 | assert "signature: class datetime(…)" in lines
162 | assert "datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])" in lines
163 |
164 |
165 | def test_inspect_source_code():
166 | class Sorcerer:
167 | def __init__(self):
168 | self.level = 1
169 | def level_up(self):
170 | self.level += 1
171 |
172 | output = inspect_format(Sorcerer, code=True)
173 | output = strip_ansi_colors(output)
174 | lines = output.splitlines()
175 | assert "value: .Sorcerer'>" in lines
176 | assert "type: type" in lines
177 | assert "signature: class Sorcerer()" in lines
178 | assert "source code:" in lines
179 | assert " class Sorcerer:" in lines
180 | assert " self.level += 1" in lines
181 |
182 |
183 | def test_inspect_async_def():
184 | async def looper():
185 | pass
186 | output = inspect_format(looper, short=True)
187 | assert_multiline_match(output, r'''
188 | value: .looper at .*>
189 | type: function
190 | signature: async def looper\(\)
191 | ''')
192 |
193 |
194 | def test_wat_with_nothing():
195 | assert str(wat) == ''
196 | with StdoutCap() as capture:
197 | assert repr(wat) == ''
198 | assert 'Try wat / object or wat.modifiers / object to inspect an object. Modifiers are:' in capture.uncolor().splitlines()
199 |
200 |
201 | def test_wat_locals():
202 | _local_var = 23
203 | output = wat.str.gray.locals.splitlines()
204 | assert 'Local variables:' in output
205 | assert ' _local_var: int = 23' in output
206 |
207 | with StdoutCap() as capture:
208 | wat()
209 | assert 'Local variables' in capture.uncolor()
210 | assert ' _local_var: int = 23' in capture.uncolor()
211 |
212 |
213 | global_var = 23
214 |
215 | def test_wat_globals():
216 | output = wat.str.gray.globals.splitlines()
217 | assert 'Global variables:' in output
218 | assert ' global_var: int = 23' in output
219 | assert " __name__: str = 'test_inspect'" in output
220 |
221 |
222 | def test_wat_with_object():
223 | with StdoutCap() as capture:
224 | wat(short=True) / 'moo'
225 | assert_multiline_match(capture.output(), r'''
226 | value: 'moo'
227 | type: str
228 | len: 3
229 | ''')
230 |
231 | with StdoutCap() as capture:
232 | wat('moo', short=True)
233 | assert_multiline_match(capture.output(), r'''
234 | value: 'moo'
235 | type: str
236 | len: 3
237 | ''')
238 |
239 |
240 | def test_wat_with_short_long_modifiers():
241 | with StdoutCap() as capture:
242 | wat.short('moo')
243 | assert_multiline_match(capture.output(), r'''
244 | value: 'moo'
245 | type: str
246 | len: 3
247 | ''')
248 |
249 | with StdoutCap() as capture:
250 | wat.long / 'moo2'
251 | assert r'''
252 | def capitalize():
253 | """
254 | ''' in capture.output()
255 |
256 |
257 | def test_wat_with_multiple_modifiers():
258 | with StdoutCap() as capture:
259 | wat.dunder.code / re.match
260 |
261 | assert '''
262 | Dunder attributes:
263 | ''' in capture.output()
264 | assert ''' def __eq__(value, /)''' in capture.output()
265 |
266 | assert '''
267 | source code:
268 | def match(pattern, string, flags=0):
269 | ''' in capture.output()
270 |
271 |
272 | def test_wat_modifiers_all_but_nodocs():
273 | with StdoutCap() as capture:
274 | wat.all.short.nodocs / re.match
275 | assert_multiline_match(capture.stripped(), r'''
276 | caller file: .*/tests/inspection/test_inspect.py:\d+
277 | caller expression: wat\.all\.short\.nodocs / re\.match
278 | value:
279 | type: function
280 | signature: def match\(pattern, string, flags=0\)
281 | source code:
282 | def match\(pattern, string, flags=0\):
283 | """.*
284 | .*"""
285 | .*
286 | ''')
287 |
288 |
289 | def test_list_parent_classes():
290 | class Parent(str, Enum):
291 | FIRST = 'first'
292 |
293 | output = inspect_format(Parent.FIRST, short=True)
294 | assert_multiline_match(output, r'''
295 | str: '(Parent\.FIRST|first)'
296 | repr:
297 | type: test_inspect\.Parent
298 | parents: str, enum\.Enum
299 | len: 5
300 | ''')
301 |
302 |
303 | def test_list_deep_mro_classes():
304 | class Grand(object):
305 | pass
306 |
307 | class Father(Grand):
308 | pass
309 |
310 | class Son(Father):
311 | pass
312 |
313 | output = inspect_format(Son(), short=True)
314 | assert_multiline_match(output, r'''
315 | value: .Son object at .*>
316 | type: test_inspect.Son
317 | parents: test_inspect.Father, test_inspect.Grand
318 | ''')
319 |
320 |
321 | def test_pydantic_class():
322 | class Person(BaseModel):
323 | name: str
324 |
325 | output = inspect_format(Person(name='george'), short=True)
326 | assert_multiline_match(output, r'''
327 | str: name='george'
328 | repr: Person\(name='george'\)
329 | type: test_inspect\.Person
330 | parents: pydantic\.main\.BaseModel
331 | ''')
332 |
333 |
334 | def test_returning_inspected_object():
335 | assert wat.short.ret / 'hello' == 'hello'
336 |
337 |
338 | def test_listing_private_attributes():
339 | class Foo:
340 | def __init__(self, name: str):
341 | self._name = name
342 |
343 | def _private_method(self):
344 | pass
345 |
346 | output = inspect_format(Foo('bar'))
347 | assert_multiline_match(output, r'''
348 | value: \.Foo object at .*>
349 | type: test_inspect\.Foo
350 |
351 | Private attributes:
352 | _name: str = 'bar'
353 |
354 | def _private_method\(\)
355 | ''')
356 |
357 |
358 | def test_backwards_wat_wat_import():
359 | from wat import wat
360 | assert wat.ret / 'foo' == 'foo'
361 |
362 |
363 | def test_wat_return_output():
364 | result = wat.short.str / 'foo'
365 | assert_multiline_match(result, r'''
366 | value: 'foo'
367 | type: str
368 | len: 3
369 | ''')
370 |
371 |
372 | def test_colorful_output():
373 | try:
374 | os.environ['WAT_COLOR'] = 'false'
375 | output = wat.str / None
376 | assert output == """value: None
377 | type: NoneType"""
378 |
379 | os.environ['WAT_COLOR'] = 'true'
380 | output = wat.str / None
381 | assert output == """\x1b[1;34mvalue:\x1b[0m \x1b[0;35mNone\x1b[0m
382 | \x1b[1;34mtype:\x1b[0m \x1b[0;33mNoneType\x1b[0m"""
383 | finally:
384 | os.environ['WAT_COLOR'] = ''
385 |
386 |
387 | def test_inspect_overriden_len():
388 | class Foo:
389 | def __len__(self):
390 | return 4
391 |
392 | output = inspect_format(Foo())
393 | assert_multiline_match(output, r'''
394 | value: \.Foo object at .*>
395 | type: test_inspect\.Foo
396 | len: 4
397 | ''')
398 |
399 |
400 | def test_catch_len_on_str_type():
401 | output = (wat.str.short / str).splitlines()
402 | assert "value: " in output
403 | assert "type: type" in output
404 | assert "signature: class str(…)" in output
405 |
406 |
407 | def test_retrieve_caller_info_type():
408 | output = wat.caller.short.str / math.sqrt(2+2)
409 | assert_multiline_match(output, r'''
410 | caller file: .*/tests/inspection/test_inspect.py:\d+
411 | caller expression: output = wat\.caller\.short\.str / math\.sqrt\(2\+2\)
412 | value: 2.0
413 | type: float
414 | ''')
415 |
416 |
417 | def test_modifiers_as_keywords():
418 | output = wat(4, short=True, str=True, gray=True)
419 | assert_multiline_match(output, r'''
420 | value: 4
421 | type: int
422 | ''')
423 | assert wat(4, ret=True) == 4
424 |
425 |
426 | def test_modifiers_long_nodocs():
427 | output = wat.long.nodocs.str / str
428 | lines = output.splitlines()
429 | assert '"""' not in lines
430 | for line in lines:
431 | assert ' # ' not in line
432 | assert '"""' not in line
433 | assert ' def capitalize(self, /)' in lines
434 |
435 |
436 | def test_short_list_attr_preview():
437 | class Foo:
438 | columns: List[str] = ['123'] * 20
439 | output = inspect_format(Foo())
440 | assert_multiline_match(output, r'''
441 | value: <.*.Foo object at .*>
442 | type: test_inspect\.Foo
443 |
444 | Public attributes:
445 | columns: list = \['123', '123', .*…
446 | ''')
447 |
448 |
449 | def test_hide_private_attrs():
450 | class Foo:
451 | __dunder: str = 'mifflin'
452 | _private_one: str = 'two'
453 | public: str = 'pub'
454 |
455 | output = wat.public.str / Foo
456 | assert_multiline_match(output, r'''
457 | value: \.Foo'>
458 | type: type
459 | signature: class Foo\(\)
460 |
461 | Public attributes:
462 | public: str = 'pub'
463 | ''')
464 |
--------------------------------------------------------------------------------
/tests/inspection/test_instaload.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import zlib
3 | from pathlib import Path
4 |
5 | from tests.asserts import assert_multiline_match, StdoutCap
6 |
7 |
8 | def test_load_instaload_snippet():
9 | code = Path('utils/insta/instaload.txt').read_text()
10 | snippet = zlib.decompress(base64.b64decode(code)).decode()
11 | assert 'wat=Wat()' in snippet.splitlines()
12 |
13 | exec(snippet, globals())
14 | with StdoutCap() as capture:
15 | wat.short / 'moo'
16 | assert_multiline_match(capture.output(), r'''
17 | value: 'moo'
18 | type: str
19 | len: 3
20 | ''')
21 |
22 |
23 | def test_load_from_magic_glyph():
24 | glyph = Path('utils/insta/magic_glyph.md').read_text()
25 | exec(zlib.decompress(bytes(ord(c)&255 for c in glyph[1:])).decode(), globals())
26 | assert wat.short.str / 'WAT is going on?' == '''value: 'WAT is going on?'
27 | type: str
28 | len: 16'''
29 |
--------------------------------------------------------------------------------
/utils/demo/.gitignore:
--------------------------------------------------------------------------------
1 | wat-demo.mp4
2 |
--------------------------------------------------------------------------------
/utils/demo/livekey.py:
--------------------------------------------------------------------------------
1 | import random
2 | import time
3 |
4 | from nuclear import shell, logger
5 |
6 |
7 | def key_type(text, key_delay: float):
8 | text = text.replace('"', '\\"')
9 | for character in text:
10 | shell(f'xdotool key type "{character}"')
11 | time.sleep(key_delay * random.uniform(0.5, 1))
12 |
13 |
14 | def key_enter():
15 | shell('xdotool key Return')
16 |
17 |
18 | def type_line(line: str, key_delay: float):
19 | key_type(line.strip(), key_delay)
20 | time.sleep(key_delay * 3)
21 | key_enter()
22 |
23 |
24 | def countdown():
25 | for i in range(3):
26 | logger.info(f'Starting in {3-i}...')
27 | time.sleep(1)
28 | logger.info('Action!')
29 |
30 |
31 | def animate_commands(commands: list[str], key_delay: float = 0.2, line_delay: float = 1.5):
32 | countdown()
33 | for command in commands:
34 | type_line(command, key_delay)
35 | time.sleep(line_delay)
36 | logger.info('Cut!')
37 |
--------------------------------------------------------------------------------
/utils/demo/run-demo-inspect.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from livekey import animate_commands
3 |
4 |
5 | commands = [
6 | 'python',
7 | 'import wat',
8 | 'wat',
9 | 'wat / {7}',
10 | 'wat.short / (1,)',
11 | 'wat.s / {}',
12 | 'import re',
13 | 'wat / re',
14 | 'wat / re.match',
15 | 'wat.locals',
16 | ]
17 |
18 |
19 | if __name__ == '__main__':
20 | animate_commands(commands, key_delay=0.1, line_delay=1.0)
21 |
22 | """
23 | - Run script: python utils/demo/run-demo-inspect.py
24 | - Record with OBS recorder
25 | - Upload MP4 to Github issue: https://github.com/igrek51/wat/issues/1
26 | - Get source URL like:
27 |