├── .github └── workflows │ └── ci.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── Makefile ├── README.md ├── annotate.json ├── images ├── colors.png ├── presentation_v2.gif ├── repr_str.png └── search.gif ├── pdir ├── __init__.py ├── _internal_utils.py ├── api.py ├── attr_category.py ├── color.py ├── configuration.py ├── constants.py └── format.py ├── pdm.lock ├── pyproject.toml ├── tests ├── conftest.py ├── data │ ├── config_1.ini │ ├── config_2.ini │ ├── config_auto_color.ini │ ├── config_disable_color.ini │ ├── config_enable_color.ini │ ├── empty_config.ini │ ├── error_config_1.ini │ └── error_config_2.ini ├── interactive_test.py ├── m.py ├── test_buggy_attrs.py ├── test_container.py ├── test_disbale_color.py ├── test_filters.py ├── test_pdir_format.py ├── test_search.py ├── test_slots.py └── test_user_config.py └── tox.ini /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "images/**" 7 | - "*.md" 8 | push: 9 | branches: 10 | - master 11 | paths-ignore: 12 | - "images/**" 13 | - "*.md" 14 | workflow_dispatch: 15 | 16 | jobs: 17 | Testing: 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | matrix: 21 | python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] 22 | os: [ubuntu-latest, macOS-latest, windows-latest] 23 | 24 | steps: 25 | - uses: actions/checkout@v1 26 | - name: Set up PDM 27 | uses: pdm-project/setup-pdm@main 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Cache pypackages 32 | uses: actions/cache@v2 33 | with: 34 | path: __pypackages__ 35 | key: pypackages-${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('pdm.lock') }} 36 | restore-keys: | 37 | pypackages-${{ matrix.os }}-${{ matrix.python-version }}- 38 | 39 | - name: Install dependencies 40 | run: pdm install -v 41 | # On Windows, interactive test doesn't work, so exclude it. 42 | 43 | - name: Run tests 44 | run: pdm run tox -v 45 | 46 | - name: Interactive tests 47 | if: runner.os != 'Windows' 48 | run: pdm run tox -eextra -v 49 | -------------------------------------------------------------------------------- /.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 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | .pytest_cache/ 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | .idea 46 | .vscode 47 | 48 | # Rope 49 | .ropeproject 50 | 51 | # Django stuff: 52 | *.log 53 | *.pot 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # pyenv 59 | .python-version 60 | 61 | # pip editable src 62 | src/ 63 | 64 | # mypy 65 | .mypy_cache/ 66 | 67 | # pdm 68 | .pdm.toml 69 | __pypackages__/ 70 | .pdm-python 71 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | Release History 2 | =============== 3 | 4 | 0.3.1(2018-10-25) 5 | ----------------- 6 | * Add support for `__slots__` (#44, #45) 7 | * Seperate `@staticmethod` with other descriptors(#38, #42) 8 | * Add `__post_init__` support 9 | 10 | Special thanks to @liwt31 for his great contribution. 11 | 12 | 0.3.0(2018-02-10) 13 | ----------------- 14 | * Add support for various filters (#37) 15 | 16 | 0.2.0(2017-04-04) 17 | ----------------- 18 | * Add support for color customization. (#14) 19 | 20 | 0.1.0(2017-03-16) 21 | ------------------ 22 | * Add support for ipython, ptpython and bpython (#4) 23 | 24 | 0.0.2(2017-03-11) 25 | --------- 26 | 27 | ### API Changes (Backward-Compatible) 28 | 29 | * Added a `case_sensitive` parameter into the `search` function (#5) 30 | 31 | ### Bugfixes 32 | * Error calling pdir(pandas.DataFrame) (#1) 33 | * Methods are now considered functions (#6) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 laike9m 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format dry_publish publist debug 2 | 3 | # Install packages for development. 4 | install_dev_packages: 5 | pip install pytest tox black flake8 pytest-annotate mypy pytest-mypy 6 | 7 | install: 8 | python2 setup.py install 9 | python3 setup.py install 10 | 11 | format: 12 | black --config pyproject.toml . 13 | 14 | publish_to_test: 15 | rm -rf dist/ 16 | pdm build 17 | pdm run twine upload --repository testpypi dist/* # Assuming .pypirc exists. 18 | 19 | publish: 20 | rm -rf dist/ 21 | pdm build 22 | pdm run twine upload --repository pypi dist/* # Assuming .pypirc exists. 23 | 24 | debug: 25 | pytest --pdb -s tests 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdir2: Pretty dir() printing with joy 2 | 3 | ![Build status](https://github.com/laike9m/pdir2/actions/workflows/ci.yml/badge.svg) 4 | [![Supported Python versions](https://img.shields.io/pypi/pyversions/pdir2.svg)](https://pypi.python.org/pypi/pdir2/) 5 | ![PyPI Version](https://img.shields.io/pypi/v/pdir2.svg) 6 | Code style: black 7 | 8 | Have you ever dreamed of a better output of `dir()`? I do. So I created this. 9 | 10 | ![](https://github.com/laike9m/pdir2/raw/master/images/presentation_v2.gif) 11 | 12 | ## Features 13 | 14 | - Attributes are grouped by types/functionalities, with beautiful colors. 15 | 16 | - Support color customization, [here's how](https://github.com/laike9m/pdir2/wiki/User-Configuration). 17 | 18 | - Support all platforms including Windows(Thanks to [colorama](https://github.com/tartley/colorama)). 19 | 20 | - Support [ipython](https://github.com/ipython/ipython), [ptpython](https://github.com/jonathanslenders/ptpython), [bpython](https://www.bpython-interpreter.org/) and [Jupyter Notebook](http://jupyter.org/)! See [wiki](https://github.com/laike9m/pdir2/wiki/REPL-Support) for details. 21 | 22 | - The return value of `pdir()` can still be used as a list of names. 23 | 24 | - ✨ Attribute searching 25 | 26 | You can search for certain names with `.s()` or `.search()`: 27 | 28 | ![](https://github.com/laike9m/pdir2/raw/master/images/search.gif) 29 | 30 | Search is case-insensitive by default. 31 | `search(name, case_sensitive=True)` does case-sensitive searching. 32 | 33 | - :star2: Attribute filtering 34 | 35 | `properties`: Find properties/variables defined in the inspected object. 36 | 37 | `methods`: Find methods/functions defined in the inspected object. 38 | 39 | `public`: Find public attributes. 40 | 41 | `own`: Find attributes that are not inherited from parent classes. 42 | 43 | These filters **can be chained!** Order does **NOT** matter. 44 | 45 | For example, use `pdir(obj).public.own.methods` to find all public own methods. 46 | 47 | You can also call `search` on the returned results. 48 | 49 | See a [complete example](https://github.com/laike9m/pdir2/wiki/Attribute-Filtering). 50 | 51 | ## Install 52 | 53 | ### Generic 54 | 55 | pip install pdir2 56 | 57 | About the name. I wanted to call it "pdir", but there's already one with this 58 | name on pypi. Mine is better, of course. 59 | 60 | ### Fedora 61 | 62 | dnf install python3-pdir2 63 | 64 | ## Automatic Import 65 | 66 | As a better alternative of `dir()`, it's more convenient to automatically import 67 | pdir2 when launching REPL. Luckily, Python provides a way to do this. In you `.bashrc`(or `.zshrc`), add this line: 68 | 69 | export PYTHONSTARTUP=$HOME/.pythonstartup 70 | 71 | Then, create `.pythonstartup` in your home folder. Add one line: 72 | 73 | import pdir 74 | 75 | Next time you launch REPL, `pdir()` is already there, Hooray! 76 | 77 | ## Development 78 | 79 | 1. Set up development environment 80 | 81 | - **PDM**: pdir2 uses [PDM](https://pdm.fming.dev/latest/) to manage dependencies, so you want to make sure it's installed. 82 | - **pyenv**: Since you need to test pdir2 on multiple Python versions, [pyenv](https://github.com/pyenv/pyenv) is highly recommended. Make sure you have Python 3.8, 3.9, 3.10 and 3.11 installed. 83 | 84 | 2. Install dev dependencies 85 | 86 | Simply run `pdm install`. 87 | 88 | If you want to work on a specific Python version, run `pdm use [PYTHON_VERSION]` first to switch PDM to that version (e.g. `pdm use 3.9` if you want to debug a Python 3.9 specific issue). 89 | 90 | 91 | 3. Run tests 92 | 93 | Run `pdm run tox` 94 | 95 | The guide may be incomplete. Please file bugs if you encounter any issues. 96 | -------------------------------------------------------------------------------- /annotate.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "pdir/_internal_utils.py", 4 | "line": 9, 5 | "func_name": "get_attr_from_dict", 6 | "type_comments": [ 7 | "(type, str) -> Dict[int, str]", 8 | "(type, str) -> Dict[str, int]", 9 | "(type, str) -> property", 10 | "(type, str) -> wrapper_descriptor", 11 | "(type, str) -> int", 12 | "(type, str) -> getset_descriptor", 13 | "(type, str) -> function", 14 | "(type, str) -> member_descriptor", 15 | ], 16 | "samples": 116, 17 | }, 18 | { 19 | "path": "pdir/_internal_utils.py", 20 | "line": 25, 21 | "func_name": "is_slotted_attr", 22 | "type_comments": [ 23 | "(type, str) -> bool", 24 | "(object, str) -> bool", 25 | "(None, str) -> bool", 26 | "(test_filters.DerivedClass, str) -> bool", 27 | "(module, str) -> bool", 28 | "(test_pdir_format.T, str) -> bool", 29 | "(test_buggy_attrs.ClassWithUserDefinedDir, str) -> bool", 30 | "(test_buggy_attrs.T, str) -> bool", 31 | ], 32 | "samples": 136, 33 | }, 34 | { 35 | "path": "pdir/_internal_utils.py", 36 | "line": 32, 37 | "func_name": "_get_repl_type", 38 | "type_comments": ["() -> pdir.constants.ReplType"], 39 | "samples": 44, 40 | }, 41 | { 42 | "path": "pdir/_internal_utils.py", 43 | "line": 44, 44 | "func_name": "is_bpython", 45 | "type_comments": ["() -> bool"], 46 | "samples": 44, 47 | }, 48 | { 49 | "path": "pdir/_internal_utils.py", 50 | "line": 48, 51 | "func_name": "is_ptpython", 52 | "type_comments": ["() -> bool"], 53 | "samples": 15, 54 | }, 55 | { 56 | "path": "pdir/api.py", 57 | "line": 28, 58 | "func_name": "PrettyDir", 59 | "type_comments": ["() -> None"], 60 | "samples": 4, 61 | }, 62 | { 63 | "path": "pdir/api.py", 64 | "line": 31, 65 | "func_name": "PrettyDir.__init__", 66 | "type_comments": [ 67 | "(object, None) -> None", 68 | "(type, None) -> None", 69 | "(test_buggy_attrs.ClassWithUserDefinedDir, None) -> None", 70 | "(test_buggy_attrs.T, None) -> None", 71 | "(object, List[pdir.api.PrettyAttribute]) -> None", 72 | "(None, None) -> None", 73 | "(test_filters.DerivedClass, None) -> None", 74 | "(test_filters.DerivedClass, List[pdir.api.PrettyAttribute]) -> None", 75 | ], 76 | "samples": 53, 77 | }, 78 | { 79 | "path": "pdir/api.py", 80 | "line": 58, 81 | "func_name": "PrettyDir.__repr__", 82 | "type_comments": ["() -> str"], 83 | "samples": 15, 84 | }, 85 | { 86 | "path": "pdir/api.py", 87 | "line": 65, 88 | "func_name": "PrettyDir.__len__", 89 | "type_comments": ["() -> int"], 90 | "samples": 4, 91 | }, 92 | { 93 | "path": "pdir/api.py", 94 | "line": 68, 95 | "func_name": "PrettyDir.__getitem__", 96 | "type_comments": [ 97 | "(int) -> pyannotate_runtime.collect_types.NoReturnType", 98 | "(int) -> str", 99 | ], 100 | "samples": 14, 101 | }, 102 | { 103 | "path": "pdir/api.py", 104 | "line": 74, 105 | "func_name": "PrettyDir.search", 106 | "type_comments": ["(str, bool) -> pdir.api.PrettyDir"], 107 | "samples": 8, 108 | }, 109 | { 110 | "path": "pdir/api.py", 111 | "line": 103, 112 | "func_name": "properties", 113 | "type_comments": ["() -> pdir.api.PrettyDir"], 114 | "samples": 4, 115 | }, 116 | { 117 | "path": "pdir/api.py", 118 | "line": 118, 119 | "func_name": "methods", 120 | "type_comments": ["() -> pdir.api.PrettyDir"], 121 | "samples": 1, 122 | }, 123 | { 124 | "path": "pdir/api.py", 125 | "line": 133, 126 | "func_name": "public", 127 | "type_comments": ["() -> pdir.api.PrettyDir"], 128 | "samples": 4, 129 | }, 130 | { 131 | "path": "pdir/api.py", 132 | "line": 140, 133 | "func_name": "own", 134 | "type_comments": ["() -> pdir.api.PrettyDir"], 135 | "samples": 4, 136 | }, 137 | { 138 | "path": "pdir/api.py", 139 | "line": 162, 140 | "func_name": "PrettyAttribute", 141 | "type_comments": ["() -> None"], 142 | "samples": 4, 143 | }, 144 | { 145 | "path": "pdir/api.py", 146 | "line": 163, 147 | "func_name": "PrettyAttribute.__init__", 148 | "type_comments": [ 149 | "(str, Tuple[pdir.attr_category.AttrCategory], int) -> None", 150 | "(str, Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory], property) -> None", 151 | "(str, Tuple[pdir.attr_category.AttrCategory], Dict[int, str]) -> None", 152 | "(str, Tuple[pdir.attr_category.AttrCategory], Dict[str, int]) -> None", 153 | "(str, Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory], function) -> None", 154 | "(str, Tuple[pdir.attr_category.AttrCategory], function) -> None", 155 | "(str, Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory], getset_descriptor) -> None", 156 | "(str, Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory], member_descriptor) -> None", 157 | ], 158 | "samples": 136, 159 | }, 160 | { 161 | "path": "pdir/api.py", 162 | "line": 183, 163 | "func_name": "PrettyAttribute.get_oneline_doc", 164 | "type_comments": ["() -> str"], 165 | "samples": 136, 166 | }, 167 | { 168 | "path": "pdir/attr_category.py", 169 | "line": 46, 170 | "func_name": "AttrCategory.__str__", 171 | "type_comments": ["() -> str"], 172 | "samples": 30, 173 | }, 174 | { 175 | "path": "pdir/attr_category.py", 176 | "line": 57, 177 | "func_name": "category_match", 178 | "type_comments": [ 179 | "(Tuple[pdir.attr_category.AttrCategory], pdir.attr_category.AttrCategory) -> bool", 180 | "(Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory], pdir.attr_category.AttrCategory) -> bool", 181 | "(Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory], pdir.attr_category.AttrCategory) -> bool", 182 | ], 183 | "samples": 41, 184 | }, 185 | { 186 | "path": "pdir/attr_category.py", 187 | "line": 218, 188 | "func_name": "wrapped", 189 | "type_comments": [ 190 | "(str, property, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 191 | "(str, function, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 192 | "(str, getset_descriptor, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 193 | "(str, member_descriptor, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 194 | "(str, Dict[str, int], type) -> Tuple[pdir.attr_category.AttrCategory]", 195 | "(str, int, type) -> Tuple[pdir.attr_category.AttrCategory]", 196 | "(str, Dict[int, str], type) -> Tuple[pdir.attr_category.AttrCategory]", 197 | "(str, function, type) -> Tuple[pdir.attr_category.AttrCategory]", 198 | ], 199 | "samples": 136, 200 | }, 201 | { 202 | "path": "pdir/attr_category.py", 203 | "line": 233, 204 | "func_name": "get_attr_category", 205 | "type_comments": [ 206 | "(str, property, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 207 | "(str, function, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 208 | "(str, getset_descriptor, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 209 | "(str, member_descriptor, type) -> Tuple[pdir.attr_category.AttrCategory, pdir.attr_category.AttrCategory]", 210 | "(str, Dict[str, int], type) -> pdir.attr_category.AttrCategory", 211 | "(str, int, type) -> pdir.attr_category.AttrCategory", 212 | "(str, Dict[int, str], type) -> pdir.attr_category.AttrCategory", 213 | "(str, function, type) -> pdir.attr_category.AttrCategory", 214 | ], 215 | "samples": 136, 216 | }, 217 | { 218 | "path": "pdir/attr_category.py", 219 | "line": 237, 220 | "func_name": "is_descriptor", 221 | "type_comments": [ 222 | "(property) -> bool", 223 | "(int) -> bool", 224 | "(member_descriptor) -> bool", 225 | "(test_buggy_attrs.D) -> bool", 226 | "(test_buggy_attrs.RevealAccess) -> bool", 227 | "(str) -> bool", 228 | "(Dict[int, str]) -> bool", 229 | "(Dict[str, int]) -> bool", 230 | ], 231 | "samples": 77, 232 | }, 233 | { 234 | "path": "pdir/color.py", 235 | "line": 4, 236 | "func_name": "_Color", 237 | "type_comments": ["() -> None"], 238 | "samples": 7, 239 | }, 240 | { 241 | "path": "pdir/color.py", 242 | "line": 5, 243 | "func_name": "_Color.__init__", 244 | "type_comments": ["(int, bool) -> None"], 245 | "samples": 35, 246 | }, 247 | { 248 | "path": "pdir/color.py", 249 | "line": 9, 250 | "func_name": "_Color.wrap_text", 251 | "type_comments": ["(str) -> str"], 252 | "samples": 44, 253 | }, 254 | { 255 | "path": "pdir/color.py", 256 | "line": 19, 257 | "func_name": "_Color.__eq__", 258 | "type_comments": ["(pdir.color._Color) -> bool"], 259 | "samples": 12, 260 | }, 261 | { 262 | "path": "pdir/configuration.py", 263 | "line": 26, 264 | "func_name": "Configuration", 265 | "type_comments": ["() -> None"], 266 | "samples": 7, 267 | }, 268 | { 269 | "path": "pdir/configuration.py", 270 | "line": 35, 271 | "func_name": "Configuration.__init__", 272 | "type_comments": [ 273 | "() -> pyannotate_runtime.collect_types.NoReturnType", 274 | "() -> None", 275 | ], 276 | "samples": 7, 277 | }, 278 | { 279 | "path": "pdir/configuration.py", 280 | "line": 39, 281 | "func_name": "uniform_color", 282 | "type_comments": ["() -> pdir.color._Color", "() -> None"], 283 | "samples": 7, 284 | }, 285 | { 286 | "path": "pdir/configuration.py", 287 | "line": 43, 288 | "func_name": "category_color", 289 | "type_comments": [ 290 | "() -> pdir.color._Color", 291 | "() -> pdir.color._Color", 292 | "() -> pdir.color._Color", 293 | ], 294 | "samples": 3, 295 | }, 296 | { 297 | "path": "pdir/configuration.py", 298 | "line": 47, 299 | "func_name": "attribute_color", 300 | "type_comments": [ 301 | "() -> pdir.color._Color", 302 | "() -> pdir.color._Color", 303 | "() -> pdir.color._Color", 304 | ], 305 | "samples": 3, 306 | }, 307 | { 308 | "path": "pdir/configuration.py", 309 | "line": 51, 310 | "func_name": "comma_color", 311 | "type_comments": [ 312 | "() -> pdir.color._Color", 313 | "() -> pdir.color._Color", 314 | "() -> pdir.color._Color", 315 | ], 316 | "samples": 3, 317 | }, 318 | { 319 | "path": "pdir/configuration.py", 320 | "line": 55, 321 | "func_name": "doc_color", 322 | "type_comments": [ 323 | "() -> pdir.color._Color", 324 | "() -> pdir.color._Color", 325 | "() -> pdir.color._Color", 326 | ], 327 | "samples": 3, 328 | }, 329 | { 330 | "path": "pdir/configuration.py", 331 | "line": 59, 332 | "func_name": "slot_color", 333 | "type_comments": [ 334 | "() -> pdir.color._Color", 335 | "() -> pdir.color._Color", 336 | "() -> pdir.color._Color", 337 | ], 338 | "samples": 3, 339 | }, 340 | { 341 | "path": "pdir/configuration.py", 342 | "line": 63, 343 | "func_name": "Configuration._load", 344 | "type_comments": [ 345 | "() -> pyannotate_runtime.collect_types.NoReturnType", 346 | "() -> None", 347 | ], 348 | "samples": 7, 349 | }, 350 | { 351 | "path": "pdir/constants.py", 352 | "line": 7, 353 | "func_name": "ReplType", 354 | "type_comments": ["() -> None"], 355 | "samples": 7, 356 | }, 357 | { 358 | "path": "pdir/constants.py", 359 | "line": 20, 360 | "func_name": "_ClassWithSlot", 361 | "type_comments": ["() -> None"], 362 | "samples": 7, 363 | }, 364 | { 365 | "path": "pdir/format.py", 366 | "line": 14, 367 | "func_name": "format_pattrs", 368 | "type_comments": ["(List) -> str", "(List[pdir.api.PrettyAttribute]) -> str"], 369 | "samples": 15, 370 | }, 371 | { 372 | "path": "pdir/format.py", 373 | "line": 32, 374 | "func_name": "_format_single_line", 375 | "type_comments": [ 376 | "(pdir.attr_category.AttrCategory, itertools._grouper) -> str" 377 | ], 378 | "samples": 28, 379 | }, 380 | { 381 | "path": "pdir/format.py", 382 | "line": 41, 383 | "func_name": "_format_multiline_with_doc", 384 | "type_comments": [ 385 | "(pdir.attr_category.AttrCategory, itertools._grouper) -> str" 386 | ], 387 | "samples": 8, 388 | }, 389 | { 390 | "path": "pdir/format.py", 391 | "line": 54, 392 | "func_name": "_format_descriptor", 393 | "type_comments": [ 394 | "(pdir.attr_category.AttrCategory, itertools._grouper) -> str" 395 | ], 396 | "samples": 1, 397 | }, 398 | { 399 | "path": "tests/m.py", 400 | "line": 5, 401 | "func_name": "OOO", 402 | "type_comments": ["() -> None"], 403 | "samples": 1, 404 | }, 405 | { 406 | "path": "tests/test_buggy_attrs.py", 407 | "line": 9, 408 | "func_name": "test_dataframe", 409 | "type_comments": ["() -> None"], 410 | "samples": 1, 411 | }, 412 | { 413 | "path": "tests/test_buggy_attrs.py", 414 | "line": 18, 415 | "func_name": "test_type", 416 | "type_comments": ["() -> None"], 417 | "samples": 1, 418 | }, 419 | { 420 | "path": "tests/test_buggy_attrs.py", 421 | "line": 26, 422 | "func_name": "test_list", 423 | "type_comments": ["() -> None"], 424 | "samples": 1, 425 | }, 426 | { 427 | "path": "tests/test_buggy_attrs.py", 428 | "line": 37, 429 | "func_name": "D.__init__", 430 | "type_comments": ["() -> None"], 431 | "samples": 1, 432 | }, 433 | { 434 | "path": "tests/test_buggy_attrs.py", 435 | "line": 53, 436 | "func_name": "RevealAccess.__init__", 437 | "type_comments": ["(int, str) -> None"], 438 | "samples": 1, 439 | }, 440 | { 441 | "path": "tests/test_buggy_attrs.py", 442 | "line": 57, 443 | "func_name": "RevealAccess.__get__", 444 | "type_comments": ["(None, type) -> int"], 445 | "samples": 1, 446 | }, 447 | { 448 | "path": "tests/test_buggy_attrs.py", 449 | "line": 69, 450 | "func_name": "test_descriptor", 451 | "type_comments": ["() -> None"], 452 | "samples": 1, 453 | }, 454 | { 455 | "path": "tests/test_buggy_attrs.py", 456 | "line": 70, 457 | "func_name": "T", 458 | "type_comments": ["() -> None"], 459 | "samples": 1, 460 | }, 461 | { 462 | "path": "tests/test_buggy_attrs.py", 463 | "line": 73, 464 | "func_name": "T.__init__", 465 | "type_comments": ["() -> None"], 466 | "samples": 1, 467 | }, 468 | { 469 | "path": "tests/test_buggy_attrs.py", 470 | "line": 105, 471 | "func_name": "test_override_dir", 472 | "type_comments": ["() -> None"], 473 | "samples": 1, 474 | }, 475 | { 476 | "path": "tests/test_buggy_attrs.py", 477 | "line": 108, 478 | "func_name": "ClassWithUserDefinedDir", 479 | "type_comments": ["() -> None"], 480 | "samples": 1, 481 | }, 482 | { 483 | "path": "tests/test_buggy_attrs.py", 484 | "line": 109, 485 | "func_name": "ClassWithUserDefinedDir.__dir__", 486 | "type_comments": ["() -> List[str]"], 487 | "samples": 4, 488 | }, 489 | { 490 | "path": "tests/test_container.py", 491 | "line": 4, 492 | "func_name": "test_acting_like_a_list", 493 | "type_comments": ["() -> None"], 494 | "samples": 1, 495 | }, 496 | { 497 | "path": "tests/test_container.py", 498 | "line": 17, 499 | "func_name": "test_acting_like_a_list_when_search", 500 | "type_comments": ["() -> None"], 501 | "samples": 1, 502 | }, 503 | { 504 | "path": "tests/test_container.py", 505 | "line": 28, 506 | "func_name": "test_attr_order", 507 | "type_comments": ["() -> None"], 508 | "samples": 1, 509 | }, 510 | { 511 | "path": "tests/test_filters.py", 512 | "line": 12, 513 | "func_name": "items_equal", 514 | "type_comments": ["(List[str], List[str]) -> bool"], 515 | "samples": 7, 516 | }, 517 | { 518 | "path": "tests/test_filters.py", 519 | "line": 45, 520 | "func_name": "test_properties", 521 | "type_comments": ["() -> None"], 522 | "samples": 1, 523 | }, 524 | { 525 | "path": "tests/test_filters.py", 526 | "line": 62, 527 | "func_name": "test_methods", 528 | "type_comments": ["() -> None"], 529 | "samples": 1, 530 | }, 531 | { 532 | "path": "tests/test_filters.py", 533 | "line": 115, 534 | "func_name": "test_public", 535 | "type_comments": ["() -> None"], 536 | "samples": 1, 537 | }, 538 | { 539 | "path": "tests/test_filters.py", 540 | "line": 129, 541 | "func_name": "test_own", 542 | "type_comments": ["() -> None"], 543 | "samples": 1, 544 | }, 545 | { 546 | "path": "tests/test_filters.py", 547 | "line": 144, 548 | "func_name": "test_chained_filters", 549 | "type_comments": ["() -> None"], 550 | "samples": 1, 551 | }, 552 | { 553 | "path": "tests/test_filters.py", 554 | "line": 155, 555 | "func_name": "test_order_of_chained_filters", 556 | "type_comments": ["() -> None"], 557 | "samples": 1, 558 | }, 559 | { 560 | "path": "tests/test_filters.py", 561 | "line": 174, 562 | "func_name": "test_filters_with_search", 563 | "type_comments": ["() -> None"], 564 | "samples": 1, 565 | }, 566 | { 567 | "path": "tests/test_pdir_format.py", 568 | "line": 7, 569 | "func_name": "test_pdir_module", 570 | "type_comments": ["() -> None"], 571 | "samples": 1, 572 | }, 573 | { 574 | "path": "tests/test_pdir_format.py", 575 | "line": 73, 576 | "func_name": "test_pdir_object", 577 | "type_comments": ["() -> None"], 578 | "samples": 1, 579 | }, 580 | { 581 | "path": "tests/test_pdir_format.py", 582 | "line": 74, 583 | "func_name": "T", 584 | "type_comments": ["() -> None"], 585 | "samples": 1, 586 | }, 587 | { 588 | "path": "tests/test_pdir_format.py", 589 | "line": 83, 590 | "func_name": "test_pdir_class", 591 | "type_comments": ["() -> None"], 592 | "samples": 1, 593 | }, 594 | { 595 | "path": "tests/test_pdir_format.py", 596 | "line": 90, 597 | "func_name": "T", 598 | "type_comments": ["() -> None"], 599 | "samples": 1, 600 | }, 601 | { 602 | "path": "tests/test_pdir_format.py", 603 | "line": 182, 604 | "func_name": "test_dir_without_argument", 605 | "type_comments": ["() -> None"], 606 | "samples": 1, 607 | }, 608 | { 609 | "path": "tests/test_pdir_format.py", 610 | "line": 203, 611 | "func_name": "test_slots", 612 | "type_comments": ["() -> None"], 613 | "samples": 1, 614 | }, 615 | { 616 | "path": "tests/test_pdir_format.py", 617 | "line": 204, 618 | "func_name": "A", 619 | "type_comments": ["() -> None"], 620 | "samples": 1, 621 | }, 622 | { 623 | "path": "tests/test_search.py", 624 | "line": 4, 625 | "func_name": "test_search_without_argument", 626 | "type_comments": ["() -> None"], 627 | "samples": 1, 628 | }, 629 | { 630 | "path": "tests/test_search.py", 631 | "line": 23, 632 | "func_name": "test_search_with_argument", 633 | "type_comments": ["() -> None"], 634 | "samples": 1, 635 | }, 636 | { 637 | "path": "tests/test_search.py", 638 | "line": 24, 639 | "func_name": "T", 640 | "type_comments": ["() -> None"], 641 | "samples": 1, 642 | }, 643 | { 644 | "path": "tests/test_slots.py", 645 | "line": 59, 646 | "func_name": "test_not_set", 647 | "type_comments": ["() -> None"], 648 | "samples": 1, 649 | }, 650 | { 651 | "path": "tests/test_slots.py", 652 | "line": 80, 653 | "func_name": "test_set_derive", 654 | "type_comments": ["() -> None"], 655 | "samples": 1, 656 | }, 657 | { 658 | "path": "tests/test_slots.py", 659 | "line": 95, 660 | "func_name": "test_set_base", 661 | "type_comments": ["() -> None"], 662 | "samples": 1, 663 | }, 664 | { 665 | "path": "tests/test_user_config.py", 666 | "line": 38, 667 | "func_name": "test_default_env_without_config", 668 | "type_comments": ["(str) -> None"], 669 | "samples": 1, 670 | }, 671 | { 672 | "path": "tests/test_user_config.py", 673 | "line": 44, 674 | "func_name": "test_set_env_without_config", 675 | "type_comments": ["(str) -> None"], 676 | "samples": 1, 677 | }, 678 | { 679 | "path": "tests/test_user_config.py", 680 | "line": 52, 681 | "func_name": "test_read_config", 682 | "type_comments": ["(str) -> None"], 683 | "samples": 1, 684 | }, 685 | { 686 | "path": "tests/test_user_config.py", 687 | "line": 63, 688 | "func_name": "test_read_config_from_custom_location", 689 | "type_comments": ["(str) -> None"], 690 | "samples": 1, 691 | }, 692 | { 693 | "path": "tests/test_user_config.py", 694 | "line": 74, 695 | "func_name": "test_uniform_color", 696 | "type_comments": ["(str) -> None"], 697 | "samples": 1, 698 | }, 699 | { 700 | "path": "tests/test_user_config.py", 701 | "line": 84, 702 | "func_name": "test_empty_config", 703 | "type_comments": ["(str) -> None"], 704 | "samples": 1, 705 | }, 706 | { 707 | "path": "tests/test_user_config.py", 708 | "line": 94, 709 | "func_name": "test_invalid_config_1", 710 | "type_comments": ["(str) -> None"], 711 | "samples": 1, 712 | }, 713 | { 714 | "path": "tests/test_user_config.py", 715 | "line": 102, 716 | "func_name": "test_invalid_config_2", 717 | "type_comments": ["(str) -> None"], 718 | "samples": 1, 719 | }, 720 | ] 721 | -------------------------------------------------------------------------------- /images/colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laike9m/pdir2/780d1c98acb124e102d2e10d24a844192fd149f0/images/colors.png -------------------------------------------------------------------------------- /images/presentation_v2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laike9m/pdir2/780d1c98acb124e102d2e10d24a844192fd149f0/images/presentation_v2.gif -------------------------------------------------------------------------------- /images/repr_str.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laike9m/pdir2/780d1c98acb124e102d2e10d24a844192fd149f0/images/repr_str.png -------------------------------------------------------------------------------- /images/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laike9m/pdir2/780d1c98acb124e102d2e10d24a844192fd149f0/images/search.gif -------------------------------------------------------------------------------- /pdir/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from .api import PrettyDir 3 | 4 | __author__ = 'laike9m ' 5 | 6 | sys.modules[__name__] = PrettyDir # type: ignore 7 | -------------------------------------------------------------------------------- /pdir/_internal_utils.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import sys 3 | from typing import Any 4 | 5 | from .constants import SLOT_TYPE, ReplType 6 | 7 | 8 | def get_attr_from_dict(inspected_obj: Any, attr_name: str) -> Any: 9 | """Ensures we get descriptor object instead of its return value.""" 10 | if inspect.isclass(inspected_obj): 11 | obj_list = [inspected_obj] + list(inspected_obj.__mro__) 12 | else: 13 | obj_list = [inspected_obj] + list(inspected_obj.__class__.__mro__) 14 | for obj in obj_list: 15 | if hasattr(obj, '__dict__') and attr_name in obj.__dict__: 16 | return obj.__dict__[attr_name] 17 | # This happens when user-defined __dir__ returns something that's not 18 | # in any __dict__. See test_override_dir. 19 | # Returns attr_name so that it's treated as a normal property. 20 | return attr_name 21 | 22 | 23 | def is_slotted_attr(child_obj: Any, attr_name: str) -> bool: 24 | return any( 25 | isinstance(getattr(obj, attr_name, None), SLOT_TYPE) 26 | for obj in list(child_obj.__class__.__mro__) 27 | ) 28 | 29 | 30 | def _get_repl_type() -> ReplType: 31 | if any(ReplType.PTPYTHON.value in key for key in sys.modules): 32 | return ReplType.PTPYTHON 33 | if any(ReplType.BPYTHON.value in key for key in sys.modules): 34 | return ReplType.BPYTHON 35 | try: 36 | __IPYTHON__ # type: ignore 37 | return ReplType.IPYTHON 38 | except NameError: 39 | return ReplType.PYTHON 40 | 41 | 42 | def is_bpython() -> bool: 43 | return _get_repl_type() == ReplType.BPYTHON 44 | 45 | 46 | def is_ptpython() -> bool: 47 | return _get_repl_type() == ReplType.PTPYTHON 48 | 49 | 50 | def get_first_sentence_of_docstring(obj: Any) -> str: 51 | """Attempt to get the first sentence from obj's docstring. 52 | 53 | There might be more than one sentence or some non-ending dots 54 | in docstring, so it's better to parse by `. ` rather than `.`. 55 | If no dots are found, original docstring will be returned. 56 | """ 57 | docstring = get_docstring_from_obj(obj) 58 | if not docstring: 59 | return '' 60 | 61 | joined = ' '.join(docstring.split('\n')) + ' ' 62 | try: 63 | first_sentence_end_pos = joined.index('. ') 64 | except ValueError: 65 | return joined.strip() 66 | 67 | return joined[: first_sentence_end_pos + 1] 68 | 69 | 70 | def get_docstring_from_obj(obj: Any) -> str: 71 | """ 72 | SystemError may occur on jpype objects, 73 | see https://github.com/laike9m/pdir2/pull/57. 74 | """ 75 | try: 76 | return inspect.getdoc(obj) or '' 77 | except Exception: 78 | return '' 79 | -------------------------------------------------------------------------------- /pdir/api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Convention: 3 | "attr" means the original attribute object. 4 | "pattr" means class PrettyAttribute instance. 5 | """ 6 | 7 | 8 | import platform 9 | from sys import _getframe 10 | from typing import Any, List, Optional, Tuple 11 | 12 | from . import format 13 | from ._internal_utils import ( 14 | get_attr_from_dict, 15 | get_first_sentence_of_docstring, 16 | is_ptpython, 17 | ) 18 | from .attr_category import AttrCategory, category_match, get_attr_category 19 | from .constants import DELETER, GETTER, SETTER, dummy_obj 20 | 21 | if platform.system() == 'Windows': 22 | from colorama import init # type: ignore 23 | 24 | init() # To support Windows. 25 | 26 | 27 | class PrettyDir: 28 | """Class that provides pretty dir and search API.""" 29 | 30 | def __init__( 31 | self, obj: Any = dummy_obj, pattrs: Optional[List['PrettyAttribute']] = None 32 | ) -> None: 33 | """ 34 | Args: 35 | obj: The object to inspect. 36 | pattrs: Used when returning search result. 37 | """ 38 | self.obj = obj 39 | if pattrs is None: 40 | if obj is dummy_obj: 41 | # User is calling dir() without arguments. 42 | attrs = _getframe(1).f_locals 43 | self.dir_result = sorted(list(attrs.keys())) 44 | else: 45 | self.dir_result = dir(self.obj) 46 | attrs = { 47 | name: get_attr_from_dict(self.obj, name) for name in self.dir_result 48 | } 49 | self.pattrs = [ 50 | PrettyAttribute(name, get_attr_category(name, attr, obj), attr) 51 | for name, attr in attrs.items() 52 | ] 53 | else: 54 | self.pattrs = pattrs 55 | self.dir_result = sorted([p.name for p in pattrs]) 56 | 57 | def __repr__(self) -> str: 58 | if not is_ptpython(): 59 | return format.format_pattrs(self.pattrs) 60 | 61 | print(format.format_pattrs(self.pattrs), end='') 62 | return '' 63 | 64 | def __len__(self) -> int: 65 | return len(self.dir_result) 66 | 67 | def __getitem__(self, index: int) -> str: 68 | return self.dir_result[index] 69 | 70 | def index(self, value): 71 | return self.dir_result.index(value) 72 | 73 | def search(self, term: str, case_sensitive: bool = False) -> 'PrettyDir': 74 | """Searches for names that match some pattern. 75 | 76 | Args: 77 | term: String used to match names. A name is returned if it matches 78 | the whole search term. 79 | case_sensitive: Boolean to match case or not, default is False 80 | (case insensitive). 81 | 82 | Return: 83 | A PrettyDir object with matched names. 84 | """ 85 | if case_sensitive: 86 | return PrettyDir( 87 | self.obj, [pattr for pattr in self.pattrs if term in pattr.name] 88 | ) 89 | term = term.lower() 90 | return PrettyDir( 91 | self.obj, [pattr for pattr in self.pattrs if term in pattr.name.lower()] 92 | ) 93 | 94 | s = search 95 | 96 | # Below methods "methods", "public", "own" can be chained when necessary. 97 | # That is, for listing all public methods that are not inherited, 98 | # use pdir(obj).public.own.methods 99 | # The order should not affect results. 100 | 101 | @property 102 | def properties(self) -> 'PrettyDir': 103 | """Returns all properties of the inspected object. 104 | 105 | Note that "properties" can mean "variables". 106 | """ 107 | return PrettyDir( 108 | self.obj, 109 | [ 110 | pattr 111 | for pattr in self.pattrs 112 | if category_match(pattr.category, AttrCategory.PROPERTY) 113 | ], 114 | ) 115 | 116 | @property 117 | def methods(self) -> 'PrettyDir': 118 | """Returns all methods of the inspected object. 119 | 120 | Note that "methods" can mean "functions" when inspecting a module. 121 | """ 122 | return PrettyDir( 123 | self.obj, 124 | [ 125 | pattr 126 | for pattr in self.pattrs 127 | if category_match(pattr.category, AttrCategory.FUNCTION) 128 | ], 129 | ) 130 | 131 | @property 132 | def public(self) -> 'PrettyDir': 133 | """Returns public attributes of the inspected object.""" 134 | return PrettyDir( 135 | self.obj, [pattr for pattr in self.pattrs if not pattr.name.startswith('_')] 136 | ) 137 | 138 | @property 139 | def own(self) -> 'PrettyDir': 140 | """Returns attributes that are not inhterited from parent classes. 141 | 142 | Now we only use a simple judgement, it is expected that many attributes 143 | not get returned, especially invoked on a module. 144 | 145 | For instance, there's no way to distinguish between properties that 146 | are initialized in instance class's __init__ and parent class's 147 | __init__(assuming super() is called). So we'll just leave it. 148 | """ 149 | return PrettyDir( 150 | self.obj, 151 | [ 152 | pattr 153 | for pattr in self.pattrs 154 | if pattr.name in type(self.obj).__dict__ 155 | or pattr.name in self.obj.__dict__ 156 | ], 157 | ) 158 | 159 | 160 | class PrettyAttribute: 161 | def __init__( 162 | self, name: str, category: Tuple[AttrCategory, ...], attr_obj: Any 163 | ) -> None: 164 | self.name = name 165 | self.category = category 166 | # Names are grouped by their category. When multiple categories exist, 167 | # pick the largest one which usually represents a more detailed 168 | # category. 169 | self.display_group = max(category) 170 | self.attr_obj = attr_obj 171 | self.doc = self.get_oneline_doc() 172 | # single category can not be a bare slot 173 | self.slotted = AttrCategory.SLOT in self.category 174 | 175 | def __repr__(self): 176 | return f'{self.name}: {self.category}' 177 | 178 | def get_oneline_doc(self) -> str: 179 | """ 180 | Doc doesn't necessarily mean doctring. It could be anything that 181 | should be put after the attr's name as an explanation. 182 | """ 183 | attr = self.attr_obj 184 | doc = get_first_sentence_of_docstring(attr) 185 | if self.display_group == AttrCategory.DESCRIPTOR: 186 | if isinstance(attr, property): 187 | doc_list = ['@property with getter'] 188 | if attr.fset: 189 | doc_list.append(SETTER) 190 | if attr.fdel: 191 | doc_list.append(DELETER) 192 | else: 193 | doc_list = ['class %s' % attr.__class__.__name__] 194 | if hasattr(attr, '__get__'): 195 | doc_list.append(GETTER) 196 | if hasattr(attr, '__set__'): 197 | doc_list.append(SETTER) 198 | if hasattr(attr, '__delete__'): 199 | doc_list.append(DELETER) 200 | doc_list[0] = ' '.join([doc_list[0], 'with', doc_list.pop(1)]) 201 | if doc: 202 | doc_list.append(doc) 203 | return ', '.join(doc_list) 204 | 205 | return doc 206 | -------------------------------------------------------------------------------- /pdir/attr_category.py: -------------------------------------------------------------------------------- 1 | import collections.abc 2 | import functools 3 | import inspect 4 | from enum import IntEnum, auto 5 | 6 | from ._internal_utils import is_slotted_attr 7 | 8 | from typing import Any 9 | from typing import Tuple 10 | from typing import Union 11 | 12 | 13 | # Detailed category should have larger values than general category. 14 | class AttrCategory(IntEnum): 15 | # Slot category: orthogonal to all other categories. 16 | SLOT = auto() 17 | # Basic category. 18 | CLASS = auto() 19 | # Often represents the internal function that's invoked: add -> __add__. 20 | FUNCTION = auto() 21 | EXCEPTION = auto() 22 | PROPERTY = auto() 23 | 24 | # Detailed category. 25 | MODULE_ATTRIBUTE = auto() 26 | SPECIAL_ATTRIBUTE = auto() 27 | ABSTRACT_CLASS = auto() 28 | MAGIC = auto() 29 | ARITHMETIC = auto() 30 | ITER = auto() 31 | CONTEXT_MANAGER = auto() 32 | OBJECT_CUSTOMIZATION = auto() 33 | RICH_COMPARISON = auto() 34 | ATTRIBUTE_ACCESS = auto() 35 | # TODO: We should probably call it "user-defined descriptor", cause pretty much 36 | # everything inside a class is a "descriptor". 37 | DESCRIPTOR = auto() 38 | DESCRIPTOR_CLASS = auto() 39 | STATIC_METHOD = auto() 40 | CLASS_CUSTOMIZATION = auto() 41 | CONTAINER = auto() 42 | COROUTINE = auto() 43 | COPY = auto() 44 | PICKLE = auto() 45 | PATTERN_MATCHING = auto() 46 | TYPING = auto() 47 | DECORATOR = auto() 48 | BUFFER = auto() 49 | 50 | def __str__(self) -> str: 51 | """ 52 | e.g. RICH_COMPARISON -> rich comparison 53 | """ 54 | return " ".join(self.name.split("_")).lower() 55 | 56 | 57 | def _always_true(obj: type) -> bool: 58 | return True 59 | 60 | 61 | def category_match( 62 | pattr_category: Union[Tuple[AttrCategory, ...], AttrCategory], 63 | target_category: AttrCategory, 64 | ) -> bool: 65 | if pattr_category == target_category: 66 | return True 67 | return isinstance(pattr_category, tuple) and target_category in pattr_category 68 | 69 | 70 | # Names that belong to different categories in different conditions. 71 | ATTR_MAP_CONDITIONAL = { 72 | "__reversed__": lambda obj: (AttrCategory.ITER, AttrCategory.FUNCTION) 73 | if isinstance(obj, collections.abc.Iterator) 74 | else (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 75 | "__iter__": lambda obj: (AttrCategory.ITER, AttrCategory.FUNCTION) 76 | if isinstance(obj, collections.abc.Iterator) 77 | else (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 78 | "__name__": lambda obj: (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY) 79 | if inspect.ismodule(obj) 80 | else (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 81 | } 82 | 83 | ATTR_MAP = { 84 | "__doc__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 85 | "__qualname__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 86 | "__module__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 87 | "__defaults__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 88 | "__code__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 89 | "__globals__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 90 | "__dict__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 91 | "__closure__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 92 | "__annotations__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 93 | "__kwdefaults__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 94 | "__func__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 95 | "__self__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 96 | "__bases__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 97 | "__class__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 98 | "__objclass__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 99 | "__slots__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 100 | "__weakref__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 101 | "__excepthook__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 102 | "__mro__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 103 | "__static_attributes__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 104 | "__firstlineno__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.PROPERTY), 105 | "__subclasses__": (AttrCategory.SPECIAL_ATTRIBUTE, AttrCategory.FUNCTION), 106 | "__next__": (AttrCategory.ITER, AttrCategory.FUNCTION), 107 | "__enter__": (AttrCategory.CONTEXT_MANAGER, AttrCategory.FUNCTION), 108 | "__exit__": (AttrCategory.CONTEXT_MANAGER, AttrCategory.FUNCTION), 109 | "__loader__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 110 | "__package__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 111 | "__spec__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 112 | "__path__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 113 | "__file__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 114 | "__cached__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 115 | "__all__": (AttrCategory.MODULE_ATTRIBUTE, AttrCategory.PROPERTY), 116 | "__abs__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 117 | "__add__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 118 | "__and__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 119 | "__complex__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 120 | "__divmod__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 121 | "__float__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 122 | "__floordiv__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 123 | "__iadd__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 124 | "__iand__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 125 | "__ifloordiv__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 126 | "__ilshift__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 127 | "__imatmul__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 128 | "__imod__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 129 | "__imul__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 130 | "__int__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 131 | "__invert__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 132 | "__ior__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 133 | "__ipow__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 134 | "__irshift__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 135 | "__isub__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 136 | "__itruediv__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 137 | "__ixor__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 138 | "__lshift__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 139 | "__matmul__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 140 | "__mod__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 141 | "__mul__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 142 | "__neg__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 143 | "__or__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 144 | "__pos__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 145 | "__pow__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 146 | "__radd__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 147 | "__rand__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 148 | "__rdivmod__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 149 | "__rfloordiv__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 150 | "__rlshift__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 151 | "__rmatmul__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 152 | "__rmod__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 153 | "__rmul__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 154 | "__ror__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 155 | "__round__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 156 | "__rpow__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 157 | "__rrshift__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 158 | "__rshift__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 159 | "__rsub__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 160 | "__rtruediv__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 161 | "__rxor__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 162 | "__sub__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 163 | "__truediv__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 164 | "__xor__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 165 | "__ceil__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 166 | "__floor__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 167 | "__trunc__": (AttrCategory.ARITHMETIC, AttrCategory.FUNCTION), 168 | "__init__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 169 | "__post_init__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 170 | "__new__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 171 | "__del__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 172 | "__repr__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 173 | "__str__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 174 | "__bytes__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 175 | "__format__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 176 | "__hash__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 177 | "__bool__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 178 | "__sizeof__": (AttrCategory.OBJECT_CUSTOMIZATION, AttrCategory.FUNCTION), 179 | "__lt__": (AttrCategory.RICH_COMPARISON, AttrCategory.FUNCTION), 180 | "__le__": (AttrCategory.RICH_COMPARISON, AttrCategory.FUNCTION), 181 | "__eq__": (AttrCategory.RICH_COMPARISON, AttrCategory.FUNCTION), 182 | "__ne__": (AttrCategory.RICH_COMPARISON, AttrCategory.FUNCTION), 183 | "__gt__": (AttrCategory.RICH_COMPARISON, AttrCategory.FUNCTION), 184 | "__ge__": (AttrCategory.RICH_COMPARISON, AttrCategory.FUNCTION), 185 | "__getattr__": (AttrCategory.ATTRIBUTE_ACCESS, AttrCategory.FUNCTION), 186 | "__getattribute__": (AttrCategory.ATTRIBUTE_ACCESS, AttrCategory.FUNCTION), 187 | "__setattr__": (AttrCategory.ATTRIBUTE_ACCESS, AttrCategory.FUNCTION), 188 | "__delattr__": (AttrCategory.ATTRIBUTE_ACCESS, AttrCategory.FUNCTION), 189 | "__dir__": (AttrCategory.ATTRIBUTE_ACCESS, AttrCategory.FUNCTION), 190 | "__get__": (AttrCategory.DESCRIPTOR_CLASS, AttrCategory.FUNCTION), 191 | "__set__": (AttrCategory.DESCRIPTOR_CLASS, AttrCategory.FUNCTION), 192 | "__delete__": (AttrCategory.DESCRIPTOR_CLASS, AttrCategory.FUNCTION), 193 | "__set_name__": (AttrCategory.DESCRIPTOR_CLASS, AttrCategory.FUNCTION), 194 | "__init_subclass__": (AttrCategory.CLASS_CUSTOMIZATION, AttrCategory.FUNCTION), 195 | "__prepare__": (AttrCategory.CLASS_CUSTOMIZATION, AttrCategory.FUNCTION), 196 | "__instancecheck__": (AttrCategory.CLASS_CUSTOMIZATION, AttrCategory.FUNCTION), 197 | "__subclasscheck__": (AttrCategory.CLASS_CUSTOMIZATION, AttrCategory.FUNCTION), 198 | "__subclasshook__": (AttrCategory.ABSTRACT_CLASS, AttrCategory.FUNCTION), 199 | "__isabstractmethod__": (AttrCategory.ABSTRACT_CLASS, AttrCategory.FUNCTION), 200 | "__abstractmethods__": (AttrCategory.ABSTRACT_CLASS, AttrCategory.PROPERTY), 201 | "__len__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 202 | "__length_hint__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 203 | "__getitem__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 204 | "__missing__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 205 | "__setitem__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 206 | "__delitem__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 207 | "__contains__": (AttrCategory.CONTAINER, AttrCategory.FUNCTION), 208 | "__await__": (AttrCategory.COROUTINE, AttrCategory.FUNCTION), 209 | "__aiter__": (AttrCategory.COROUTINE, AttrCategory.FUNCTION), 210 | "__anext__": (AttrCategory.COROUTINE, AttrCategory.FUNCTION), 211 | "__aenter__": (AttrCategory.COROUTINE, AttrCategory.FUNCTION), 212 | "__aexit__": (AttrCategory.COROUTINE, AttrCategory.FUNCTION), 213 | "__index__": (AttrCategory.MAGIC, AttrCategory.FUNCTION), 214 | "__call__": (AttrCategory.MAGIC, AttrCategory.FUNCTION), 215 | "__copy__": (AttrCategory.COPY, AttrCategory.FUNCTION), 216 | "__replace__": (AttrCategory.COPY, AttrCategory.FUNCTION), 217 | "__deepcopy__": (AttrCategory.COPY, AttrCategory.FUNCTION), 218 | "__getnewargs_ex__": (AttrCategory.PICKLE, AttrCategory.FUNCTION), 219 | "__getnewargs__": (AttrCategory.PICKLE, AttrCategory.FUNCTION), 220 | "__getstate__": (AttrCategory.PICKLE, AttrCategory.FUNCTION), 221 | "__setstate__": (AttrCategory.PICKLE, AttrCategory.FUNCTION), 222 | "__reduce__": (AttrCategory.PICKLE, AttrCategory.FUNCTION), 223 | "__reduce_ex__": (AttrCategory.PICKLE, AttrCategory.FUNCTION), 224 | "__match_args__": (AttrCategory.PATTERN_MATCHING, AttrCategory.PROPERTY), 225 | "__origin__": (AttrCategory.TYPING, AttrCategory.PROPERTY), 226 | "__args__": (AttrCategory.TYPING, AttrCategory.PROPERTY), 227 | "__parameters__": (AttrCategory.TYPING, AttrCategory.PROPERTY), 228 | "__class_getitem__": (AttrCategory.TYPING, AttrCategory.FUNCTION), 229 | "__final__": (AttrCategory.TYPING, AttrCategory.PROPERTY), 230 | "__orig_bases__": (AttrCategory.TYPING, AttrCategory.PROPERTY), 231 | "__type_params__": (AttrCategory.TYPING, AttrCategory.PROPERTY), 232 | "__wrapped__": (AttrCategory.DECORATOR, AttrCategory.PROPERTY), 233 | "__buffer__": (AttrCategory.BUFFER, AttrCategory.FUNCTION), 234 | "__release_buffer__": (AttrCategory.BUFFER, AttrCategory.FUNCTION), 235 | } 236 | 237 | 238 | def attr_category_postprocess(get_attr_category_func): 239 | """Unifies attr_category to a tuple, add AttrCategory.SLOT if needed.""" 240 | 241 | @functools.wraps(get_attr_category_func) 242 | def wrapped(name: str, attr: Any, obj: Any) -> Tuple[AttrCategory, ...]: 243 | category = get_attr_category_func(name, attr, obj) 244 | category = list(category) if isinstance(category, tuple) else [category] 245 | if is_slotted_attr(obj, name): 246 | # Refactoring all tuples to lists is not easy 247 | # and pleasant. Maybe do this in future if necessary 248 | category.append(AttrCategory.SLOT) 249 | return tuple(category) 250 | 251 | return wrapped 252 | 253 | 254 | @attr_category_postprocess 255 | def get_attr_category( 256 | name: str, attr: Any, obj: Any 257 | ) -> Union[Tuple[AttrCategory, ...], AttrCategory]: 258 | def is_descriptor(obj: Any) -> bool: 259 | return ( 260 | hasattr(obj, "__get__") 261 | or hasattr(obj, "__set__") 262 | or hasattr(obj, "__delete__") 263 | ) 264 | 265 | method_descriptor = type(list.append) 266 | 267 | if name in ATTR_MAP_CONDITIONAL: 268 | return ATTR_MAP_CONDITIONAL[name](obj) 269 | 270 | if name in ATTR_MAP: 271 | return ATTR_MAP[name] 272 | 273 | if inspect.isclass(attr): 274 | return ( 275 | AttrCategory.EXCEPTION 276 | if issubclass(attr, Exception) 277 | else AttrCategory.CLASS 278 | ) 279 | elif ( 280 | inspect.isfunction(attr) 281 | or inspect.ismethod(attr) 282 | or inspect.isbuiltin(attr) 283 | or isinstance(attr, method_descriptor) 284 | ): 285 | # Technically, method_descriptor is descriptor, but since they 286 | # act as functions, let's treat them as functions. 287 | return AttrCategory.FUNCTION 288 | elif isinstance(attr, staticmethod): 289 | return ( 290 | AttrCategory.DESCRIPTOR, 291 | AttrCategory.STATIC_METHOD, 292 | AttrCategory.FUNCTION, 293 | ) 294 | elif is_descriptor(attr): 295 | # Maybe add getsetdescriptor memberdescriptor in the future. 296 | return AttrCategory.DESCRIPTOR, AttrCategory.PROPERTY 297 | else: 298 | # attr that is neither function nor class is a normal variable, 299 | # and it's classified to property. 300 | return AttrCategory.PROPERTY 301 | -------------------------------------------------------------------------------- /pdir/color.py: -------------------------------------------------------------------------------- 1 | from ._internal_utils import is_bpython 2 | from typing_extensions import Protocol 3 | 4 | 5 | class _Renderable(Protocol): 6 | def wrap_text(self, text: str) -> str: 7 | pass 8 | 9 | def __eq__(self, other: object) -> bool: 10 | pass 11 | 12 | 13 | class _Color(_Renderable): 14 | def __init__(self, color_code: int, bright: bool = False) -> None: 15 | self.color_code = str(color_code) 16 | self.intensity = '1' if bright else '0' 17 | 18 | def wrap_text(self, text: str) -> str: 19 | if not is_bpython(): 20 | return f'\033[{self.intensity};{self.color_code}m{text}\033[0m' 21 | 22 | colored_text = f'\033[{self.color_code}m{text}\033[0m' 23 | if self.intensity == '0': 24 | return colored_text 25 | else: 26 | return '\033[1m' + colored_text 27 | 28 | def __eq__(self, other: object) -> bool: # type: ignore 29 | # __eq__ should work with any objects. 30 | # https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides 31 | if not isinstance(other, _Color): 32 | return False 33 | 34 | return self.color_code == other.color_code 35 | 36 | def __repr__(self): 37 | return f'\033[{self.color_code}m{"color"}\033[0m' 38 | 39 | 40 | class _ColorDisabled(_Renderable): 41 | def wrap_text(self, text: str) -> str: 42 | return text 43 | 44 | def __eq__(self, other: object) -> bool: 45 | if isinstance(other, _ColorDisabled): 46 | return True 47 | return False 48 | 49 | 50 | COLORS = { 51 | 'black': _Color(30), 52 | 'bright black': _Color(30, True), 53 | 'grey': _Color(30, True), 54 | 'red': _Color(31), 55 | 'bright red': _Color(31, True), 56 | 'green': _Color(32), 57 | 'bright green': _Color(32, True), 58 | 'yellow': _Color(33), 59 | 'bright yellow': _Color(33, True), 60 | 'blue': _Color(34), 61 | 'bright blue': _Color(34, True), 62 | 'magenta': _Color(35), 63 | 'bright magenta': _Color(35, True), 64 | 'cyan': _Color(36), 65 | 'bright cyan': _Color(36, True), 66 | 'white': _Color(37), 67 | 'bright white': _Color(37, True), 68 | } 69 | COLOR_DISABLED = _ColorDisabled() 70 | -------------------------------------------------------------------------------- /pdir/configuration.py: -------------------------------------------------------------------------------- 1 | """Configuration management setup 2 | """ 3 | 4 | import os 5 | import sys 6 | from configparser import ConfigParser 7 | from os.path import expanduser 8 | 9 | from .color import COLORS, COLOR_DISABLED 10 | 11 | # User Configuration 12 | _DEFAULT_CONFIG_FILE = expanduser('~/.pdir2config') 13 | _DEFAULT = 'global' 14 | _UNIFORM_COLOR = 'uniform-color' 15 | _COLORFUL_OUTPUT = 'enable-colorful-output' 16 | TRUTHY_TERMS = frozenset({'True', 'Y', '1', 'true'}) 17 | VALID_CONFIG_KEYS = frozenset( 18 | { 19 | 'category-color', 20 | 'attribute-color', 21 | 'comma-color', 22 | 'doc-color', 23 | 'slot-color', 24 | } 25 | ) 26 | 27 | 28 | class Configuration: 29 | _uniform_color = None 30 | _enable_colorful_output = None 31 | _category_color = COLORS['yellow'] 32 | _attribute_color = COLORS['cyan'] 33 | _comma_color = COLORS['grey'] 34 | _doc_color = COLORS['grey'] 35 | _slot_color = COLORS['magenta'] 36 | 37 | def __init__(self): 38 | self._configparser = ConfigParser() 39 | self._load() 40 | 41 | @property 42 | def enable_colorful_output(self): 43 | return self._enable_colorful_output 44 | 45 | @property 46 | def uniform_color(self): 47 | return self._uniform_color 48 | 49 | @property 50 | def category_color(self): 51 | return self._category_color 52 | 53 | @property 54 | def attribute_color(self): 55 | return self._attribute_color 56 | 57 | @property 58 | def comma_color(self): 59 | return self._comma_color 60 | 61 | @property 62 | def doc_color(self): 63 | return self._doc_color 64 | 65 | @property 66 | def slot_color(self): 67 | return self._slot_color 68 | 69 | def _load(self): 70 | config_file = os.environ.get('PDIR2_CONFIG_FILE', _DEFAULT_CONFIG_FILE) 71 | config_file = os.path.expanduser(config_file) 72 | if not os.path.exists(config_file): 73 | if config_file == _DEFAULT_CONFIG_FILE: 74 | # Only raise exception if user set CONFIG_FILE_ENV. 75 | return 76 | else: 77 | raise OSError('Config file not exist: %s' % config_file) 78 | 79 | self._configparser.read(config_file) 80 | if not self._configparser.has_section(_DEFAULT): 81 | return 82 | user_config_dict = dict(self._configparser.items(_DEFAULT)) 83 | 84 | for item, color in user_config_dict.items(): 85 | if item == _COLORFUL_OUTPUT: 86 | self._enable_colorful_output = user_config_dict.get(_COLORFUL_OUTPUT) 87 | continue 88 | # UNIFORM_COLOR suppresses other settings. 89 | if item == _UNIFORM_COLOR: 90 | self._uniform_color = COLORS[user_config_dict[_UNIFORM_COLOR]] 91 | return 92 | # then the color settings 93 | if item not in VALID_CONFIG_KEYS: 94 | raise ValueError('Invalid key: %s' % item) 95 | if color not in set(COLORS.keys()): 96 | raise ValueError('Invalid color value: %s' % color) 97 | # item uses "-", e.g. "doc-color" 98 | self.__setattr__('_' + item.replace('-', '_'), COLORS[color]) 99 | 100 | 101 | _cfg = Configuration() 102 | 103 | 104 | def should_enable_colorful_output() -> bool: 105 | """When set, environ suppresses config file.""" 106 | environ_set = os.getenv("PDIR2_NOCOLOR") 107 | if environ_set and environ_set in TRUTHY_TERMS: 108 | return False 109 | 110 | if ( 111 | _cfg.enable_colorful_output is None or _cfg.enable_colorful_output == "auto" 112 | ): # Not set, default to "auto" 113 | return sys.stdout.isatty() 114 | 115 | return _cfg.enable_colorful_output == "True" 116 | 117 | 118 | if should_enable_colorful_output(): 119 | if _cfg.uniform_color: 120 | category_color = attribute_color = doc_color = _cfg.uniform_color 121 | comma = _cfg.uniform_color.wrap_text(', ') 122 | slot_tag = _cfg.uniform_color.wrap_text('(slotted)') 123 | else: 124 | category_color = _cfg.category_color 125 | attribute_color = _cfg.attribute_color 126 | doc_color = _cfg.doc_color 127 | comma = _cfg.comma_color.wrap_text(', ') 128 | slot_tag = _cfg.slot_color.wrap_text('(slotted)') 129 | else: 130 | category_color = attribute_color = doc_color = COLOR_DISABLED 131 | comma = ', ' 132 | slot_tag = '(slotted)' 133 | -------------------------------------------------------------------------------- /pdir/constants.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | dummy_obj = object() 4 | 5 | 6 | # repl 7 | class ReplType(Enum): 8 | PYTHON = 'python' 9 | IPYTHON = 'ipython' 10 | PTPYTHON = 'ptpython' 11 | BPYTHON = 'bpython' 12 | 13 | 14 | # descriptor 15 | GETTER = 'getter' 16 | SETTER = 'setter' 17 | DELETER = 'deleter' 18 | 19 | 20 | class _ClassWithSlot: 21 | __slots__ = ['a'] 22 | 23 | 24 | SLOT_TYPE = type(_ClassWithSlot.a) # type: ignore 25 | -------------------------------------------------------------------------------- /pdir/format.py: -------------------------------------------------------------------------------- 1 | """ 2 | Defines how attr is organized and displayed. 3 | """ 4 | from collections import namedtuple 5 | from collections.abc import Iterable 6 | from itertools import groupby 7 | from typing import List 8 | 9 | from . import api # noqa: F401, '.api' imported but unused 10 | from .attr_category import AttrCategory 11 | from .configuration import attribute_color, category_color, comma, doc_color, slot_tag 12 | 13 | 14 | def format_pattrs(pattrs: List["api.PrettyAttribute"]) -> str: 15 | """Generates repr string given a list of pattrs.""" 16 | pattrs.sort( 17 | key=lambda x: ( 18 | _FORMATTER[x.display_group].display_index, 19 | x.display_group, 20 | x.name, 21 | ) 22 | ) 23 | output = [ 24 | _FORMATTER[display_group].formatter(display_group, grouped_pattrs) 25 | for display_group, grouped_pattrs in groupby(pattrs, lambda x: x.display_group) 26 | ] 27 | 28 | return "\n".join(output) 29 | 30 | 31 | def _format_single_line(category: AttrCategory, pattrs: Iterable) -> str: 32 | category_line = category_color.wrap_text(str(category) + ":") 33 | output_text = [] 34 | for pattr in pattrs: 35 | single_attr = attribute_color.wrap_text(pattr.name) 36 | output_text.append(single_attr + slot_tag if pattr.slotted else single_attr) 37 | return f"{category_line}\n {comma.join(output_text)}" 38 | 39 | 40 | def _format_multiline_with_doc(category: AttrCategory, pattrs: Iterable) -> str: 41 | category_line = category_color.wrap_text(str(category) + ":") + "\n" 42 | output_text = [] 43 | for pattr in pattrs: 44 | name = attribute_color.wrap_text(pattr.name) 45 | if pattr.slotted: 46 | name += slot_tag 47 | name += attribute_color.wrap_text(": ") 48 | doc = doc_color.wrap_text(pattr.doc) 49 | output_text.append(f" {name}{doc}") 50 | return category_line + "\n".join(output_text) 51 | 52 | 53 | def _format_descriptor(category: AttrCategory, attrs: Iterable) -> str: 54 | return _format_multiline_with_doc(category, attrs) 55 | 56 | 57 | _AttributeGroupFormatter = namedtuple( 58 | "_AttributeGroupFormatter", ["display_index", "formatter"] 59 | ) 60 | 61 | _single_line = _AttributeGroupFormatter(display_index=0, formatter=_format_single_line) 62 | _descriptor = _AttributeGroupFormatter(display_index=1, formatter=_format_descriptor) 63 | _multiline_with_doc = _AttributeGroupFormatter( 64 | display_index=2, formatter=_format_multiline_with_doc 65 | ) 66 | 67 | _FORMATTER = { 68 | AttrCategory.SLOT: _single_line, 69 | AttrCategory.FUNCTION: _multiline_with_doc, 70 | AttrCategory.CLASS: _multiline_with_doc, 71 | AttrCategory.EXCEPTION: _multiline_with_doc, 72 | AttrCategory.PROPERTY: _single_line, 73 | # Attribute 74 | AttrCategory.MODULE_ATTRIBUTE: _single_line, 75 | AttrCategory.SPECIAL_ATTRIBUTE: _single_line, 76 | # Function 77 | AttrCategory.MAGIC: _multiline_with_doc, 78 | AttrCategory.ARITHMETIC: _single_line, 79 | AttrCategory.ITER: _single_line, 80 | AttrCategory.CONTEXT_MANAGER: _single_line, 81 | AttrCategory.OBJECT_CUSTOMIZATION: _single_line, 82 | AttrCategory.RICH_COMPARISON: _single_line, 83 | AttrCategory.ATTRIBUTE_ACCESS: _single_line, 84 | AttrCategory.DESCRIPTOR: _descriptor, 85 | AttrCategory.DESCRIPTOR_CLASS: _single_line, 86 | AttrCategory.STATIC_METHOD: _descriptor, 87 | AttrCategory.CLASS_CUSTOMIZATION: _single_line, 88 | AttrCategory.CONTAINER: _single_line, 89 | AttrCategory.COROUTINE: _single_line, 90 | AttrCategory.COPY: _single_line, 91 | AttrCategory.PICKLE: _single_line, 92 | AttrCategory.ABSTRACT_CLASS: _single_line, 93 | AttrCategory.PATTERN_MATCHING: _single_line, 94 | AttrCategory.TYPING: _single_line, 95 | AttrCategory.DECORATOR: _single_line, 96 | AttrCategory.BUFFER: _single_line, 97 | } 98 | -------------------------------------------------------------------------------- /pdm.lock: -------------------------------------------------------------------------------- 1 | # This file is @generated by PDM. 2 | # It is not intended for manual editing. 3 | 4 | [metadata] 5 | groups = ["default", "dev"] 6 | strategy = ["cross_platform"] 7 | lock_version = "4.5.0" 8 | content_hash = "sha256:10bb72947529998cf891711e6d90c183e342ea1c4f895e621656699054bb9556" 9 | 10 | [[metadata.targets]] 11 | requires_python = ">=3.8" 12 | 13 | [[package]] 14 | name = "ansicon" 15 | version = "1.89.0" 16 | summary = "Python wrapper for loading Jason Hood's ANSICON" 17 | files = [ 18 | {file = "ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec"}, 19 | {file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"}, 20 | ] 21 | 22 | [[package]] 23 | name = "appdirs" 24 | version = "1.4.4" 25 | summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 26 | files = [ 27 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 28 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 29 | ] 30 | 31 | [[package]] 32 | name = "appnope" 33 | version = "0.1.3" 34 | summary = "Disable App Nap on macOS >= 10.9" 35 | files = [ 36 | {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, 37 | {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, 38 | ] 39 | 40 | [[package]] 41 | name = "attrs" 42 | version = "23.1.0" 43 | requires_python = ">=3.7" 44 | summary = "Classes Without Boilerplate" 45 | dependencies = [ 46 | "importlib-metadata; python_version < \"3.8\"", 47 | ] 48 | files = [ 49 | {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, 50 | {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, 51 | ] 52 | 53 | [[package]] 54 | name = "backcall" 55 | version = "0.2.0" 56 | summary = "Specifications for callback functions passed in to an API" 57 | files = [ 58 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 59 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 60 | ] 61 | 62 | [[package]] 63 | name = "blessed" 64 | version = "1.20.0" 65 | requires_python = ">=2.7" 66 | summary = "Easy, practical library for making terminal apps, by providing an elegant, well-documented interface to Colors, Keyboard input, and screen Positioning capabilities." 67 | dependencies = [ 68 | "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", 69 | "jinxed>=1.1.0; platform_system == \"Windows\"", 70 | "ordereddict==1.1; python_version < \"2.7\"", 71 | "six>=1.9.0", 72 | "wcwidth>=0.1.4", 73 | ] 74 | files = [ 75 | {file = "blessed-1.20.0-py2.py3-none-any.whl", hash = "sha256:0c542922586a265e699188e52d5f5ac5ec0dd517e5a1041d90d2bbf23f906058"}, 76 | {file = "blessed-1.20.0.tar.gz", hash = "sha256:2cdd67f8746e048f00df47a2880f4d6acbcdb399031b604e34ba8f71d5787680"}, 77 | ] 78 | 79 | [[package]] 80 | name = "bpython" 81 | version = "0.24" 82 | requires_python = ">=3.7" 83 | summary = "" 84 | dependencies = [ 85 | "backports-cached-property; python_version < \"3.8\"", 86 | "curtsies>=0.4.0", 87 | "cwcwidth", 88 | "greenlet", 89 | "pygments", 90 | "pyxdg", 91 | "requests", 92 | "typing-extensions; python_version < \"3.8\"", 93 | ] 94 | files = [ 95 | {file = "bpython-0.24-py3-none-any.whl", hash = "sha256:0d196ae3d1ce3dcd559a3fb89ed2c468dfbd1504af0d680b906dd65a9c7a32eb"}, 96 | {file = "bpython-0.24.tar.gz", hash = "sha256:98736ffd7a8c48fd2bfb53d898a475f4241bde0b672125706af04d9d08fd3dbd"}, 97 | ] 98 | 99 | [[package]] 100 | name = "cachetools" 101 | version = "5.5.0" 102 | requires_python = ">=3.7" 103 | summary = "Extensible memoizing collections and decorators" 104 | files = [ 105 | {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, 106 | {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, 107 | ] 108 | 109 | [[package]] 110 | name = "certifi" 111 | version = "2023.7.22" 112 | requires_python = ">=3.6" 113 | summary = "Python package for providing Mozilla's CA Bundle." 114 | files = [ 115 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 116 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 117 | ] 118 | 119 | [[package]] 120 | name = "cffi" 121 | version = "1.16.0" 122 | requires_python = ">=3.8" 123 | summary = "Foreign Function Interface for Python calling C code." 124 | dependencies = [ 125 | "pycparser", 126 | ] 127 | files = [ 128 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 129 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 130 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 131 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 132 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 133 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 134 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 135 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 136 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 137 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 138 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 139 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 140 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 141 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 142 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 143 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 144 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 145 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 146 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 147 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 148 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 149 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 150 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 151 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 152 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 153 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 154 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 155 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 156 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 157 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 158 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 159 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 160 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 161 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 162 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 163 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 164 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 165 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 166 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 167 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 168 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 169 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 170 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 171 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 172 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 173 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 174 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 175 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 176 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 177 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 178 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 179 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 180 | ] 181 | 182 | [[package]] 183 | name = "chardet" 184 | version = "5.2.0" 185 | requires_python = ">=3.7" 186 | summary = "Universal encoding detector for Python 3" 187 | files = [ 188 | {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, 189 | {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, 190 | ] 191 | 192 | [[package]] 193 | name = "charset-normalizer" 194 | version = "3.3.0" 195 | requires_python = ">=3.7.0" 196 | summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 197 | files = [ 198 | {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, 199 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, 200 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, 201 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, 202 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, 203 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, 204 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, 205 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, 206 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, 207 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, 208 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, 209 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, 210 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, 211 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, 212 | {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, 213 | {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, 214 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, 215 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, 216 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, 217 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, 218 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, 219 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, 220 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, 221 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, 222 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, 223 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, 224 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, 225 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, 226 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, 227 | {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, 228 | {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, 229 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, 230 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, 231 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, 232 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, 233 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, 234 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, 235 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, 236 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, 237 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, 238 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, 239 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, 240 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, 241 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, 242 | {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, 243 | {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, 244 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, 245 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, 246 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, 247 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, 248 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, 249 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, 250 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, 251 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, 252 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, 253 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, 254 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, 255 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, 256 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, 257 | {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, 258 | {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, 259 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, 260 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, 261 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, 262 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, 263 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, 264 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, 265 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, 266 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, 267 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, 268 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, 269 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, 270 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, 271 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, 272 | {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, 273 | {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, 274 | {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, 275 | ] 276 | 277 | [[package]] 278 | name = "colorama" 279 | version = "0.4.6" 280 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 281 | summary = "Cross-platform colored terminal text." 282 | files = [ 283 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 284 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 285 | ] 286 | 287 | [[package]] 288 | name = "cryptography" 289 | version = "41.0.4" 290 | requires_python = ">=3.7" 291 | summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 292 | dependencies = [ 293 | "cffi>=1.12", 294 | ] 295 | files = [ 296 | {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, 297 | {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, 298 | {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, 299 | {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, 300 | {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, 301 | {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, 302 | {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, 303 | {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, 304 | {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, 305 | {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, 306 | {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, 307 | {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, 308 | {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, 309 | {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, 310 | {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, 311 | {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, 312 | {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, 313 | {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, 314 | {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, 315 | {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, 316 | {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, 317 | {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, 318 | {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, 319 | ] 320 | 321 | [[package]] 322 | name = "curtsies" 323 | version = "0.4.2" 324 | requires_python = ">=3.7" 325 | summary = "Curses-like terminal wrapper, with colored strings!" 326 | dependencies = [ 327 | "backports-cached-property; python_version < \"3.8\"", 328 | "blessed>=1.5", 329 | "cwcwidth", 330 | ] 331 | files = [ 332 | {file = "curtsies-0.4.2-py3-none-any.whl", hash = "sha256:f24d676a8c4711fb9edba1ab7e6134bc52305a222980b3b717bb303f5e94cec6"}, 333 | {file = "curtsies-0.4.2.tar.gz", hash = "sha256:6ebe33215bd7c92851a506049c720cca4cf5c192c1665c1d7a98a04c4702760e"}, 334 | ] 335 | 336 | [[package]] 337 | name = "cwcwidth" 338 | version = "0.1.8" 339 | requires_python = ">=3.7" 340 | summary = "Python bindings for wc(s)width" 341 | files = [ 342 | {file = "cwcwidth-0.1.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a5256190f2fa081e9ce0a4e3031992e7c8b98868fc77e4c00bc9563b1fc3017"}, 343 | {file = "cwcwidth-0.1.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dbb6c6faa5899f71a3064843c528e9ed31881a2b0c83e33ee5109a71a1389cf"}, 344 | {file = "cwcwidth-0.1.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5c9c9f6ee425d6d1bf5d09227304e10178755576b103d83457cc9884c7513f"}, 345 | {file = "cwcwidth-0.1.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:79fea2acf7418f7b6398d43f47195b993fcefd4adf4c9a851d93b50914b0eceb"}, 346 | {file = "cwcwidth-0.1.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:577a9394cba36339d41e633c9d82da0c67d26a3392ca64ac33e8379492206041"}, 347 | {file = "cwcwidth-0.1.8-cp310-cp310-win32.whl", hash = "sha256:70f3dd71d935780657e91047389161188e2d121e01c4356746263d280e294fa9"}, 348 | {file = "cwcwidth-0.1.8-cp310-cp310-win_amd64.whl", hash = "sha256:f83b063b93ac8581f11c6a1ebd9adc1c74942fb2f2d5dfd027f81d5217038cf2"}, 349 | {file = "cwcwidth-0.1.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62d4a203782b9eb677e5645517c6f2bfb033f07f9d3d112e994e37c1ac0d411c"}, 350 | {file = "cwcwidth-0.1.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87a53489a715416b1c0e2b6a498061d9d395747ad4e05af34e4b6efdb68566b7"}, 351 | {file = "cwcwidth-0.1.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56f65342c9878bf243849ed9d4f262544de1590eb0d429821e30fed3f7333c4"}, 352 | {file = "cwcwidth-0.1.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72aed59f8cdb7a6b0fe3717be766bbdc41c8ac9ad43deb4b61e96e1c8a6f7f1b"}, 353 | {file = "cwcwidth-0.1.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b85483ca46e7715b1d8c3e64a45d15413e33f2a7d5760022fe745135373dad7c"}, 354 | {file = "cwcwidth-0.1.8-cp311-cp311-win32.whl", hash = "sha256:63ea84c5c3ac3f146ef091cded6a96cb9d421c28d8c4b40902adb1e28aeba52a"}, 355 | {file = "cwcwidth-0.1.8-cp311-cp311-win_amd64.whl", hash = "sha256:1e156b9fa6068d32b14984fef9281cedfda114616d6f40cc21202756c04045fd"}, 356 | {file = "cwcwidth-0.1.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1cb4768ff9f6dcebcfb9e3bcc06b2de4f7d24ab423b01b94740f6ac5c72eede6"}, 357 | {file = "cwcwidth-0.1.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6569c91d787adb312d8d30608e83def6689d0756d39edc4e1d9f85a0512abeee"}, 358 | {file = "cwcwidth-0.1.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d89448dfe4b9bde74b4a43fd359c892797262f3c77579027409fb4099f88670f"}, 359 | {file = "cwcwidth-0.1.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5c75d262d3bbb7fda982b0b8ce234d8a9e6bec0920f20c7daad93f7a631b1396"}, 360 | {file = "cwcwidth-0.1.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4dc6b7a86893cb8615f3ff9a29132e21e292d47a30ccb33f1e024d745f30f1f9"}, 361 | {file = "cwcwidth-0.1.8-cp38-cp38-win32.whl", hash = "sha256:26875ee1ac234e4bd73e6b55c0d8e1f61ae51736f4b712a4f9dd147e559fb17f"}, 362 | {file = "cwcwidth-0.1.8-cp38-cp38-win_amd64.whl", hash = "sha256:eaaea46c6895ed5dc2f6ff67a3d13400d8a5f5d0f4f243f487879c358b902099"}, 363 | {file = "cwcwidth-0.1.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:add82ab289c8a9d98af50e711199da1784abbac25a17979dea05ba4e862694de"}, 364 | {file = "cwcwidth-0.1.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5130eabcab72efdf1eee614a9ed00d0f8da9f6d7e6f81537ce010cecaf3427"}, 365 | {file = "cwcwidth-0.1.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9abc6c4616359c21862f3ba42630556102577897f474a9d00bb9cdfa5cb53757"}, 366 | {file = "cwcwidth-0.1.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:058162c84ca54362f8af99a57feba5abe76075e16c7fe8a290f99f63a9b40bd9"}, 367 | {file = "cwcwidth-0.1.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:275d89f102a966755e7b8482b96ad2efd8399853481b2a91d662da48fbcb8539"}, 368 | {file = "cwcwidth-0.1.8-cp39-cp39-win32.whl", hash = "sha256:3e99cd2433ab37e35061057b6fddfd714d34e214bd2f2d9f708e422fe6c4eeb6"}, 369 | {file = "cwcwidth-0.1.8-cp39-cp39-win_amd64.whl", hash = "sha256:02a2e8a7cf1aaa590c1c56624cf87b793b1ecb84850b9a965df78766843439a1"}, 370 | {file = "cwcwidth-0.1.8.tar.gz", hash = "sha256:5adc034b7c90e6a8586bd046bcbf6004e35e16b0d7e31de395513a50d729bbf6"}, 371 | ] 372 | 373 | [[package]] 374 | name = "decorator" 375 | version = "5.1.1" 376 | requires_python = ">=3.5" 377 | summary = "Decorators for Humans" 378 | files = [ 379 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, 380 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, 381 | ] 382 | 383 | [[package]] 384 | name = "distlib" 385 | version = "0.3.7" 386 | summary = "Distribution utilities" 387 | files = [ 388 | {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, 389 | {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, 390 | ] 391 | 392 | [[package]] 393 | name = "docutils" 394 | version = "0.20.1" 395 | requires_python = ">=3.7" 396 | summary = "Docutils -- Python Documentation Utilities" 397 | files = [ 398 | {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, 399 | {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, 400 | ] 401 | 402 | [[package]] 403 | name = "exceptiongroup" 404 | version = "1.1.3" 405 | requires_python = ">=3.7" 406 | summary = "Backport of PEP 654 (exception groups)" 407 | files = [ 408 | {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, 409 | {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, 410 | ] 411 | 412 | [[package]] 413 | name = "filelock" 414 | version = "3.16.1" 415 | requires_python = ">=3.8" 416 | summary = "A platform independent file lock." 417 | files = [ 418 | {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, 419 | {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, 420 | ] 421 | 422 | [[package]] 423 | name = "greenlet" 424 | version = "3.0.0" 425 | requires_python = ">=3.7" 426 | summary = "Lightweight in-process concurrent programming" 427 | files = [ 428 | {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, 429 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, 430 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, 431 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, 432 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, 433 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, 434 | {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, 435 | {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, 436 | {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, 437 | {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, 438 | {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, 439 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, 440 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, 441 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, 442 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, 443 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, 444 | {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, 445 | {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, 446 | {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, 447 | {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, 448 | {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, 449 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, 450 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, 451 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, 452 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, 453 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, 454 | {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, 455 | {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, 456 | {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, 457 | {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, 458 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, 459 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, 460 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, 461 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, 462 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, 463 | {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, 464 | {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, 465 | {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, 466 | {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, 467 | {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, 468 | {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, 469 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, 470 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, 471 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, 472 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, 473 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, 474 | {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, 475 | {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, 476 | {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, 477 | {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, 478 | {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, 479 | {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, 480 | ] 481 | 482 | [[package]] 483 | name = "hypothesis" 484 | version = "6.21.6" 485 | requires_python = ">=3.6" 486 | summary = "A library for property-based testing" 487 | dependencies = [ 488 | "attrs>=19.2.0", 489 | "sortedcontainers<3.0.0,>=2.1.0", 490 | ] 491 | files = [ 492 | {file = "hypothesis-6.21.6-py3-none-any.whl", hash = "sha256:1a96c67658f0fca00476380c2c7323066e2fb1d45a2b0874dd07fd0643b49b46"}, 493 | {file = "hypothesis-6.21.6.tar.gz", hash = "sha256:29b72005910f2a548d727e5d5accf862a6ae84e49176d748fb6ca040ef657592"}, 494 | ] 495 | 496 | [[package]] 497 | name = "idna" 498 | version = "3.4" 499 | requires_python = ">=3.5" 500 | summary = "Internationalized Domain Names in Applications (IDNA)" 501 | files = [ 502 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 503 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 504 | ] 505 | 506 | [[package]] 507 | name = "importlib-metadata" 508 | version = "6.8.0" 509 | requires_python = ">=3.8" 510 | summary = "Read metadata from Python packages" 511 | dependencies = [ 512 | "typing-extensions>=3.6.4; python_version < \"3.8\"", 513 | "zipp>=0.5", 514 | ] 515 | files = [ 516 | {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, 517 | {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, 518 | ] 519 | 520 | [[package]] 521 | name = "importlib-resources" 522 | version = "6.1.0" 523 | requires_python = ">=3.8" 524 | summary = "Read resources from Python packages" 525 | dependencies = [ 526 | "zipp>=3.1.0; python_version < \"3.10\"", 527 | ] 528 | files = [ 529 | {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, 530 | {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, 531 | ] 532 | 533 | [[package]] 534 | name = "iniconfig" 535 | version = "2.0.0" 536 | requires_python = ">=3.7" 537 | summary = "brain-dead simple config-ini parsing" 538 | files = [ 539 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 540 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 541 | ] 542 | 543 | [[package]] 544 | name = "ipython" 545 | version = "7.16.3" 546 | requires_python = ">=3.6" 547 | summary = "IPython: Productive Interactive Computing" 548 | dependencies = [ 549 | "appnope; sys_platform == \"darwin\"", 550 | "backcall", 551 | "colorama; sys_platform == \"win32\"", 552 | "decorator", 553 | "jedi<=0.17.2,>=0.10", 554 | "pexpect; sys_platform != \"win32\"", 555 | "pickleshare", 556 | "prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0", 557 | "pygments", 558 | "setuptools>=18.5", 559 | "traitlets>=4.2", 560 | ] 561 | files = [ 562 | {file = "ipython-7.16.3-py3-none-any.whl", hash = "sha256:c0427ed8bc33ac481faf9d3acf7e84e0010cdaada945e0badd1e2e74cc075833"}, 563 | {file = "ipython-7.16.3.tar.gz", hash = "sha256:5ac47dc9af66fc2f5530c12069390877ae372ac905edca75a92a6e363b5d7caa"}, 564 | ] 565 | 566 | [[package]] 567 | name = "jaraco-classes" 568 | version = "3.3.0" 569 | requires_python = ">=3.8" 570 | summary = "Utility functions for Python class constructs" 571 | dependencies = [ 572 | "more-itertools", 573 | ] 574 | files = [ 575 | {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, 576 | {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, 577 | ] 578 | 579 | [[package]] 580 | name = "jedi" 581 | version = "0.17.2" 582 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 583 | summary = "An autocompletion tool for Python that can be used for text editors." 584 | dependencies = [ 585 | "parso<0.8.0,>=0.7.0", 586 | ] 587 | files = [ 588 | {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, 589 | {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, 590 | ] 591 | 592 | [[package]] 593 | name = "jeepney" 594 | version = "0.8.0" 595 | requires_python = ">=3.7" 596 | summary = "Low-level, pure Python DBus protocol wrapper." 597 | files = [ 598 | {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, 599 | {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, 600 | ] 601 | 602 | [[package]] 603 | name = "jinxed" 604 | version = "1.2.0" 605 | summary = "Jinxed Terminal Library" 606 | dependencies = [ 607 | "ansicon; platform_system == \"Windows\"", 608 | ] 609 | files = [ 610 | {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, 611 | {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, 612 | ] 613 | 614 | [[package]] 615 | name = "keyring" 616 | version = "24.2.0" 617 | requires_python = ">=3.8" 618 | summary = "Store and access your passwords safely." 619 | dependencies = [ 620 | "SecretStorage>=3.2; sys_platform == \"linux\"", 621 | "importlib-metadata>=4.11.4; python_version < \"3.12\"", 622 | "importlib-resources; python_version < \"3.9\"", 623 | "jaraco-classes", 624 | "jeepney>=0.4.2; sys_platform == \"linux\"", 625 | "pywin32-ctypes>=0.2.0; sys_platform == \"win32\"", 626 | ] 627 | files = [ 628 | {file = "keyring-24.2.0-py3-none-any.whl", hash = "sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6"}, 629 | {file = "keyring-24.2.0.tar.gz", hash = "sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509"}, 630 | ] 631 | 632 | [[package]] 633 | name = "markdown-it-py" 634 | version = "3.0.0" 635 | requires_python = ">=3.8" 636 | summary = "Python port of markdown-it. Markdown parsing, done right!" 637 | dependencies = [ 638 | "mdurl~=0.1", 639 | ] 640 | files = [ 641 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 642 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 643 | ] 644 | 645 | [[package]] 646 | name = "mdurl" 647 | version = "0.1.2" 648 | requires_python = ">=3.7" 649 | summary = "Markdown URL utilities" 650 | files = [ 651 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 652 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 653 | ] 654 | 655 | [[package]] 656 | name = "more-itertools" 657 | version = "10.1.0" 658 | requires_python = ">=3.8" 659 | summary = "More routines for operating on iterables, beyond itertools" 660 | files = [ 661 | {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, 662 | {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, 663 | ] 664 | 665 | [[package]] 666 | name = "mypy" 667 | version = "1.11.2" 668 | requires_python = ">=3.8" 669 | summary = "Optional static typing for Python" 670 | dependencies = [ 671 | "mypy-extensions>=1.0.0", 672 | "tomli>=1.1.0; python_version < \"3.11\"", 673 | "typing-extensions>=4.6.0", 674 | ] 675 | files = [ 676 | {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, 677 | {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, 678 | {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, 679 | {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, 680 | {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, 681 | {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, 682 | {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, 683 | {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, 684 | {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, 685 | {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, 686 | {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, 687 | {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, 688 | {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, 689 | {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, 690 | {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, 691 | {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, 692 | {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, 693 | {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, 694 | {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, 695 | {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, 696 | {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, 697 | {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, 698 | {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, 699 | {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, 700 | {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, 701 | {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, 702 | {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, 703 | ] 704 | 705 | [[package]] 706 | name = "mypy-extensions" 707 | version = "1.0.0" 708 | requires_python = ">=3.5" 709 | summary = "Type system extensions for programs checked with the mypy type checker." 710 | files = [ 711 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 712 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 713 | ] 714 | 715 | [[package]] 716 | name = "nh3" 717 | version = "0.2.14" 718 | summary = "Ammonia HTML sanitizer Python binding" 719 | files = [ 720 | {file = "nh3-0.2.14-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a"}, 721 | {file = "nh3-0.2.14-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75"}, 722 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450"}, 723 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e"}, 724 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e"}, 725 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad"}, 726 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2"}, 727 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525"}, 728 | {file = "nh3-0.2.14-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6"}, 729 | {file = "nh3-0.2.14-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4"}, 730 | {file = "nh3-0.2.14-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5"}, 731 | {file = "nh3-0.2.14-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d"}, 732 | {file = "nh3-0.2.14-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6"}, 733 | {file = "nh3-0.2.14-cp37-abi3-win32.whl", hash = "sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873"}, 734 | {file = "nh3-0.2.14-cp37-abi3-win_amd64.whl", hash = "sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e"}, 735 | {file = "nh3-0.2.14.tar.gz", hash = "sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4"}, 736 | ] 737 | 738 | [[package]] 739 | name = "packaging" 740 | version = "24.1" 741 | requires_python = ">=3.8" 742 | summary = "Core utilities for Python packages" 743 | files = [ 744 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 745 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 746 | ] 747 | 748 | [[package]] 749 | name = "parso" 750 | version = "0.7.1" 751 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 752 | summary = "A Python Parser" 753 | files = [ 754 | {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, 755 | {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, 756 | ] 757 | 758 | [[package]] 759 | name = "pexpect" 760 | version = "4.8.0" 761 | summary = "Pexpect allows easy control of interactive console applications." 762 | dependencies = [ 763 | "ptyprocess>=0.5", 764 | ] 765 | files = [ 766 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 767 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 768 | ] 769 | 770 | [[package]] 771 | name = "pickleshare" 772 | version = "0.7.5" 773 | summary = "Tiny 'shelve'-like database with concurrency support" 774 | dependencies = [ 775 | "pathlib2; python_version in \"2.6 2.7 3.2 3.3\"", 776 | ] 777 | files = [ 778 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 779 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 780 | ] 781 | 782 | [[package]] 783 | name = "pkginfo" 784 | version = "1.9.6" 785 | requires_python = ">=3.6" 786 | summary = "Query metadata from sdists / bdists / installed packages." 787 | files = [ 788 | {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, 789 | {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, 790 | ] 791 | 792 | [[package]] 793 | name = "platformdirs" 794 | version = "4.3.6" 795 | requires_python = ">=3.8" 796 | summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 797 | files = [ 798 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 799 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 800 | ] 801 | 802 | [[package]] 803 | name = "pluggy" 804 | version = "1.5.0" 805 | requires_python = ">=3.8" 806 | summary = "plugin and hook calling mechanisms for python" 807 | files = [ 808 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 809 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 810 | ] 811 | 812 | [[package]] 813 | name = "prompt-toolkit" 814 | version = "3.0.48" 815 | requires_python = ">=3.7.0" 816 | summary = "Library for building powerful interactive command lines in Python" 817 | dependencies = [ 818 | "wcwidth", 819 | ] 820 | files = [ 821 | {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, 822 | {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, 823 | ] 824 | 825 | [[package]] 826 | name = "ptpython" 827 | version = "3.0.29" 828 | requires_python = ">=3.7" 829 | summary = "Python REPL build on top of prompt_toolkit" 830 | dependencies = [ 831 | "appdirs", 832 | "importlib-metadata; python_version < \"3.8\"", 833 | "jedi>=0.16.0", 834 | "prompt-toolkit<3.1.0,>=3.0.43", 835 | "pygments", 836 | ] 837 | files = [ 838 | {file = "ptpython-3.0.29-py2.py3-none-any.whl", hash = "sha256:65d75c4871859e4305a020c9b9e204366dceb4d08e0e2bd7b7511bd5e917a402"}, 839 | {file = "ptpython-3.0.29.tar.gz", hash = "sha256:b9d625183aef93a673fc32cbe1c1fcaf51412e7a4f19590521cdaccadf25186e"}, 840 | ] 841 | 842 | [[package]] 843 | name = "ptyprocess" 844 | version = "0.7.0" 845 | summary = "Run a subprocess in a pseudo terminal" 846 | files = [ 847 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 848 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 849 | ] 850 | 851 | [[package]] 852 | name = "pycparser" 853 | version = "2.21" 854 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 855 | summary = "C parser in Python" 856 | files = [ 857 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 858 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 859 | ] 860 | 861 | [[package]] 862 | name = "pygments" 863 | version = "2.16.1" 864 | requires_python = ">=3.7" 865 | summary = "Pygments is a syntax highlighting package written in Python." 866 | files = [ 867 | {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, 868 | {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, 869 | ] 870 | 871 | [[package]] 872 | name = "pyproject-api" 873 | version = "1.8.0" 874 | requires_python = ">=3.8" 875 | summary = "API to interact with the python pyproject.toml based projects" 876 | dependencies = [ 877 | "packaging>=24.1", 878 | "tomli>=2.0.1; python_version < \"3.11\"", 879 | ] 880 | files = [ 881 | {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, 882 | {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, 883 | ] 884 | 885 | [[package]] 886 | name = "pytest" 887 | version = "7.4.4" 888 | requires_python = ">=3.7" 889 | summary = "pytest: simple powerful testing with Python" 890 | dependencies = [ 891 | "colorama; sys_platform == \"win32\"", 892 | "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", 893 | "importlib-metadata>=0.12; python_version < \"3.8\"", 894 | "iniconfig", 895 | "packaging", 896 | "pluggy<2.0,>=0.12", 897 | "tomli>=1.0.0; python_version < \"3.11\"", 898 | ] 899 | files = [ 900 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 901 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 902 | ] 903 | 904 | [[package]] 905 | name = "pytest-mypy" 906 | version = "0.10.3" 907 | requires_python = ">=3.6" 908 | summary = "Mypy static type checker plugin for Pytest" 909 | dependencies = [ 910 | "attrs>=19.0", 911 | "filelock>=3.0", 912 | "mypy>=0.500; python_version < \"3.8\"", 913 | "mypy>=0.700; python_version >= \"3.8\" and python_version < \"3.9\"", 914 | "mypy>=0.780; python_version >= \"3.9\" and python_version < \"3.11\"", 915 | "mypy>=0.900; python_version >= \"3.11\"", 916 | "pytest>=4.6; python_version >= \"3.6\" and python_version < \"3.10\"", 917 | "pytest>=6.2; python_version >= \"3.10\"", 918 | ] 919 | files = [ 920 | {file = "pytest-mypy-0.10.3.tar.gz", hash = "sha256:f8458f642323f13a2ca3e2e61509f7767966b527b4d8adccd5032c3e7b4fd3db"}, 921 | {file = "pytest_mypy-0.10.3-py3-none-any.whl", hash = "sha256:7638d0d3906848fc1810cb2f5cc7fceb4cc5c98524aafcac58f28620e3102053"}, 922 | ] 923 | 924 | [[package]] 925 | name = "pywin32-ctypes" 926 | version = "0.2.2" 927 | requires_python = ">=3.6" 928 | summary = "A (partial) reimplementation of pywin32 using ctypes/cffi" 929 | files = [ 930 | {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, 931 | {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, 932 | ] 933 | 934 | [[package]] 935 | name = "pyxdg" 936 | version = "0.28" 937 | summary = "PyXDG contains implementations of freedesktop.org standards in python." 938 | files = [ 939 | {file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"}, 940 | {file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"}, 941 | ] 942 | 943 | [[package]] 944 | name = "readme-renderer" 945 | version = "42.0" 946 | requires_python = ">=3.8" 947 | summary = "readme_renderer is a library for rendering readme descriptions for Warehouse" 948 | dependencies = [ 949 | "Pygments>=2.5.1", 950 | "docutils>=0.13.1", 951 | "nh3>=0.2.14", 952 | ] 953 | files = [ 954 | {file = "readme_renderer-42.0-py3-none-any.whl", hash = "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d"}, 955 | {file = "readme_renderer-42.0.tar.gz", hash = "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1"}, 956 | ] 957 | 958 | [[package]] 959 | name = "requests" 960 | version = "2.31.0" 961 | requires_python = ">=3.7" 962 | summary = "Python HTTP for Humans." 963 | dependencies = [ 964 | "certifi>=2017.4.17", 965 | "charset-normalizer<4,>=2", 966 | "idna<4,>=2.5", 967 | "urllib3<3,>=1.21.1", 968 | ] 969 | files = [ 970 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 971 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 972 | ] 973 | 974 | [[package]] 975 | name = "requests-toolbelt" 976 | version = "1.0.0" 977 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 978 | summary = "A utility belt for advanced users of python-requests" 979 | dependencies = [ 980 | "requests<3.0.0,>=2.0.1", 981 | ] 982 | files = [ 983 | {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, 984 | {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, 985 | ] 986 | 987 | [[package]] 988 | name = "rfc3986" 989 | version = "2.0.0" 990 | requires_python = ">=3.7" 991 | summary = "Validating URI References per RFC 3986" 992 | files = [ 993 | {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, 994 | {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, 995 | ] 996 | 997 | [[package]] 998 | name = "rich" 999 | version = "13.6.0" 1000 | requires_python = ">=3.7.0" 1001 | summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 1002 | dependencies = [ 1003 | "markdown-it-py>=2.2.0", 1004 | "pygments<3.0.0,>=2.13.0", 1005 | "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", 1006 | ] 1007 | files = [ 1008 | {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, 1009 | {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "secretstorage" 1014 | version = "3.3.3" 1015 | requires_python = ">=3.6" 1016 | summary = "Python bindings to FreeDesktop.org Secret Service API" 1017 | dependencies = [ 1018 | "cryptography>=2.0", 1019 | "jeepney>=0.6", 1020 | ] 1021 | files = [ 1022 | {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, 1023 | {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "setuptools" 1028 | version = "68.2.2" 1029 | requires_python = ">=3.8" 1030 | summary = "Easily download, build, install, upgrade, and uninstall Python packages" 1031 | files = [ 1032 | {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, 1033 | {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "six" 1038 | version = "1.16.0" 1039 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1040 | summary = "Python 2 and 3 compatibility utilities" 1041 | files = [ 1042 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1043 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "sortedcontainers" 1048 | version = "2.4.0" 1049 | summary = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" 1050 | files = [ 1051 | {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, 1052 | {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "toml" 1057 | version = "0.10.2" 1058 | requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 1059 | summary = "Python Library for Tom's Obvious, Minimal Language" 1060 | files = [ 1061 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1062 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "tomli" 1067 | version = "2.0.1" 1068 | requires_python = ">=3.7" 1069 | summary = "A lil' TOML parser" 1070 | files = [ 1071 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1072 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "tox" 1077 | version = "4.21.2" 1078 | requires_python = ">=3.8" 1079 | summary = "tox is a generic virtualenv management and test command line tool" 1080 | dependencies = [ 1081 | "cachetools>=5.5", 1082 | "chardet>=5.2", 1083 | "colorama>=0.4.6", 1084 | "filelock>=3.16.1", 1085 | "packaging>=24.1", 1086 | "platformdirs>=4.3.6", 1087 | "pluggy>=1.5", 1088 | "pyproject-api>=1.8", 1089 | "tomli>=2.0.1; python_version < \"3.11\"", 1090 | "typing-extensions>=4.12.2; python_version < \"3.11\"", 1091 | "virtualenv>=20.26.6", 1092 | ] 1093 | files = [ 1094 | {file = "tox-4.21.2-py3-none-any.whl", hash = "sha256:13d996adcd792e7c82994b0e116d85efd84f0c6d185254d83d156f73f86b2038"}, 1095 | {file = "tox-4.21.2.tar.gz", hash = "sha256:49381ff102296753e378fa5ff30e42a35e695f149b4dbf8a2c49d15fdb5797b2"}, 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "tox-gh-actions" 1100 | version = "3.2.0" 1101 | requires_python = ">=3.7" 1102 | summary = "Seamless integration of tox into GitHub Actions" 1103 | dependencies = [ 1104 | "tox<5,>=4", 1105 | ] 1106 | files = [ 1107 | {file = "tox-gh-actions-3.2.0.tar.gz", hash = "sha256:ac6fa3b8da51bc90dd77985fd55f09e746c6558c55910c0a93d643045a2b0ccc"}, 1108 | {file = "tox_gh_actions-3.2.0-py2.py3-none-any.whl", hash = "sha256:821b66a4751a788fa3e9617bd796d696507b08c6e1d929ee4faefba06b73b694"}, 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "tox-pdm" 1113 | version = "0.5.0" 1114 | requires_python = ">=3.7" 1115 | summary = "A plugin for tox that utilizes PDM as the package manager and installer" 1116 | dependencies = [ 1117 | "toml>=0.10", 1118 | "tox>=3.18.0", 1119 | ] 1120 | files = [ 1121 | {file = "tox-pdm-0.5.0.tar.gz", hash = "sha256:45531c80c9670e7d9a0109ec5ab0d9add0492db4b41aa4ae0f20b295e4fd60e3"}, 1122 | {file = "tox_pdm-0.5.0-py3-none-any.whl", hash = "sha256:0075ff9ed47ee13dc6e55122e6da121661a5553a633b8dd278013fbee81e4bb4"}, 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "traitlets" 1127 | version = "5.11.2" 1128 | requires_python = ">=3.8" 1129 | summary = "Traitlets Python configuration system" 1130 | files = [ 1131 | {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, 1132 | {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "twine" 1137 | version = "5.1.1" 1138 | requires_python = ">=3.8" 1139 | summary = "Collection of utilities for publishing packages on PyPI" 1140 | dependencies = [ 1141 | "importlib-metadata>=3.6", 1142 | "keyring>=15.1", 1143 | "pkginfo<1.11", 1144 | "pkginfo>=1.8.1", 1145 | "readme-renderer>=35.0", 1146 | "requests-toolbelt!=0.9.0,>=0.8.0", 1147 | "requests>=2.20", 1148 | "rfc3986>=1.4.0", 1149 | "rich>=12.0.0", 1150 | "urllib3>=1.26.0", 1151 | ] 1152 | files = [ 1153 | {file = "twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997"}, 1154 | {file = "twine-5.1.1.tar.gz", hash = "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db"}, 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "typing-extensions" 1159 | version = "4.12.2" 1160 | requires_python = ">=3.8" 1161 | summary = "Backported and Experimental Type Hints for Python 3.8+" 1162 | files = [ 1163 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 1164 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "urllib3" 1169 | version = "2.0.6" 1170 | requires_python = ">=3.7" 1171 | summary = "HTTP library with thread-safe connection pooling, file post, and more." 1172 | files = [ 1173 | {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, 1174 | {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "virtualenv" 1179 | version = "20.26.6" 1180 | requires_python = ">=3.7" 1181 | summary = "Virtual Python Environment builder" 1182 | dependencies = [ 1183 | "distlib<1,>=0.3.7", 1184 | "filelock<4,>=3.12.2", 1185 | "importlib-metadata>=6.6; python_version < \"3.8\"", 1186 | "platformdirs<5,>=3.9.1", 1187 | ] 1188 | files = [ 1189 | {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, 1190 | {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "wcwidth" 1195 | version = "0.2.8" 1196 | summary = "Measures the displayed width of unicode strings in a terminal" 1197 | dependencies = [ 1198 | "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", 1199 | ] 1200 | files = [ 1201 | {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, 1202 | {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "zipp" 1207 | version = "3.17.0" 1208 | requires_python = ">=3.8" 1209 | summary = "Backport of pathlib-compatible object wrapper for zip files" 1210 | files = [ 1211 | {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, 1212 | {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, 1213 | ] 1214 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pdir2" 3 | version = "1.1.1" 4 | authors = [{ name = "laike9m", email = "laike9m@gmail.com" }] 5 | dependencies = ['colorama;platform_system=="Windows"', 'typing-extensions==4.*'] 6 | requires-python = ">=3.8" 7 | license = { text = "MIT" } 8 | description = "Pretty dir printing with joy" 9 | readme = "README.md" 10 | classifiers = [ 11 | 'Intended Audience :: Developers', 12 | 'License :: OSI Approved :: MIT License', 13 | 'Operating System :: MacOS :: MacOS X', 14 | 'Operating System :: Microsoft :: Windows', 15 | 'Operating System :: POSIX', 16 | 'Programming Language :: Python', 17 | 'Programming Language :: Python :: 3', 18 | 'Programming Language :: Python :: 3.8', 19 | 'Programming Language :: Python :: 3.9', 20 | 'Programming Language :: Python :: 3.10', 21 | 'Programming Language :: Python :: 3.11', 22 | 'Programming Language :: Python :: 3.12', 23 | 'Programming Language :: Python :: 3.13', 24 | 'Topic :: Software Development :: Libraries :: Python Modules', 25 | 'Programming Language :: Python :: Implementation :: PyPy', 26 | 'Programming Language :: Python :: Implementation :: CPython', 27 | ] 28 | 29 | [project.urls] 30 | Funding = "https://github.com/sponsors/laike9m" 31 | "Bug Tracker" = "https://github.com/laike9m/pdir2/issues" 32 | homepage = "https://github.com/laike9m/pdir2" 33 | repository = "https://github.com/laike9m/pdir2" 34 | 35 | [tool.pdm.dev-dependencies] 36 | dev = [ 37 | "pytest==7.*", 38 | "tox==4.*", 39 | "tox-pdm==0.5.*", 40 | "ptpython==3.0.*", 41 | "bpython>=0.24", 42 | "ipython==7.16.*", 43 | "pytest-mypy==0.10.*", 44 | "hypothesis==6.21.*", 45 | "tox-gh-actions==3.*", 46 | "mypy>=1.4", 47 | "twine>=4.0.2", 48 | ] 49 | 50 | [build-system] 51 | requires = ["pdm-pep517"] 52 | build-backend = "pdm.pep517.api" 53 | 54 | [tool.black] 55 | skip-string-normalization = true 56 | 57 | [[tool.mypy.overrides]] 58 | module = ["pytest"] 59 | ignore_missing_imports = true 60 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from unittest.mock import patch 4 | from sys import modules 5 | 6 | PDIR2_CONFIG_FILE = "PDIR2_CONFIG_FILE" 7 | 8 | 9 | def remove_module_cache(): 10 | """ 11 | There are some global settings which are initialized when pdir firstly 12 | import, then after that, no matter how you change the config or mock 13 | ``isatty()`` functions, those global values (settings, mostly), will 14 | not change. 15 | 16 | So in order to make some of our patches or test configurations work, we 17 | need to clear the import cache. So that when we ``import pdir`` in test 18 | cases, those global values will be initialized again due to the lack of 19 | cache. 20 | 21 | more detail: https://www.kawabangga.com/posts/4706 (Chinese) 22 | """ 23 | imported_modules = modules.keys() 24 | pdir_modules = [m for m in imported_modules if m.startswith('pdir')] 25 | for module in pdir_modules: 26 | try: 27 | del modules[module] 28 | except KeyError: 29 | pass 30 | 31 | 32 | @pytest.fixture 33 | def clean_cached_modules(): 34 | remove_module_cache() 35 | 36 | DEFAULT_CONFIG_FILE = os.path.join(os.path.expanduser('~'), '.pdir2config') 37 | yield DEFAULT_CONFIG_FILE 38 | if PDIR2_CONFIG_FILE in os.environ: 39 | if os.path.exists(os.environ[PDIR2_CONFIG_FILE]): 40 | os.remove(os.environ[PDIR2_CONFIG_FILE]) 41 | del os.environ[PDIR2_CONFIG_FILE] 42 | if os.path.exists(DEFAULT_CONFIG_FILE): 43 | os.remove(DEFAULT_CONFIG_FILE) 44 | 45 | 46 | @pytest.fixture(scope="session") 47 | def fake_tty(): 48 | with patch("sys.stdout.isatty") as faketty: 49 | faketty.return_value = True 50 | remove_module_cache() 51 | yield 52 | -------------------------------------------------------------------------------- /tests/data/config_1.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | doc-color = white 3 | category-color = bright yellow 4 | comma-color = bright green 5 | -------------------------------------------------------------------------------- /tests/data/config_2.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | uniform-color = white 3 | doc-color = black 4 | attribute-color = red 5 | -------------------------------------------------------------------------------- /tests/data/config_auto_color.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | enable-colorful-output = auto 3 | -------------------------------------------------------------------------------- /tests/data/config_disable_color.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | enable-colorful-output = False 3 | -------------------------------------------------------------------------------- /tests/data/config_enable_color.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | enable-colorful-output = True 3 | -------------------------------------------------------------------------------- /tests/data/empty_config.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laike9m/pdir2/780d1c98acb124e102d2e10d24a844192fd149f0/tests/data/empty_config.ini -------------------------------------------------------------------------------- /tests/data/error_config_1.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | attribute-color = red 3 | doc-color1 = blue 4 | -------------------------------------------------------------------------------- /tests/data/error_config_2.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | doc-color = blue 3 | attribute-color = 42 4 | -------------------------------------------------------------------------------- /tests/interactive_test.py: -------------------------------------------------------------------------------- 1 | def interactive_test(): 2 | """ 3 | This function runs pdir2 in bpython, ipython, ptpython. 4 | Note that giving the right output does not mean pdir2 works correctly, 5 | because print(string) is not equivalent to repr it in a REPL. 6 | To ensure everything truly works, manually verification is necessary. 7 | """ 8 | import pdir 9 | from pdir._internal_utils import _get_repl_type 10 | from pdir.constants import ReplType 11 | 12 | print('Environment: ' + _get_repl_type().value) 13 | import requests 14 | 15 | print('\nShould show result of pdir(requests):') 16 | print(pdir(requests)) 17 | # if any('bpython' in key for key in sys.modules): 18 | if _get_repl_type() == ReplType.BPYTHON: 19 | import sys 20 | 21 | # exit() in bpython interactive mode leads to a ValueError. 22 | # So I defined an exception hook to silent it. 23 | # sys.exit(0) is to make tox believe there's no error. 24 | def deal_with_exception_when_exit(a, b, c): 25 | sys.exit(0) 26 | 27 | sys.excepthook = deal_with_exception_when_exit 28 | exit() 29 | else: 30 | exit() 31 | 32 | 33 | if __name__ in ('__main__', '__console__'): 34 | interactive_test() 35 | -------------------------------------------------------------------------------- /tests/m.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | b = 2 3 | 4 | 5 | class OOO: 6 | """OOO today.""" 7 | 8 | pass 9 | 10 | 11 | def func(): 12 | """This is a function""" 13 | pass 14 | -------------------------------------------------------------------------------- /tests/test_buggy_attrs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test attrs that previously caused bugs. 3 | """ 4 | 5 | import pytest 6 | import pdir 7 | from pdir.attr_category import AttrCategory, category_match 8 | 9 | 10 | def test_dataframe(): 11 | pandas = pytest.importorskip("pandas") 12 | result = pdir(pandas.DataFrame) 13 | for attr in result.pattrs: 14 | if attr.name in ('columns', 'index'): 15 | assert category_match(attr.category, AttrCategory.PROPERTY) 16 | 17 | 18 | def test_type(): 19 | result = pdir(type) 20 | for attr in result.pattrs: 21 | if attr.name == '__abstractmethods__': 22 | assert category_match(attr.category, AttrCategory.ABSTRACT_CLASS) 23 | return 24 | 25 | 26 | def test_list(): 27 | result = pdir(list) 28 | for attr in result.pattrs: 29 | if attr.name == 'append': 30 | assert category_match(attr.category, AttrCategory.FUNCTION) 31 | return 32 | 33 | 34 | class D: 35 | """this is D""" 36 | 37 | def __init__(self): 38 | pass 39 | 40 | def __get__(self, instance, type=None): 41 | pass 42 | 43 | def __set__(self, instance, value): 44 | pass 45 | 46 | def __delete__(self, obj): 47 | pass 48 | 49 | 50 | class RevealAccess: 51 | """this is R""" 52 | 53 | def __init__(self, initval=None, name='var'): 54 | self.val = initval 55 | self.name = name 56 | 57 | def __get__(self, obj, objtype): 58 | print('Retrieving', self.name) 59 | return self.val 60 | 61 | def __set__(self, obj, val): 62 | print('Updating', self.name) 63 | self.val = val 64 | 65 | def __delete__(self, obj): 66 | pass 67 | 68 | 69 | def test_descriptor(): 70 | class T: 71 | r = RevealAccess(10, 'var ' r'') 72 | 73 | def __init__(self): 74 | self.d = D() 75 | 76 | @property 77 | def p(self): 78 | 'this is p' 79 | return 1 80 | 81 | @p.setter 82 | def p(self): 83 | pass 84 | 85 | @p.deleter 86 | def p(self): 87 | pass 88 | 89 | t = T() 90 | pattrs = pdir(t).pattrs 91 | for pattr in pattrs: 92 | if pattr.name == 'd': 93 | assert category_match(pattr.category, AttrCategory.DESCRIPTOR) 94 | assert pattr.doc == ('class D with getter, setter, deleter, ' 'this is D') 95 | if pattr.name == 'r': 96 | assert category_match(pattr.category, AttrCategory.DESCRIPTOR) 97 | assert pattr.doc == ( 98 | 'class RevealAccess with getter, setter, ' 'deleter, this is R' 99 | ) 100 | if pattr.name == 'p': 101 | assert category_match(pattr.category, AttrCategory.DESCRIPTOR) 102 | assert pattr.doc == ('@property with getter, setter, ' 'deleter, this is p') 103 | 104 | 105 | def test_override_dir(): 106 | # In the class attrs in `__dir__()` can not be found in `__dict__` 107 | class ClassWithUserDefinedDir: 108 | def __dir__(self): 109 | return ['foo'] 110 | 111 | inst = ClassWithUserDefinedDir() 112 | pattrs = pdir(inst).pattrs 113 | assert ('foo' in pdir(inst)) == ('foo' in dir(inst)) 114 | assert ('foo' in [pattr.name for pattr in pattrs]) == ('foo' in dir(inst)) 115 | 116 | 117 | def test_get_attribute_fail(): 118 | """ "Tests if get_online_doc returns '' when __doc__ access throws an exception.""" 119 | 120 | class DocAttributeFail: 121 | """Fails when __doc__ atribute is accessed.""" 122 | 123 | def __getattribute__(self, name): 124 | if name == '__doc__': 125 | raise Exception('failed successfully') 126 | else: 127 | return super().__getattribute__(name) 128 | 129 | class DocFailContainer: 130 | """Holds attributes that fail when __doc__ is accessed.""" 131 | 132 | dac1 = DocAttributeFail() 133 | 134 | def __init__(self): 135 | self.dac2 = DocAttributeFail() 136 | 137 | for pattr in pdir(DocFailContainer()).pattrs: 138 | if pattr.name in ['dac1', 'dac2']: 139 | assert pattr.get_oneline_doc() == '' 140 | -------------------------------------------------------------------------------- /tests/test_container.py: -------------------------------------------------------------------------------- 1 | import pdir 2 | 3 | 4 | def test_acting_like_a_list(): 5 | dadada = 1 6 | cadada = 1 7 | vadada = 1 8 | apple1 = 1 9 | xapple2 = 1 10 | result, correct = pdir(), dir() 11 | assert len(correct) == len(result) 12 | 13 | for x, y in zip(correct, result): 14 | assert x == y 15 | 16 | 17 | def test_acting_like_a_list_when_search(): 18 | dadada = 1 19 | cadada = 1 20 | vadada = 1 21 | apple1 = 1 22 | xapple2 = 1 23 | result = pdir().s('apple') 24 | assert len(result) == 2 25 | assert list(result) == ['apple1', 'xapple2'] 26 | 27 | 28 | def test_attr_order(): 29 | dir_attrs = dir(None) 30 | pdir_attrs = list(pdir(None)) 31 | assert dir_attrs == pdir_attrs 32 | -------------------------------------------------------------------------------- /tests/test_disbale_color.py: -------------------------------------------------------------------------------- 1 | from pdir.color import _ColorDisabled 2 | 3 | 4 | def test_color_disabled(): 5 | c = _ColorDisabled() 6 | assert c.wrap_text("foobar") == "foobar" 7 | 8 | d = _ColorDisabled() 9 | assert c == d 10 | assert c is not d 11 | -------------------------------------------------------------------------------- /tests/test_filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests attribute filter's behaviors. 3 | """ 4 | 5 | import collections 6 | import sys 7 | import pdir 8 | 9 | 10 | # https://github.com/python/cpython/blob/da2bf9f66d0c95b988c5d87646d168f65499b316/Lib/unittest/case.py#L1164-L1195 11 | def check_items_equality(first, second): 12 | """A simplified version of the unittest.assertEqual method.""" 13 | first_seq, second_seq = list(first), list(second) 14 | first = collections.Counter(first_seq) 15 | second = collections.Counter(second_seq) 16 | assert first == second 17 | 18 | 19 | class Base: 20 | base_class_variable = 1 21 | 22 | def __init__(self): 23 | self.base_instance_variable = 2 24 | 25 | def base_method(self): 26 | pass 27 | 28 | 29 | class DerivedClass(Base): 30 | derived_class_variable = 1 31 | 32 | def __init__(self): 33 | self.derived_instance_variable = 2 34 | super().__init__() 35 | 36 | def derived_method(self): 37 | pass 38 | 39 | 40 | inst = DerivedClass() 41 | 42 | 43 | def test_properties(): 44 | check_items_equality( 45 | [p.name for p in pdir(inst).properties.pattrs], 46 | [ 47 | 'base_class_variable', 48 | 'base_instance_variable', 49 | 'derived_class_variable', 50 | 'derived_instance_variable', 51 | '__class__', 52 | '__dict__', 53 | '__doc__', 54 | '__module__', 55 | '__weakref__', 56 | ], 57 | ) 58 | 59 | 60 | def test_methods(): 61 | extra_items = ['__getstate__'] if sys.version_info >= (3, 11) else [] 62 | check_items_equality( 63 | [p.name for p in pdir(inst).methods.pattrs], 64 | [ 65 | '__subclasshook__', 66 | '__delattr__', 67 | '__dir__', 68 | '__getattribute__', 69 | '__setattr__', 70 | '__init_subclass__', 71 | 'base_method', 72 | 'derived_method', 73 | '__format__', 74 | '__hash__', 75 | '__init__', 76 | '__new__', 77 | '__repr__', 78 | '__sizeof__', 79 | '__str__', 80 | '__reduce__', 81 | '__reduce_ex__', 82 | '__eq__', 83 | '__ge__', 84 | '__gt__', 85 | '__le__', 86 | '__lt__', 87 | '__ne__', 88 | ] 89 | + extra_items, 90 | ) 91 | 92 | 93 | def test_public(): 94 | check_items_equality( 95 | [p.name for p in pdir(inst).public.pattrs], 96 | [ 97 | 'base_method', 98 | 'derived_method', 99 | 'base_class_variable', 100 | 'base_instance_variable', 101 | 'derived_class_variable', 102 | 'derived_instance_variable', 103 | ], 104 | ) 105 | 106 | 107 | def test_own(): 108 | check_items_equality( 109 | [p.name for p in pdir(inst).own.pattrs], 110 | [ 111 | 'derived_method', 112 | '__init__', 113 | 'base_instance_variable', 114 | 'derived_class_variable', 115 | 'derived_instance_variable', 116 | '__doc__', 117 | '__module__', 118 | ], 119 | ) 120 | 121 | 122 | def test_chained_filters(): 123 | check_items_equality( 124 | [p.name for p in pdir(inst).public.own.properties.pattrs], 125 | [ 126 | 'base_instance_variable', 127 | 'derived_class_variable', 128 | 'derived_instance_variable', 129 | ], 130 | ) 131 | 132 | 133 | def test_order_of_chained_filters(): 134 | check_items_equality( 135 | [p.name for p in pdir(inst).own.properties.public.pattrs], 136 | [ 137 | 'base_instance_variable', 138 | 'derived_class_variable', 139 | 'derived_instance_variable', 140 | ], 141 | ) 142 | check_items_equality( 143 | [p.name for p in pdir(inst).properties.public.own.pattrs], 144 | [ 145 | 'base_instance_variable', 146 | 'derived_class_variable', 147 | 'derived_instance_variable', 148 | ], 149 | ) 150 | 151 | 152 | def test_filters_with_search(): 153 | def test_chained_filters(): 154 | check_items_equality( 155 | [ 156 | p.name 157 | for p in pdir(inst).public.own.properties.search('derived_in').pattrs 158 | ], 159 | ['derived_instance_variable'], 160 | ) 161 | -------------------------------------------------------------------------------- /tests/test_pdir_format.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | 4 | 5 | def test_formatter_integrity(fake_tty): 6 | from pdir.attr_category import AttrCategory 7 | from pdir.format import _FORMATTER 8 | 9 | for ac in AttrCategory: 10 | assert ac in _FORMATTER 11 | 12 | 13 | def test_pdir_module(fake_tty): 14 | import pdir 15 | import m 16 | 17 | result = pdir(m) 18 | expected = '\n'.join( 19 | [ 20 | '\x1b[0;33mproperty:\x1b[0m', 21 | ( 22 | ' \x1b[0;36m__builtins__\x1b[0m\x1b[1;30m, ' 23 | '\x1b[0m\x1b[0;36ma\x1b[0m\x1b[1;30m, ' 24 | '\x1b[0m\x1b[0;36mb\x1b[0m' 25 | ), 26 | '\x1b[0;33mmodule attribute:\x1b[0m', 27 | ( 28 | ' \x1b[0;36m__cached__\x1b[0m\x1b[1;30m, ' 29 | '\x1b[0m\x1b[0;36m__file__\x1b[0m\x1b[1;30m, ' 30 | '\x1b[0m\x1b[0;36m__loader__\x1b[0m\x1b[1;30m, ' 31 | '\x1b[0m\x1b[0;36m__name__\x1b[0m\x1b[1;30m, ' 32 | '\x1b[0m\x1b[0;36m__package__\x1b[0m\x1b[1;30m, ' 33 | '\x1b[0m\x1b[0;36m__spec__\x1b[0m' 34 | ), 35 | '\x1b[0;33mspecial attribute:\x1b[0m', 36 | ' \x1b[0;36m__doc__\x1b[0m', 37 | '\x1b[0;33mclass:\x1b[0m', 38 | ' \x1b[0;36mOOO\x1b[0m\x1b[0;36m: ' 39 | '\x1b[0m\x1b[1;30mOOO today.\x1b[0m', 40 | '\x1b[0;33mfunction:\x1b[0m', 41 | ( 42 | ' \x1b[0;36mfunc\x1b[0m\x1b[0;36m: ' 43 | '\x1b[0m\x1b[1;30mThis is a function\x1b[0m' 44 | ), 45 | ] 46 | ) 47 | assert repr(result) == expected 48 | print(result) 49 | del m 50 | 51 | 52 | def test_pdir_object(fake_tty): 53 | import pdir 54 | 55 | class T: 56 | def what(self): 57 | """doc line""" 58 | pass 59 | 60 | result = pdir(T()) 61 | print(result) # TODO: add real test. 62 | 63 | 64 | def test_dir_without_argument(fake_tty): 65 | import pdir 66 | 67 | a = 1 68 | b = 2 69 | 70 | def whatever(): 71 | """One line doc.""" 72 | pass 73 | 74 | result = pdir() 75 | assert repr(result) == '\n'.join( 76 | [ 77 | '\x1b[0;33mproperty:\x1b[0m', 78 | ( 79 | ' \x1b[0;36ma\x1b[0m\x1b[1;30m, \x1b[0m\x1b[0;36mb\x1b[0m' 80 | '\x1b[1;30m, \x1b[0m\x1b[0;36mfake_tty\x1b[0m' 81 | ), 82 | '\x1b[0;33mclass:\x1b[0m', 83 | ( 84 | ' \x1b[0;36mpdir\x1b[0m\x1b[0;36m: \x1b[0m\x1b[1;30mClass ' 85 | 'that provides pretty dir and search API.\x1b[0m' 86 | ), 87 | '\x1b[0;33mfunction:\x1b[0m', 88 | ( 89 | ' \x1b[0;36mwhatever\x1b[0m\x1b[0;36m: ' 90 | '\x1b[0m\x1b[1;30mOne line doc.\x1b[0m' 91 | ), 92 | ] 93 | ) 94 | print(result) 95 | 96 | 97 | def test_slots(fake_tty): 98 | import pdir 99 | 100 | class A: 101 | __slots__ = ['__mul__', '__hash__', 'a', 'b'] 102 | 103 | a = A() 104 | result = pdir(a) 105 | 106 | expected = '\n'.join( 107 | [ 108 | '\x1b[0;33mspecial attribute:\x1b[0m', 109 | ( 110 | ' \x1b[0;36m__class__\x1b[0m\x1b[1;30m, ' 111 | '\x1b[0m\x1b[0;36m__doc__\x1b[0m\x1b[1;30m, ' 112 | '\x1b[0m\x1b[0;36m__module__\x1b[0m\x1b[1;30m, ' 113 | '\x1b[0m\x1b[0;36m__slots__\x1b[0m' 114 | ), 115 | '\x1b[0;33mabstract class:\x1b[0m', 116 | ' \x1b[0;36m__subclasshook__\x1b[0m', 117 | '\x1b[0;33marithmetic:\x1b[0m', 118 | ' \x1b[0;36m__mul__\x1b[0m\x1b[0;35m(slotted)\x1b[0m', 119 | '\x1b[0;33mobject customization:\x1b[0m', 120 | ( 121 | ' \x1b[0;36m__format__\x1b[0m\x1b[1;30m, ' 122 | '\x1b[0m\x1b[0;36m__hash__\x1b[0m' 123 | '\x1b[0;35m(slotted)\x1b[0m\x1b[1;30m, ' 124 | '\x1b[0m\x1b[0;36m__init__\x1b[0m\x1b[1;30m, ' 125 | '\x1b[0m\x1b[0;36m__new__\x1b[0m\x1b[1;30m, ' 126 | '\x1b[0m\x1b[0;36m__repr__\x1b[0m\x1b[1;30m, ' 127 | '\x1b[0m\x1b[0;36m__sizeof__\x1b[0m\x1b[1;30m, ' 128 | '\x1b[0m\x1b[0;36m__str__\x1b[0m' 129 | ), 130 | '\x1b[0;33mrich comparison:\x1b[0m', 131 | ( 132 | ' \x1b[0;36m__eq__\x1b[0m\x1b[1;30m, ' 133 | '\x1b[0m\x1b[0;36m__ge__\x1b[0m\x1b[1;30m, ' 134 | '\x1b[0m\x1b[0;36m__gt__\x1b[0m\x1b[1;30m, ' 135 | '\x1b[0m\x1b[0;36m__le__\x1b[0m\x1b[1;30m, ' 136 | '\x1b[0m\x1b[0;36m__lt__\x1b[0m\x1b[1;30m, ' 137 | '\x1b[0m\x1b[0;36m__ne__\x1b[0m' 138 | ), 139 | '\x1b[0;33mattribute access:\x1b[0m', 140 | ( 141 | ' \x1b[0;36m__delattr__\x1b[0m\x1b[1;30m, ' 142 | '\x1b[0m\x1b[0;36m__dir__\x1b[0m\x1b[1;30m, ' 143 | '\x1b[0m\x1b[0;36m__getattribute__\x1b[0m\x1b[1;30m, ' 144 | '\x1b[0m\x1b[0;36m__setattr__\x1b[0m' 145 | ), 146 | '\x1b[0;33mclass customization:\x1b[0m', 147 | ' \x1b[0;36m__init_subclass__\x1b[0m', 148 | '\x1b[0;33mpickle:\x1b[0m', 149 | ( 150 | ( 151 | ' \x1b[0;36m__getstate__\x1b[0m\x1b[1;30m, ' 152 | '\x1b[0m\x1b[0;36m__reduce__\x1b[0m\x1b[1;30m, ' 153 | '\x1b[0m\x1b[0;36m__reduce_ex__\x1b[0m' 154 | ) 155 | if sys.version_info >= (3, 11) 156 | else ( 157 | ' \x1b[0;36m__reduce__\x1b[0m\x1b[1;30m, ' 158 | '\x1b[0m\x1b[0;36m__reduce_ex__\x1b[0m' 159 | ) 160 | ), 161 | '\x1b[0;33mdescriptor:\x1b[0m', 162 | ( 163 | ' \x1b[0;36ma\x1b[0m' 164 | '\x1b[0;35m(slotted)\x1b[0m\x1b[0;36m: ' 165 | '\x1b[0m\x1b[1;30mclass member_descriptor with ' 166 | 'getter, setter, deleter\x1b[0m' 167 | ), 168 | ( 169 | ' \x1b[0;36mb\x1b[0m' 170 | '\x1b[0;35m(slotted)\x1b[0m\x1b[0;36m: ' 171 | '\x1b[0m\x1b[1;30mclass member_descriptor with ' 172 | 'getter, setter, deleter\x1b[0m' 173 | ), 174 | ] 175 | ) 176 | assert repr(result) == expected 177 | 178 | 179 | @pytest.mark.parametrize( 180 | 'docstring, first_line', 181 | [ 182 | ('', ''), 183 | ('Foobar', 'Foobar'), 184 | ('Foobar.', 'Foobar.'), 185 | ('Foo\nbar', 'Foo bar'), 186 | ('Foo\nbar.', 'Foo bar.'), 187 | ('Return nothing.\nNo exceptions.', 'Return nothing.'), 188 | ('Return a.b as\nresult', 'Return a.b as result'), 189 | ], 190 | ) 191 | def test_get_first_line_of_docstring(docstring, first_line, fake_tty): 192 | from pdir._internal_utils import get_first_sentence_of_docstring 193 | 194 | CustomClass = type('CustomClass', (object,), {}) 195 | setattr(CustomClass, '__doc__', docstring) 196 | assert get_first_sentence_of_docstring(CustomClass) == first_line 197 | -------------------------------------------------------------------------------- /tests/test_search.py: -------------------------------------------------------------------------------- 1 | def test_search_without_argument(fake_tty): 2 | import pdir 3 | 4 | foo = 1 5 | bar = 1 6 | baz = 1 7 | apple1 = 1 8 | xapple2 = 1 9 | result, result2 = pdir().s('apple'), pdir().search('apple') 10 | assert repr(result) == ( 11 | '\x1b[0;33mproperty:\x1b[0m\n' 12 | ' \x1b[0;36mapple1\x1b[0m\x1b[1;30m,' 13 | ' \x1b[0m\x1b[0;36mxapple2\x1b[0m' 14 | ) 15 | assert repr(result2) == ( 16 | '\x1b[0;33mproperty:\x1b[0m\n' 17 | ' \x1b[0;36mapple1\x1b[0m\x1b[1;30m,' 18 | ' \x1b[0m\x1b[0;36mxapple2\x1b[0m' 19 | ) 20 | 21 | 22 | def test_search_with_argument(fake_tty): 23 | import pdir 24 | 25 | class T: 26 | pass 27 | 28 | result, result2 = pdir(T).s('attr'), pdir(T).search('attr') 29 | result3, result4 = pdir(T).s('Attr'), pdir(T).search('Attr') 30 | expected = '\n'.join( 31 | [ 32 | '\x1b[0;33mattribute access:\x1b[0m', 33 | ( 34 | ' \x1b[0;36m__delattr__\x1b[0m\x1b[1;30m, ' 35 | '\x1b[0m\x1b[0;36m__getattribute__\x1b[0m\x1b[1;30m, ' 36 | '\x1b[0m\x1b[0;36m__setattr__\x1b[0m' 37 | ), 38 | ] 39 | ) 40 | assert repr(result) == expected 41 | assert repr(result2) == expected 42 | assert repr(result3) == expected 43 | assert repr(result4) == expected 44 | 45 | # Case sensitive 46 | result5, result6 = pdir(T).s('Attr', True), pdir(T).search('Attr', True) 47 | assert repr(result5) == '' 48 | assert repr(result6) == '' 49 | -------------------------------------------------------------------------------- /tests/test_slots.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test classes with __slots__ 3 | """ 4 | 5 | from typing import List 6 | import pdir 7 | from pdir.attr_category import AttrCategory, category_match 8 | 9 | 10 | BASE = 'base' 11 | DERIVE = 'derive' 12 | 13 | 14 | class BaseNoSlot: 15 | pass 16 | 17 | 18 | class BaseEmptySlot: 19 | __slots__: List[str] = [] 20 | 21 | 22 | class BaseSlot: 23 | __slots__: List[str] = [BASE] 24 | 25 | 26 | class DeriveNoSlotBaseEmpty(BaseEmptySlot): 27 | pass 28 | 29 | 30 | class DeriveNoSlotBaseSlot(BaseSlot): 31 | pass 32 | 33 | 34 | class DeriveEmptySlotBaseNo(BaseNoSlot): 35 | __slots__: List[str] = [] 36 | 37 | 38 | class DeriveEmptySlotBaseEmpty(BaseEmptySlot): 39 | __slots__: List[str] = [] 40 | 41 | 42 | class DeriveEmptySlotBaseSlot(BaseSlot): 43 | __slots__: List[str] = [] 44 | 45 | 46 | class DeriveSlotBaseNo(BaseNoSlot): 47 | __slots__ = [DERIVE] 48 | 49 | 50 | class DeriveSlotBaseEmpty(BaseEmptySlot): 51 | __slots__ = [DERIVE] 52 | 53 | 54 | class DeriveSlotBaseSlot(BaseSlot): 55 | __slots__ = [DERIVE] 56 | 57 | 58 | def test_not_set(): 59 | expected_res = [ # class type empty slot attr num 60 | (DeriveNoSlotBaseEmpty, 0), 61 | (DeriveNoSlotBaseSlot, 1), 62 | (DeriveEmptySlotBaseNo, 0), 63 | (DeriveEmptySlotBaseEmpty, 0), 64 | (DeriveEmptySlotBaseSlot, 1), 65 | (DeriveSlotBaseNo, 1), 66 | (DeriveSlotBaseEmpty, 1), 67 | (DeriveSlotBaseSlot, 2), 68 | ] 69 | for c_type, attr_num in expected_res: 70 | attr_count = 0 71 | for attr in pdir(c_type()).pattrs: 72 | if attr.name in [BASE, DERIVE]: 73 | attr_count += 1 74 | assert category_match(attr.category, AttrCategory.DESCRIPTOR) 75 | assert category_match(attr.category, AttrCategory.SLOT) 76 | assert attr_count == attr_num 77 | 78 | 79 | def test_set_derive(): 80 | c_types = [DeriveSlotBaseNo, DeriveSlotBaseEmpty, DeriveSlotBaseSlot] 81 | for c_type in c_types: 82 | instance = c_type() 83 | instance.derive = 'foo' 84 | for attr in pdir(instance).pattrs: 85 | if attr.name == DERIVE: 86 | assert category_match(attr.category, AttrCategory.DESCRIPTOR) 87 | assert category_match(attr.category, AttrCategory.SLOT) 88 | break 89 | else: 90 | # No derive attribute found 91 | assert False 92 | 93 | 94 | def test_set_base(): 95 | c_types = [DeriveNoSlotBaseSlot, DeriveEmptySlotBaseSlot, DeriveSlotBaseSlot] 96 | for c_type in c_types: 97 | instance = c_type() 98 | instance.base = 'foo' 99 | for attr in pdir(instance).pattrs: 100 | if attr.name == BASE: 101 | assert category_match(attr.category, AttrCategory.DESCRIPTOR) 102 | assert category_match(attr.category, AttrCategory.SLOT) 103 | break 104 | else: 105 | # No base attribute found 106 | assert False 107 | -------------------------------------------------------------------------------- /tests/test_user_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test user configuration. 3 | """ 4 | 5 | import os 6 | import shutil 7 | 8 | import pytest 9 | 10 | 11 | def test_default_env_without_config(fake_tty, clean_cached_modules): 12 | import pdir 13 | 14 | pdir() 15 | 16 | 17 | def test_set_env_without_config(fake_tty, clean_cached_modules): 18 | os.environ['PDIR2_CONFIG_FILE'] = 'aaa' 19 | with pytest.raises(OSError, match='Config file not exist: aaa'): 20 | import pdir 21 | 22 | pdir() 23 | 24 | 25 | def test_read_config(fake_tty, clean_cached_modules): 26 | # 'clean' is the DEFAULT_CONFIG_FILE yielded from fixture. 27 | shutil.copyfile('tests/data/config_1.ini', clean_cached_modules) 28 | from pdir.format import doc_color, category_color, attribute_color, comma 29 | from pdir.color import COLORS 30 | 31 | assert doc_color == COLORS['white'] 32 | assert category_color == COLORS['bright yellow'] 33 | assert comma == '\033[1;32m, \033[0m' 34 | assert attribute_color == COLORS['cyan'] 35 | 36 | 37 | def test_config_disable_color_tty(fake_tty, clean_cached_modules): 38 | # 'clean' is the DEFAULT_CONFIG_FILE yielded from fixture. 39 | shutil.copyfile('tests/data/config_disable_color.ini', clean_cached_modules) 40 | from pdir.format import doc_color, category_color, attribute_color, comma 41 | from pdir.color import COLOR_DISABLED 42 | 43 | assert doc_color == COLOR_DISABLED 44 | assert category_color == COLOR_DISABLED 45 | assert comma == ', ' 46 | assert attribute_color == COLOR_DISABLED 47 | 48 | 49 | def test_config_auto_color_tty(fake_tty, clean_cached_modules): 50 | # 'clean' is the DEFAULT_CONFIG_FILE yielded from fixture. 51 | shutil.copyfile('tests/data/config_auto_color.ini', clean_cached_modules) 52 | from pdir.format import doc_color 53 | from pdir.color import COLORS 54 | 55 | assert doc_color == COLORS['grey'] 56 | 57 | 58 | def test_config_enable_color_not_tty(clean_cached_modules): 59 | # 'clean' is the DEFAULT_CONFIG_FILE yielded from fixture. 60 | shutil.copyfile('tests/data/config_enable_color.ini', clean_cached_modules) 61 | from pdir.format import doc_color 62 | from pdir.color import COLORS 63 | 64 | assert doc_color == COLORS['grey'] 65 | 66 | 67 | def test_env_disable_color_even_config_set(fake_tty, clean_cached_modules): 68 | shutil.copyfile('tests/data/config_enable_color.ini', clean_cached_modules) 69 | os.environ['PDIR2_NOCOLOR'] = "true" 70 | from pdir.format import doc_color, category_color, attribute_color, comma 71 | from pdir.color import COLOR_DISABLED 72 | 73 | assert doc_color == COLOR_DISABLED 74 | assert category_color == COLOR_DISABLED 75 | assert comma == ', ' 76 | assert attribute_color == COLOR_DISABLED 77 | 78 | del os.environ['PDIR2_NOCOLOR'] 79 | 80 | 81 | def test_read_config_from_custom_location(fake_tty, clean_cached_modules): 82 | os.environ['PDIR2_CONFIG_FILE'] = os.path.join(os.path.expanduser('~'), '.myconfig') 83 | shutil.copyfile('tests/data/config_1.ini', os.environ['PDIR2_CONFIG_FILE']) 84 | from pdir.format import doc_color, category_color, attribute_color, comma 85 | from pdir.color import COLORS 86 | 87 | assert doc_color == COLORS['white'] 88 | assert category_color == COLORS['bright yellow'] 89 | assert comma == '\033[1;32m, \033[0m' 90 | assert attribute_color == COLORS['cyan'] 91 | 92 | 93 | def test_uniform_color(fake_tty, clean_cached_modules): 94 | shutil.copyfile('tests/data/config_2.ini', clean_cached_modules) 95 | from pdir.format import doc_color, category_color, attribute_color, comma 96 | from pdir.color import COLORS 97 | 98 | assert doc_color == COLORS['white'] 99 | assert category_color == COLORS['white'] 100 | assert comma == '\033[0;37m, \033[0m' 101 | assert attribute_color == COLORS['white'] 102 | 103 | 104 | def test_empty_config(fake_tty, clean_cached_modules): 105 | shutil.copyfile('tests/data/empty_config.ini', clean_cached_modules) 106 | from pdir.format import doc_color, category_color, attribute_color, comma 107 | from pdir.color import COLORS 108 | 109 | assert doc_color == COLORS['grey'] 110 | assert category_color == COLORS['yellow'] 111 | assert comma == '\033[1;30m, \033[0m' 112 | assert attribute_color == COLORS['cyan'] 113 | 114 | 115 | def test_invalid_config_1(clean_cached_modules): 116 | shutil.copyfile('tests/data/error_config_1.ini', clean_cached_modules) 117 | with pytest.raises(ValueError, match='Invalid key: doc-color1'): 118 | import pdir 119 | 120 | pdir() 121 | 122 | 123 | def test_invalid_config_2(clean_cached_modules): 124 | shutil.copyfile('tests/data/error_config_2.ini', clean_cached_modules) 125 | with pytest.raises(ValueError, match='Invalid color value: 42'): 126 | import pdir 127 | 128 | pdir() 129 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = python3.8,python3.9,python3.10,python3.11,python3.12,extra,mypy 3 | isolated_build = True 4 | 5 | [gh-actions] 6 | python = 7 | 3.8: python3.8 8 | 3.9: python3.9 9 | 3.10: python3.10 10 | 3.11: python3.11 11 | 3.12: python3.12 12 | 13 | [testenv] 14 | commands_pre= 15 | pip install pandas 16 | commands= 17 | pytest -s -vv {posargs} {toxinidir}/tests/ 18 | 19 | [testenv:extra] 20 | changedir={toxinidir}/tests/ 21 | setenv= 22 | TERM=linux 23 | TERMINFO=/etc/terminfo 24 | commands= 25 | ptpython interactive_test.py --interactive 26 | bpython -i interactive_test.py 27 | ipython interactive_test.py 28 | 29 | [testenv:mypy] 30 | commands = pytest -s --mypy {toxinidir}/tests/ \ 31 | --ignore={toxinidir}/tests/interactive_test.py 32 | --------------------------------------------------------------------------------