├── .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 | 
4 | [](https://pypi.python.org/pypi/pdir2/)
5 | 
6 |
7 |
8 | Have you ever dreamed of a better output of `dir()`? I do. So I created this.
9 |
10 | 
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 | 
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 |
--------------------------------------------------------------------------------