Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +162 -0
- .venv/Lib/site-packages/PyYAML-6.0.2.dist-info/INSTALLER +1 -0
- .venv/Lib/site-packages/PyYAML-6.0.2.dist-info/LICENSE +20 -0
- .venv/Lib/site-packages/PyYAML-6.0.2.dist-info/METADATA +46 -0
- .venv/Lib/site-packages/PyYAML-6.0.2.dist-info/RECORD +38 -0
- .venv/Lib/site-packages/PyYAML-6.0.2.dist-info/WHEEL +5 -0
- .venv/Lib/site-packages/PyYAML-6.0.2.dist-info/top_level.txt +2 -0
- .venv/Lib/site-packages/certifi-2025.8.3.dist-info/INSTALLER +1 -0
- .venv/Lib/site-packages/certifi-2025.8.3.dist-info/top_level.txt +1 -0
- .venv/Lib/site-packages/certifi/__main__.py +12 -0
- .venv/Lib/site-packages/certifi/cacert.pem +0 -0
- .venv/Lib/site-packages/certifi/core.py +83 -0
- .venv/Lib/site-packages/certifi/py.typed +0 -0
- .venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/INSTALLER +1 -0
- .venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/METADATA +170 -0
- .venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/RECORD +271 -0
- .venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/WHEEL +5 -0
- .venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/top_level.txt +1 -0
- .venv/Lib/site-packages/prompt_toolkit/__init__.py +53 -0
- .venv/Lib/site-packages/prompt_toolkit/document.py +1182 -0
- .venv/Lib/site-packages/prompt_toolkit/enums.py +19 -0
- .venv/Lib/site-packages/prompt_toolkit/history.py +306 -0
- .venv/Lib/site-packages/prompt_toolkit/keys.py +222 -0
- .venv/Lib/site-packages/prompt_toolkit/log.py +13 -0
- .venv/Lib/site-packages/prompt_toolkit/mouse_events.py +85 -0
- .venv/Lib/site-packages/prompt_toolkit/patch_stdout.py +297 -0
- .venv/Lib/site-packages/prompt_toolkit/py.typed +0 -0
- .venv/Lib/site-packages/prompt_toolkit/renderer.py +820 -0
- .venv/Lib/site-packages/prompt_toolkit/search.py +226 -0
- .venv/Lib/site-packages/prompt_toolkit/selection.py +58 -0
- .venv/Lib/site-packages/prompt_toolkit/token.py +9 -0
- .venv/Lib/site-packages/prompt_toolkit/utils.py +327 -0
- .venv/Lib/site-packages/prompt_toolkit/validation.py +192 -0
- .venv/Lib/site-packages/prompt_toolkit/win32_types.py +229 -0
- .venv/Lib/site-packages/requests-2.32.5.dist-info/INSTALLER +1 -0
- .venv/Lib/site-packages/requests-2.32.5.dist-info/METADATA +133 -0
- .venv/Lib/site-packages/requests-2.32.5.dist-info/RECORD +37 -0
- .venv/Lib/site-packages/requests-2.32.5.dist-info/WHEEL +5 -0
- .venv/Lib/site-packages/requests-2.32.5.dist-info/top_level.txt +1 -0
- .venv/Lib/site-packages/requests/__init__.py +184 -0
- .venv/Lib/site-packages/requests/__version__.py +14 -0
- .venv/Lib/site-packages/requests/_internal_utils.py +50 -0
- .venv/Lib/site-packages/requests/adapters.py +696 -0
- .venv/Lib/site-packages/requests/api.py +157 -0
- .venv/Lib/site-packages/requests/auth.py +314 -0
- .venv/Lib/site-packages/requests/certs.py +17 -0
- .venv/Lib/site-packages/requests/compat.py +106 -0
- .venv/Lib/site-packages/requests/cookies.py +561 -0
- .venv/Lib/site-packages/requests/exceptions.py +151 -0
- .venv/Lib/site-packages/requests/help.py +134 -0
.gitignore
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py,cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Translations
|
| 55 |
+
*.mo
|
| 56 |
+
*.pot
|
| 57 |
+
|
| 58 |
+
# Django stuff:
|
| 59 |
+
*.log
|
| 60 |
+
local_settings.py
|
| 61 |
+
db.sqlite3
|
| 62 |
+
db.sqlite3-journal
|
| 63 |
+
|
| 64 |
+
# Flask stuff:
|
| 65 |
+
instance/
|
| 66 |
+
.webassets-cache
|
| 67 |
+
|
| 68 |
+
# Scrapy stuff:
|
| 69 |
+
.scrapy
|
| 70 |
+
|
| 71 |
+
# Sphinx documentation
|
| 72 |
+
docs/_build/
|
| 73 |
+
|
| 74 |
+
# PyBuilder
|
| 75 |
+
.pybuilder/
|
| 76 |
+
target/
|
| 77 |
+
|
| 78 |
+
# Jupyter Notebook
|
| 79 |
+
.ipynb_checkpoints
|
| 80 |
+
|
| 81 |
+
# IPython
|
| 82 |
+
profile_default/
|
| 83 |
+
ipython_config.py
|
| 84 |
+
|
| 85 |
+
# pyenv
|
| 86 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 88 |
+
# .python-version
|
| 89 |
+
|
| 90 |
+
# pipenv
|
| 91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 94 |
+
# install all needed dependencies.
|
| 95 |
+
#Pipfile.lock
|
| 96 |
+
|
| 97 |
+
# poetry
|
| 98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 100 |
+
# commonly ignored for libraries.
|
| 101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 102 |
+
#poetry.lock
|
| 103 |
+
|
| 104 |
+
# pdm
|
| 105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 106 |
+
#pdm.lock
|
| 107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 108 |
+
# in version control.
|
| 109 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
| 110 |
+
.pdm.toml
|
| 111 |
+
.pdm-python
|
| 112 |
+
.pdm-build/
|
| 113 |
+
|
| 114 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 115 |
+
__pypackages__/
|
| 116 |
+
|
| 117 |
+
# Celery stuff
|
| 118 |
+
celerybeat-schedule
|
| 119 |
+
celerybeat.pid
|
| 120 |
+
|
| 121 |
+
# SageMath parsed files
|
| 122 |
+
*.sage.py
|
| 123 |
+
|
| 124 |
+
# Environments
|
| 125 |
+
.env
|
| 126 |
+
.venv
|
| 127 |
+
env/
|
| 128 |
+
venv/
|
| 129 |
+
ENV/
|
| 130 |
+
env.bak/
|
| 131 |
+
venv.bak/
|
| 132 |
+
|
| 133 |
+
# Spyder project settings
|
| 134 |
+
.spyderproject
|
| 135 |
+
.spyproject
|
| 136 |
+
|
| 137 |
+
# Rope project settings
|
| 138 |
+
.ropeproject
|
| 139 |
+
|
| 140 |
+
# mkdocs documentation
|
| 141 |
+
/site
|
| 142 |
+
|
| 143 |
+
# mypy
|
| 144 |
+
.mypy_cache/
|
| 145 |
+
.dmypy.json
|
| 146 |
+
dmypy.json
|
| 147 |
+
|
| 148 |
+
# Pyre type checker
|
| 149 |
+
.pyre/
|
| 150 |
+
|
| 151 |
+
# pytype static type analyzer
|
| 152 |
+
.pytype/
|
| 153 |
+
|
| 154 |
+
# Cython debug symbols
|
| 155 |
+
cython_debug/
|
| 156 |
+
|
| 157 |
+
# PyCharm
|
| 158 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 159 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 160 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 161 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 162 |
+
#.idea/
|
.venv/Lib/site-packages/PyYAML-6.0.2.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/Lib/site-packages/PyYAML-6.0.2.dist-info/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2017-2021 Ingy döt Net
|
| 2 |
+
Copyright (c) 2006-2016 Kirill Simonov
|
| 3 |
+
|
| 4 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
| 5 |
+
this software and associated documentation files (the "Software"), to deal in
|
| 6 |
+
the Software without restriction, including without limitation the rights to
|
| 7 |
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
| 8 |
+
of the Software, and to permit persons to whom the Software is furnished to do
|
| 9 |
+
so, subject to the following conditions:
|
| 10 |
+
|
| 11 |
+
The above copyright notice and this permission notice shall be included in all
|
| 12 |
+
copies or substantial portions of the Software.
|
| 13 |
+
|
| 14 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 15 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 16 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 17 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 18 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 19 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 20 |
+
SOFTWARE.
|
.venv/Lib/site-packages/PyYAML-6.0.2.dist-info/METADATA
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: PyYAML
|
| 3 |
+
Version: 6.0.2
|
| 4 |
+
Summary: YAML parser and emitter for Python
|
| 5 |
+
Home-page: https://pyyaml.org/
|
| 6 |
+
Download-URL: https://pypi.org/project/PyYAML/
|
| 7 |
+
Author: Kirill Simonov
|
| 8 |
+
Author-email: xi@resolvent.net
|
| 9 |
+
License: MIT
|
| 10 |
+
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
|
| 11 |
+
Project-URL: CI, https://github.com/yaml/pyyaml/actions
|
| 12 |
+
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
|
| 13 |
+
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
|
| 14 |
+
Project-URL: Source Code, https://github.com/yaml/pyyaml
|
| 15 |
+
Platform: Any
|
| 16 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 17 |
+
Classifier: Intended Audience :: Developers
|
| 18 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 19 |
+
Classifier: Operating System :: OS Independent
|
| 20 |
+
Classifier: Programming Language :: Cython
|
| 21 |
+
Classifier: Programming Language :: Python
|
| 22 |
+
Classifier: Programming Language :: Python :: 3
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 25 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 26 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 27 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 28 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 29 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 30 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 31 |
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
| 32 |
+
Classifier: Topic :: Text Processing :: Markup
|
| 33 |
+
Requires-Python: >=3.8
|
| 34 |
+
License-File: LICENSE
|
| 35 |
+
|
| 36 |
+
YAML is a data serialization format designed for human readability
|
| 37 |
+
and interaction with scripting languages. PyYAML is a YAML parser
|
| 38 |
+
and emitter for Python.
|
| 39 |
+
|
| 40 |
+
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
|
| 41 |
+
support, capable extension API, and sensible error messages. PyYAML
|
| 42 |
+
supports standard YAML tags and provides Python-specific tags that
|
| 43 |
+
allow to represent an arbitrary Python object.
|
| 44 |
+
|
| 45 |
+
PyYAML is applicable for a broad range of tasks from complex
|
| 46 |
+
configuration files to object serialization and persistence.
|
.venv/Lib/site-packages/PyYAML-6.0.2.dist-info/RECORD
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
PyYAML-6.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
PyYAML-6.0.2.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
| 3 |
+
PyYAML-6.0.2.dist-info/METADATA,sha256=9lwXqTOrXPts-jI2Lo5UwuaAYo0hiRA0BZqjch0WjAk,2106
|
| 4 |
+
PyYAML-6.0.2.dist-info/RECORD,,
|
| 5 |
+
PyYAML-6.0.2.dist-info/WHEEL,sha256=c7SWG1_hRvc9HXHEkmWlTu1Jr4WpzRucfzqTP-_8q0s,102
|
| 6 |
+
PyYAML-6.0.2.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
|
| 7 |
+
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
|
| 8 |
+
_yaml/__pycache__/__init__.cpython-312.pyc,,
|
| 9 |
+
yaml/__init__.py,sha256=N35S01HMesFTe0aRRMWkPj0Pa8IEbHpE9FK7cr5Bdtw,12311
|
| 10 |
+
yaml/__pycache__/__init__.cpython-312.pyc,,
|
| 11 |
+
yaml/__pycache__/composer.cpython-312.pyc,,
|
| 12 |
+
yaml/__pycache__/constructor.cpython-312.pyc,,
|
| 13 |
+
yaml/__pycache__/dumper.cpython-312.pyc,,
|
| 14 |
+
yaml/__pycache__/emitter.cpython-312.pyc,,
|
| 15 |
+
yaml/__pycache__/error.cpython-312.pyc,,
|
| 16 |
+
yaml/__pycache__/events.cpython-312.pyc,,
|
| 17 |
+
yaml/__pycache__/loader.cpython-312.pyc,,
|
| 18 |
+
yaml/__pycache__/nodes.cpython-312.pyc,,
|
| 19 |
+
yaml/__pycache__/reader.cpython-312.pyc,,
|
| 20 |
+
yaml/__pycache__/representer.cpython-312.pyc,,
|
| 21 |
+
yaml/__pycache__/resolver.cpython-312.pyc,,
|
| 22 |
+
yaml/_yaml.cp312-win_amd64.pyd,sha256=Bx7e_LEQx7cnd1_A9_nClp3X77g-_Lw1aoAAtYZbwWk,263680
|
| 23 |
+
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
|
| 24 |
+
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
|
| 25 |
+
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
|
| 26 |
+
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
|
| 27 |
+
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
|
| 28 |
+
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
|
| 29 |
+
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
|
| 30 |
+
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
|
| 31 |
+
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
|
| 32 |
+
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
|
| 33 |
+
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
|
| 34 |
+
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190
|
| 35 |
+
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004
|
| 36 |
+
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279
|
| 37 |
+
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
|
| 38 |
+
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
|
.venv/Lib/site-packages/PyYAML-6.0.2.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: bdist_wheel (0.44.0)
|
| 3 |
+
Root-Is-Purelib: false
|
| 4 |
+
Tag: cp312-cp312-win_amd64
|
| 5 |
+
|
.venv/Lib/site-packages/PyYAML-6.0.2.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_yaml
|
| 2 |
+
yaml
|
.venv/Lib/site-packages/certifi-2025.8.3.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/Lib/site-packages/certifi-2025.8.3.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
certifi
|
.venv/Lib/site-packages/certifi/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from certifi import contents, where
|
| 4 |
+
|
| 5 |
+
parser = argparse.ArgumentParser()
|
| 6 |
+
parser.add_argument("-c", "--contents", action="store_true")
|
| 7 |
+
args = parser.parse_args()
|
| 8 |
+
|
| 9 |
+
if args.contents:
|
| 10 |
+
print(contents())
|
| 11 |
+
else:
|
| 12 |
+
print(where())
|
.venv/Lib/site-packages/certifi/cacert.pem
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
.venv/Lib/site-packages/certifi/core.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
certifi.py
|
| 3 |
+
~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module returns the installation location of cacert.pem or its contents.
|
| 6 |
+
"""
|
| 7 |
+
import sys
|
| 8 |
+
import atexit
|
| 9 |
+
|
| 10 |
+
def exit_cacert_ctx() -> None:
|
| 11 |
+
_CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
if sys.version_info >= (3, 11):
|
| 15 |
+
|
| 16 |
+
from importlib.resources import as_file, files
|
| 17 |
+
|
| 18 |
+
_CACERT_CTX = None
|
| 19 |
+
_CACERT_PATH = None
|
| 20 |
+
|
| 21 |
+
def where() -> str:
|
| 22 |
+
# This is slightly terrible, but we want to delay extracting the file
|
| 23 |
+
# in cases where we're inside of a zipimport situation until someone
|
| 24 |
+
# actually calls where(), but we don't want to re-extract the file
|
| 25 |
+
# on every call of where(), so we'll do it once then store it in a
|
| 26 |
+
# global variable.
|
| 27 |
+
global _CACERT_CTX
|
| 28 |
+
global _CACERT_PATH
|
| 29 |
+
if _CACERT_PATH is None:
|
| 30 |
+
# This is slightly janky, the importlib.resources API wants you to
|
| 31 |
+
# manage the cleanup of this file, so it doesn't actually return a
|
| 32 |
+
# path, it returns a context manager that will give you the path
|
| 33 |
+
# when you enter it and will do any cleanup when you leave it. In
|
| 34 |
+
# the common case of not needing a temporary file, it will just
|
| 35 |
+
# return the file system location and the __exit__() is a no-op.
|
| 36 |
+
#
|
| 37 |
+
# We also have to hold onto the actual context manager, because
|
| 38 |
+
# it will do the cleanup whenever it gets garbage collected, so
|
| 39 |
+
# we will also store that at the global level as well.
|
| 40 |
+
_CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem"))
|
| 41 |
+
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
| 42 |
+
atexit.register(exit_cacert_ctx)
|
| 43 |
+
|
| 44 |
+
return _CACERT_PATH
|
| 45 |
+
|
| 46 |
+
def contents() -> str:
|
| 47 |
+
return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii")
|
| 48 |
+
|
| 49 |
+
else:
|
| 50 |
+
|
| 51 |
+
from importlib.resources import path as get_path, read_text
|
| 52 |
+
|
| 53 |
+
_CACERT_CTX = None
|
| 54 |
+
_CACERT_PATH = None
|
| 55 |
+
|
| 56 |
+
def where() -> str:
|
| 57 |
+
# This is slightly terrible, but we want to delay extracting the
|
| 58 |
+
# file in cases where we're inside of a zipimport situation until
|
| 59 |
+
# someone actually calls where(), but we don't want to re-extract
|
| 60 |
+
# the file on every call of where(), so we'll do it once then store
|
| 61 |
+
# it in a global variable.
|
| 62 |
+
global _CACERT_CTX
|
| 63 |
+
global _CACERT_PATH
|
| 64 |
+
if _CACERT_PATH is None:
|
| 65 |
+
# This is slightly janky, the importlib.resources API wants you
|
| 66 |
+
# to manage the cleanup of this file, so it doesn't actually
|
| 67 |
+
# return a path, it returns a context manager that will give
|
| 68 |
+
# you the path when you enter it and will do any cleanup when
|
| 69 |
+
# you leave it. In the common case of not needing a temporary
|
| 70 |
+
# file, it will just return the file system location and the
|
| 71 |
+
# __exit__() is a no-op.
|
| 72 |
+
#
|
| 73 |
+
# We also have to hold onto the actual context manager, because
|
| 74 |
+
# it will do the cleanup whenever it gets garbage collected, so
|
| 75 |
+
# we will also store that at the global level as well.
|
| 76 |
+
_CACERT_CTX = get_path("certifi", "cacert.pem")
|
| 77 |
+
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
| 78 |
+
atexit.register(exit_cacert_ctx)
|
| 79 |
+
|
| 80 |
+
return _CACERT_PATH
|
| 81 |
+
|
| 82 |
+
def contents() -> str:
|
| 83 |
+
return read_text("certifi", "cacert.pem", encoding="ascii")
|
.venv/Lib/site-packages/certifi/py.typed
ADDED
|
File without changes
|
.venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/METADATA
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: prompt_toolkit
|
| 3 |
+
Version: 3.0.51
|
| 4 |
+
Summary: Library for building powerful interactive command lines in Python
|
| 5 |
+
Author: Jonathan Slenders
|
| 6 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 7 |
+
Classifier: Intended Audience :: Developers
|
| 8 |
+
Classifier: License :: OSI Approved :: BSD License
|
| 9 |
+
Classifier: Operating System :: OS Independent
|
| 10 |
+
Classifier: Programming Language :: Python :: 3
|
| 11 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 12 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 13 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 14 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 15 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 16 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 17 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 18 |
+
Classifier: Programming Language :: Python
|
| 19 |
+
Classifier: Topic :: Software Development
|
| 20 |
+
Requires-Python: >=3.8
|
| 21 |
+
Description-Content-Type: text/x-rst
|
| 22 |
+
License-File: LICENSE
|
| 23 |
+
License-File: AUTHORS.rst
|
| 24 |
+
Requires-Dist: wcwidth
|
| 25 |
+
Dynamic: license-file
|
| 26 |
+
|
| 27 |
+
Python Prompt Toolkit
|
| 28 |
+
=====================
|
| 29 |
+
|
| 30 |
+
|AppVeyor| |PyPI| |RTD| |License| |Codecov|
|
| 31 |
+
|
| 32 |
+
.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png
|
| 33 |
+
|
| 34 |
+
``prompt_toolkit`` *is a library for building powerful interactive command line applications in Python.*
|
| 35 |
+
|
| 36 |
+
Read the `documentation on readthedocs
|
| 37 |
+
<http://python-prompt-toolkit.readthedocs.io/en/stable/>`_.
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
Gallery
|
| 41 |
+
*******
|
| 42 |
+
|
| 43 |
+
`ptpython <http://github.com/prompt-toolkit/ptpython/>`_ is an interactive
|
| 44 |
+
Python Shell, build on top of ``prompt_toolkit``.
|
| 45 |
+
|
| 46 |
+
.. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png
|
| 47 |
+
|
| 48 |
+
`More examples <https://python-prompt-toolkit.readthedocs.io/en/stable/pages/gallery.html>`_
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
prompt_toolkit features
|
| 52 |
+
***********************
|
| 53 |
+
|
| 54 |
+
``prompt_toolkit`` could be a replacement for `GNU readline
|
| 55 |
+
<https://tiswww.case.edu/php/chet/readline/rltop.html>`_, but it can be much
|
| 56 |
+
more than that.
|
| 57 |
+
|
| 58 |
+
Some features:
|
| 59 |
+
|
| 60 |
+
- **Pure Python**.
|
| 61 |
+
- Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.)
|
| 62 |
+
- Multi-line input editing.
|
| 63 |
+
- Advanced code completion.
|
| 64 |
+
- Both Emacs and Vi key bindings. (Similar to readline.)
|
| 65 |
+
- Even some advanced Vi functionality, like named registers and digraphs.
|
| 66 |
+
- Reverse and forward incremental search.
|
| 67 |
+
- Works well with Unicode double width characters. (Chinese input.)
|
| 68 |
+
- Selecting text for copy/paste. (Both Emacs and Vi style.)
|
| 69 |
+
- Support for `bracketed paste <https://cirw.in/blog/bracketed-paste>`_.
|
| 70 |
+
- Mouse support for cursor positioning and scrolling.
|
| 71 |
+
- Auto suggestions. (Like `fish shell <http://fishshell.com/>`_.)
|
| 72 |
+
- Multiple input buffers.
|
| 73 |
+
- No global state.
|
| 74 |
+
- Lightweight, the only dependencies are Pygments and wcwidth.
|
| 75 |
+
- Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems.
|
| 76 |
+
- And much more...
|
| 77 |
+
|
| 78 |
+
Feel free to create tickets for bugs and feature requests, and create pull
|
| 79 |
+
requests if you have nice patches that you would like to share with others.
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
Installation
|
| 83 |
+
************
|
| 84 |
+
|
| 85 |
+
::
|
| 86 |
+
|
| 87 |
+
pip install prompt_toolkit
|
| 88 |
+
|
| 89 |
+
For Conda, do:
|
| 90 |
+
|
| 91 |
+
::
|
| 92 |
+
|
| 93 |
+
conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
About Windows support
|
| 97 |
+
*********************
|
| 98 |
+
|
| 99 |
+
``prompt_toolkit`` is cross platform, and everything that you build on top
|
| 100 |
+
should run fine on both Unix and Windows systems. Windows support is best on
|
| 101 |
+
recent Windows 10 builds, for which the command line window supports vt100
|
| 102 |
+
escape sequences. (If not supported, we fall back to using Win32 APIs for color
|
| 103 |
+
and cursor movements).
|
| 104 |
+
|
| 105 |
+
It's worth noting that the implementation is a "best effort of what is
|
| 106 |
+
possible". Both Unix and Windows terminals have their limitations. But in
|
| 107 |
+
general, the Unix experience will still be a little better.
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
Getting started
|
| 111 |
+
***************
|
| 112 |
+
|
| 113 |
+
The most simple example of the library would look like this:
|
| 114 |
+
|
| 115 |
+
.. code:: python
|
| 116 |
+
|
| 117 |
+
from prompt_toolkit import prompt
|
| 118 |
+
|
| 119 |
+
if __name__ == '__main__':
|
| 120 |
+
answer = prompt('Give me some input: ')
|
| 121 |
+
print('You said: %s' % answer)
|
| 122 |
+
|
| 123 |
+
For more complex examples, have a look in the ``examples`` directory. All
|
| 124 |
+
examples are chosen to demonstrate only one thing. Also, don't be afraid to
|
| 125 |
+
look at the source code. The implementation of the ``prompt`` function could be
|
| 126 |
+
a good start.
|
| 127 |
+
|
| 128 |
+
Philosophy
|
| 129 |
+
**********
|
| 130 |
+
|
| 131 |
+
The source code of ``prompt_toolkit`` should be **readable**, **concise** and
|
| 132 |
+
**efficient**. We prefer short functions focusing each on one task and for which
|
| 133 |
+
the input and output types are clearly specified. We mostly prefer composition
|
| 134 |
+
over inheritance, because inheritance can result in too much functionality in
|
| 135 |
+
the same object. We prefer immutable objects where possible (objects don't
|
| 136 |
+
change after initialization). Reusability is important. We absolutely refrain
|
| 137 |
+
from having a changing global state, it should be possible to have multiple
|
| 138 |
+
independent instances of the same code in the same process. The architecture
|
| 139 |
+
should be layered: the lower levels operate on primitive operations and data
|
| 140 |
+
structures giving -- when correctly combined -- all the possible flexibility;
|
| 141 |
+
while at the higher level, there should be a simpler API, ready-to-use and
|
| 142 |
+
sufficient for most use cases. Thinking about algorithms and efficiency is
|
| 143 |
+
important, but avoid premature optimization.
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
`Projects using prompt_toolkit <PROJECTS.rst>`_
|
| 147 |
+
***********************************************
|
| 148 |
+
|
| 149 |
+
Special thanks to
|
| 150 |
+
*****************
|
| 151 |
+
|
| 152 |
+
- `Pygments <http://pygments.org/>`_: Syntax highlighter.
|
| 153 |
+
- `wcwidth <https://github.com/jquast/wcwidth>`_: Determine columns needed for a wide characters.
|
| 154 |
+
|
| 155 |
+
.. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg
|
| 156 |
+
:target: https://pypi.python.org/pypi/prompt-toolkit/
|
| 157 |
+
:alt: Latest Version
|
| 158 |
+
|
| 159 |
+
.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true
|
| 160 |
+
:target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/
|
| 161 |
+
|
| 162 |
+
.. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/
|
| 163 |
+
:target: https://python-prompt-toolkit.readthedocs.io/en/master/
|
| 164 |
+
|
| 165 |
+
.. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg
|
| 166 |
+
:target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE
|
| 167 |
+
|
| 168 |
+
.. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat
|
| 169 |
+
:target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/
|
| 170 |
+
|
.venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/RECORD
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
prompt_toolkit-3.0.51.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
prompt_toolkit-3.0.51.dist-info/METADATA,sha256=lewVXm0ZlAORaErFwfRKkNDPtq-1hlXd_c58vyT3fvY,6383
|
| 3 |
+
prompt_toolkit-3.0.51.dist-info/RECORD,,
|
| 4 |
+
prompt_toolkit-3.0.51.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
| 5 |
+
prompt_toolkit-3.0.51.dist-info/licenses/AUTHORS.rst,sha256=09xixryENmWElauJrqN1Eef6k5HSgmVyOcnPuA29QuU,148
|
| 6 |
+
prompt_toolkit-3.0.51.dist-info/licenses/LICENSE,sha256=MDV02b3YXHV9YCUBeUK_F7ru3yd49ivX9CXQfYgPTEo,1493
|
| 7 |
+
prompt_toolkit-3.0.51.dist-info/top_level.txt,sha256=5rJXrEGx6st4KkmhOPR6l0ITDbV53x_Xy6MurOukXfA,15
|
| 8 |
+
prompt_toolkit/__init__.py,sha256=bZ8Mrn9XS4WaevxVwEwKLKZ9EpPI5IPA1x6rRR7tkVI,1354
|
| 9 |
+
prompt_toolkit/__pycache__/__init__.cpython-312.pyc,,
|
| 10 |
+
prompt_toolkit/__pycache__/cache.cpython-312.pyc,,
|
| 11 |
+
prompt_toolkit/__pycache__/cursor_shapes.cpython-312.pyc,,
|
| 12 |
+
prompt_toolkit/__pycache__/data_structures.cpython-312.pyc,,
|
| 13 |
+
prompt_toolkit/__pycache__/document.cpython-312.pyc,,
|
| 14 |
+
prompt_toolkit/__pycache__/enums.cpython-312.pyc,,
|
| 15 |
+
prompt_toolkit/__pycache__/history.cpython-312.pyc,,
|
| 16 |
+
prompt_toolkit/__pycache__/log.cpython-312.pyc,,
|
| 17 |
+
prompt_toolkit/__pycache__/mouse_events.cpython-312.pyc,,
|
| 18 |
+
prompt_toolkit/__pycache__/patch_stdout.cpython-312.pyc,,
|
| 19 |
+
prompt_toolkit/__pycache__/renderer.cpython-312.pyc,,
|
| 20 |
+
prompt_toolkit/__pycache__/search.cpython-312.pyc,,
|
| 21 |
+
prompt_toolkit/__pycache__/selection.cpython-312.pyc,,
|
| 22 |
+
prompt_toolkit/__pycache__/token.cpython-312.pyc,,
|
| 23 |
+
prompt_toolkit/__pycache__/utils.cpython-312.pyc,,
|
| 24 |
+
prompt_toolkit/__pycache__/validation.cpython-312.pyc,,
|
| 25 |
+
prompt_toolkit/__pycache__/win32_types.cpython-312.pyc,,
|
| 26 |
+
prompt_toolkit/application/__init__.py,sha256=rat9iPhYTmo7nd2BU8xZSU_-AfJpjnnBmxe9y3TQivM,657
|
| 27 |
+
prompt_toolkit/application/__pycache__/__init__.cpython-312.pyc,,
|
| 28 |
+
prompt_toolkit/application/__pycache__/current.cpython-312.pyc,,
|
| 29 |
+
prompt_toolkit/application/__pycache__/run_in_terminal.cpython-312.pyc,,
|
| 30 |
+
prompt_toolkit/application/application.py,sha256=oziKsiHNmGJQhMj1ODQIkJSeOI3ejRWWQ9XduVHYOAE,63046
|
| 31 |
+
prompt_toolkit/application/current.py,sha256=uCJz0tx0fnj39xMrV3R_-iJEAA4-s8ik9dC5x21NQgk,6207
|
| 32 |
+
prompt_toolkit/application/dummy.py,sha256=BCfThUgz5Eb5fWJSKBVeJaA5kwksw8jJQtN6g61xMXM,1619
|
| 33 |
+
prompt_toolkit/application/run_in_terminal.py,sha256=qgTfpG3qGP4wRy9l_7zU8P7s59CARIvagulTyPn_oEg,3767
|
| 34 |
+
prompt_toolkit/auto_suggest.py,sha256=qSiaxlaKjLyNEJ8bJN0641gqsIzZ3LB2cOyq88xBQb4,5798
|
| 35 |
+
prompt_toolkit/buffer.py,sha256=VkAbKTJV7441aO4Ei-ozqc8IBlNPEnLGAt5t42tB6jg,74513
|
| 36 |
+
prompt_toolkit/cache.py,sha256=Lo3ewsEIgn-LQBYNni79w74u5LSvvuVYF0e6giEArQg,3823
|
| 37 |
+
prompt_toolkit/clipboard/__init__.py,sha256=yK0LonIfEZRyoXqcgLdh8kjOhechjO-Ej2-C1g3VegQ,439
|
| 38 |
+
prompt_toolkit/clipboard/__pycache__/__init__.cpython-312.pyc,,
|
| 39 |
+
prompt_toolkit/clipboard/__pycache__/base.cpython-312.pyc,,
|
| 40 |
+
prompt_toolkit/clipboard/__pycache__/in_memory.cpython-312.pyc,,
|
| 41 |
+
prompt_toolkit/clipboard/__pycache__/pyperclip.cpython-312.pyc,,
|
| 42 |
+
prompt_toolkit/clipboard/base.py,sha256=rucEv1kKfvZUj6bwGRz04uSSTZie7rvnKUnyVXb2vv4,2515
|
| 43 |
+
prompt_toolkit/clipboard/in_memory.py,sha256=U_iY6UUevkKMfTvir_XMsD0qwuLVKuoTeRdjkZW-A6I,1060
|
| 44 |
+
prompt_toolkit/clipboard/pyperclip.py,sha256=H9HOlyGW0XItvx_Ji64zBQkiQPfLb6DFAw5J5irzK28,1160
|
| 45 |
+
prompt_toolkit/completion/__init__.py,sha256=8Hm2yJ1nqBkaC-R9ugELgjhU32U308V89F6bJG0QDYo,992
|
| 46 |
+
prompt_toolkit/completion/__pycache__/__init__.cpython-312.pyc,,
|
| 47 |
+
prompt_toolkit/completion/__pycache__/deduplicate.cpython-312.pyc,,
|
| 48 |
+
prompt_toolkit/completion/__pycache__/filesystem.cpython-312.pyc,,
|
| 49 |
+
prompt_toolkit/completion/__pycache__/fuzzy_completer.cpython-312.pyc,,
|
| 50 |
+
prompt_toolkit/completion/__pycache__/nested.cpython-312.pyc,,
|
| 51 |
+
prompt_toolkit/completion/__pycache__/word_completer.cpython-312.pyc,,
|
| 52 |
+
prompt_toolkit/completion/base.py,sha256=T7212aScNaGMaSrDIwsJIXeXLIM_eVCIcScNcDPZYwI,16103
|
| 53 |
+
prompt_toolkit/completion/deduplicate.py,sha256=QibqYI23GPjsbyxaxiNoqAbKawzHmfYOlxeW2HPFbTE,1436
|
| 54 |
+
prompt_toolkit/completion/filesystem.py,sha256=Z_RR72e14bVavdWnbxECw23qCt_TWTY9R6DpVqW7vxE,3949
|
| 55 |
+
prompt_toolkit/completion/fuzzy_completer.py,sha256=RnREvA7y6nC7LKGqZUEvtuSm8eXVQYheJTsnhUvRbmM,7738
|
| 56 |
+
prompt_toolkit/completion/nested.py,sha256=ig2Qy4dLyDvOT6O8Qb-iRZLWzlT11S5tVQ3GFZmpm-U,3844
|
| 57 |
+
prompt_toolkit/completion/word_completer.py,sha256=VF1S7YCxYqI3pKmVXJaD82eMW1ZMq8_zAAIS1XKGU5M,3435
|
| 58 |
+
prompt_toolkit/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 59 |
+
prompt_toolkit/contrib/__pycache__/__init__.cpython-312.pyc,,
|
| 60 |
+
prompt_toolkit/contrib/completers/__init__.py,sha256=qJB_xNFGbhfiDv_zUaox9mkQEGqBYqP_jfByQDb93hA,103
|
| 61 |
+
prompt_toolkit/contrib/completers/__pycache__/__init__.cpython-312.pyc,,
|
| 62 |
+
prompt_toolkit/contrib/completers/__pycache__/system.cpython-312.pyc,,
|
| 63 |
+
prompt_toolkit/contrib/completers/system.py,sha256=0Hc2dziheEx2qNog4YOl-4Tu8Fg5Dx2xjNURTx09BDg,2057
|
| 64 |
+
prompt_toolkit/contrib/regular_languages/__init__.py,sha256=cgMQkakD4FbvLUozDGucRRFOk8yScfcKfqOMpCtvAPo,3279
|
| 65 |
+
prompt_toolkit/contrib/regular_languages/__pycache__/__init__.cpython-312.pyc,,
|
| 66 |
+
prompt_toolkit/contrib/regular_languages/__pycache__/completion.cpython-312.pyc,,
|
| 67 |
+
prompt_toolkit/contrib/regular_languages/__pycache__/lexer.cpython-312.pyc,,
|
| 68 |
+
prompt_toolkit/contrib/regular_languages/__pycache__/regex_parser.cpython-312.pyc,,
|
| 69 |
+
prompt_toolkit/contrib/regular_languages/__pycache__/validation.cpython-312.pyc,,
|
| 70 |
+
prompt_toolkit/contrib/regular_languages/compiler.py,sha256=3tnUJCE2jCcVI63vcpI0kG4KfuqIatSQRb8-F5UCgsI,21948
|
| 71 |
+
prompt_toolkit/contrib/regular_languages/completion.py,sha256=jESF35RaYWj_rnT-OZc_zC9QZXYvPao4JZ8wx7yS3KM,3468
|
| 72 |
+
prompt_toolkit/contrib/regular_languages/lexer.py,sha256=DBgyek9LkfJv6hz24eOaVM--w9Qaw4zIMWusMvGHBts,3415
|
| 73 |
+
prompt_toolkit/contrib/regular_languages/regex_parser.py,sha256=zWGJfQSjomvdj2rD7MPpn2pWOUR7VMv4su5iAV0jzM4,7732
|
| 74 |
+
prompt_toolkit/contrib/regular_languages/validation.py,sha256=4k5wxgUFc_KTOW5PmmZOrWb-Z-HjX8fjjKqul-oR8uc,2059
|
| 75 |
+
prompt_toolkit/contrib/ssh/__init__.py,sha256=UcRG2wc28EEKtFEudoIXz_DFzWKKQjAVSv6cf-ufPiM,180
|
| 76 |
+
prompt_toolkit/contrib/ssh/__pycache__/__init__.cpython-312.pyc,,
|
| 77 |
+
prompt_toolkit/contrib/ssh/__pycache__/server.cpython-312.pyc,,
|
| 78 |
+
prompt_toolkit/contrib/ssh/server.py,sha256=81McNn6r0Cbu9SPceH7fa5QirAnteHmNh1Gk4dFpgvI,6130
|
| 79 |
+
prompt_toolkit/contrib/telnet/__init__.py,sha256=NyUfsmJdafGiUxD9gzYQNlVdHu_ILDH7F57VJw8efUM,104
|
| 80 |
+
prompt_toolkit/contrib/telnet/__pycache__/__init__.cpython-312.pyc,,
|
| 81 |
+
prompt_toolkit/contrib/telnet/__pycache__/log.cpython-312.pyc,,
|
| 82 |
+
prompt_toolkit/contrib/telnet/__pycache__/server.cpython-312.pyc,,
|
| 83 |
+
prompt_toolkit/contrib/telnet/log.py,sha256=LcFRDyRxoRKSZsVRVpBOrEgsEt_LQLyUHKtgVZklopI,167
|
| 84 |
+
prompt_toolkit/contrib/telnet/protocol.py,sha256=2i-JYfaAse-uFWtNdVEoP_Q-OMbkl3YbUfv_wvaaS3k,5584
|
| 85 |
+
prompt_toolkit/contrib/telnet/server.py,sha256=dKHpEhXkIef_iuvZCbumwotapx6i03t6Gk01zkAoNIU,13477
|
| 86 |
+
prompt_toolkit/cursor_shapes.py,sha256=k5g5yJONGl1ITgy29KX9yzspJvIJ6Jbbwd7WkYC9Z-4,3721
|
| 87 |
+
prompt_toolkit/data_structures.py,sha256=w0BZy6Fpx4se-kAI9Kj8Q7lAKLln8U_Em_ncpqnC1xY,212
|
| 88 |
+
prompt_toolkit/document.py,sha256=vzg3U2Zzd95l1pkZnIFjAA88ygFYSdybhRKwJcxvvr0,40579
|
| 89 |
+
prompt_toolkit/enums.py,sha256=F3q9JmH9vhpMLA2OKKN7RrNQu_YDlNWoPU-0qsTUuAs,358
|
| 90 |
+
prompt_toolkit/eventloop/__init__.py,sha256=pxSkV_zybeoj6Ff3lgNHhbD5ENmBW9mk_XkiyeRL_OY,730
|
| 91 |
+
prompt_toolkit/eventloop/__pycache__/inputhook.cpython-312.pyc,,
|
| 92 |
+
prompt_toolkit/eventloop/__pycache__/utils.cpython-312.pyc,,
|
| 93 |
+
prompt_toolkit/eventloop/async_generator.py,sha256=nozLJR4z2MJKV7Qi0hsknA2mb1Jcp7XJx-AdUEDhDhw,3933
|
| 94 |
+
prompt_toolkit/eventloop/inputhook.py,sha256=LDElZtmg-kLQiItMS8CFPxtLzxV8QzohWHsWUvw3h00,6130
|
| 95 |
+
prompt_toolkit/eventloop/utils.py,sha256=VhYmsDZmRwVXnEPBF_C2LpiW-ranPn6EIXWIuMa6XaU,3200
|
| 96 |
+
prompt_toolkit/eventloop/win32.py,sha256=wrLJVOtOw_tqVOeK6ttNF47Sk2oX342dLN1pxKBLCL4,2286
|
| 97 |
+
prompt_toolkit/filters/__init__.py,sha256=2YSVwf4EnLf1VOXYmb8Dr0WoA93XGGO0iCUIr14KGXQ,1990
|
| 98 |
+
prompt_toolkit/filters/__pycache__/__init__.cpython-312.pyc,,
|
| 99 |
+
prompt_toolkit/filters/__pycache__/app.cpython-312.pyc,,
|
| 100 |
+
prompt_toolkit/filters/__pycache__/base.cpython-312.pyc,,
|
| 101 |
+
prompt_toolkit/filters/__pycache__/cli.cpython-312.pyc,,
|
| 102 |
+
prompt_toolkit/filters/__pycache__/utils.cpython-312.pyc,,
|
| 103 |
+
prompt_toolkit/filters/app.py,sha256=QVJMjR6Zf-BxlmGaUd-WbtEaGlxMKYMFVwj3qcwo7ns,10408
|
| 104 |
+
prompt_toolkit/filters/base.py,sha256=asrgKE-gzYlRLrS4w3kMFimvZtXQ9pk252Vs5ShVeeM,6855
|
| 105 |
+
prompt_toolkit/filters/cli.py,sha256=QGV7JT7-BUXpPXNzBLUcNH3GI69ugFZCDV1nylOjq78,1867
|
| 106 |
+
prompt_toolkit/filters/utils.py,sha256=4nOjHPEd451Pj3qpfg40fq3XSnt1kmq3WoAbhu2NV-8,859
|
| 107 |
+
prompt_toolkit/formatted_text/__init__.py,sha256=aQtNhxOhIa_HmvlNOQ2RGGpplg-KX3sYFJWiXgNfQxY,1509
|
| 108 |
+
prompt_toolkit/formatted_text/__pycache__/__init__.cpython-312.pyc,,
|
| 109 |
+
prompt_toolkit/formatted_text/__pycache__/base.cpython-312.pyc,,
|
| 110 |
+
prompt_toolkit/formatted_text/__pycache__/html.cpython-312.pyc,,
|
| 111 |
+
prompt_toolkit/formatted_text/__pycache__/pygments.cpython-312.pyc,,
|
| 112 |
+
prompt_toolkit/formatted_text/__pycache__/utils.cpython-312.pyc,,
|
| 113 |
+
prompt_toolkit/formatted_text/ansi.py,sha256=5uERUQxVrXAQfbTE5R9oWTIqLnDfNmsNpWPtfH5nLSo,9679
|
| 114 |
+
prompt_toolkit/formatted_text/base.py,sha256=X3y5QIPH2IS9LesYzXneELtT4zGpik8gd-UQVh6I2bE,5162
|
| 115 |
+
prompt_toolkit/formatted_text/html.py,sha256=-88VwuuCLRNkzEgK8FJKOHT9NDh939BxH8vGivvILdU,4374
|
| 116 |
+
prompt_toolkit/formatted_text/pygments.py,sha256=sK-eFFzOnD2sgadVLgNkW-xOuTw_uIf8_z06DZ4CA8g,780
|
| 117 |
+
prompt_toolkit/formatted_text/utils.py,sha256=r6tPtwo6dqvqf9gqZ7ARyvtNUjDDq6QZqrTWg6EMFuQ,3044
|
| 118 |
+
prompt_toolkit/history.py,sha256=S9W9SgL83QftMQANdjdjBMm-yGmeM51_qCRRC1H4Mr8,9441
|
| 119 |
+
prompt_toolkit/input/__init__.py,sha256=7g6kwNanG4Ml12FFdj9E1ivChpXWcfRUMUJzmTQMS7U,273
|
| 120 |
+
prompt_toolkit/input/__pycache__/__init__.cpython-312.pyc,,
|
| 121 |
+
prompt_toolkit/input/__pycache__/ansi_escape_sequences.cpython-312.pyc,,
|
| 122 |
+
prompt_toolkit/input/__pycache__/base.cpython-312.pyc,,
|
| 123 |
+
prompt_toolkit/input/__pycache__/defaults.cpython-312.pyc,,
|
| 124 |
+
prompt_toolkit/input/__pycache__/posix_pipe.cpython-312.pyc,,
|
| 125 |
+
prompt_toolkit/input/__pycache__/posix_utils.cpython-312.pyc,,
|
| 126 |
+
prompt_toolkit/input/__pycache__/typeahead.cpython-312.pyc,,
|
| 127 |
+
prompt_toolkit/input/__pycache__/vt100.cpython-312.pyc,,
|
| 128 |
+
prompt_toolkit/input/__pycache__/win32.cpython-312.pyc,,
|
| 129 |
+
prompt_toolkit/input/__pycache__/win32_pipe.cpython-312.pyc,,
|
| 130 |
+
prompt_toolkit/input/ansi_escape_sequences.py,sha256=h7714SZgs2z80PZRVxsCfHHJjtEUmWlToVCBtFFvfR4,13663
|
| 131 |
+
prompt_toolkit/input/base.py,sha256=pItwaKXtVZwemrKpoltmRskwoeXnSaBUZ_6iFZBdQf8,4036
|
| 132 |
+
prompt_toolkit/input/defaults.py,sha256=a-vczSh7kngFArLhFsJ2CXNdkx5WQlzilxHLdzGDkFw,2500
|
| 133 |
+
prompt_toolkit/input/posix_pipe.py,sha256=B_JS2-FB6Sk0da9gSH0NnhcUCkp3bw0m1-ogMOHmmcE,3158
|
| 134 |
+
prompt_toolkit/input/posix_utils.py,sha256=ySaEGnt_IwG5nzxcpILgEXC60mbrIAbC3ZZ6kuE9zCw,3973
|
| 135 |
+
prompt_toolkit/input/typeahead.py,sha256=mAaf5_XKTLpao1kw9ORIrhGGEz9gvu4oc-iZKKMQz3k,2545
|
| 136 |
+
prompt_toolkit/input/vt100.py,sha256=soxxSLU7fwp6yn77j5gCYUZroEp7KBKm4a3Zn4vRAsk,10514
|
| 137 |
+
prompt_toolkit/input/vt100_parser.py,sha256=qDrNbsnPukZblfyjgfjCvzMv8xQKRz0M84UvUWq7P44,8407
|
| 138 |
+
prompt_toolkit/input/win32.py,sha256=9UKo8f_W4AU8P4Luc7G3vWBWSCbpUIQDE0xlKp76mH8,30769
|
| 139 |
+
prompt_toolkit/input/win32_pipe.py,sha256=OvjKHN5xfEoGHLygWwayyeB0RolHL6YHLNeOMK-54LU,4700
|
| 140 |
+
prompt_toolkit/key_binding/__init__.py,sha256=IZWqJLBjQaQMfo0SJTjqJKQH0TZcSNa2Cdln-M4z8JI,447
|
| 141 |
+
prompt_toolkit/key_binding/__pycache__/__init__.cpython-312.pyc,,
|
| 142 |
+
prompt_toolkit/key_binding/__pycache__/defaults.cpython-312.pyc,,
|
| 143 |
+
prompt_toolkit/key_binding/__pycache__/emacs_state.cpython-312.pyc,,
|
| 144 |
+
prompt_toolkit/key_binding/__pycache__/key_bindings.cpython-312.pyc,,
|
| 145 |
+
prompt_toolkit/key_binding/__pycache__/key_processor.cpython-312.pyc,,
|
| 146 |
+
prompt_toolkit/key_binding/__pycache__/vi_state.cpython-312.pyc,,
|
| 147 |
+
prompt_toolkit/key_binding/bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 148 |
+
prompt_toolkit/key_binding/bindings/__pycache__/__init__.cpython-312.pyc,,
|
| 149 |
+
prompt_toolkit/key_binding/bindings/__pycache__/auto_suggest.cpython-312.pyc,,
|
| 150 |
+
prompt_toolkit/key_binding/bindings/__pycache__/basic.cpython-312.pyc,,
|
| 151 |
+
prompt_toolkit/key_binding/bindings/__pycache__/cpr.cpython-312.pyc,,
|
| 152 |
+
prompt_toolkit/key_binding/bindings/__pycache__/emacs.cpython-312.pyc,,
|
| 153 |
+
prompt_toolkit/key_binding/bindings/__pycache__/focus.cpython-312.pyc,,
|
| 154 |
+
prompt_toolkit/key_binding/bindings/__pycache__/mouse.cpython-312.pyc,,
|
| 155 |
+
prompt_toolkit/key_binding/bindings/__pycache__/named_commands.cpython-312.pyc,,
|
| 156 |
+
prompt_toolkit/key_binding/bindings/__pycache__/open_in_editor.cpython-312.pyc,,
|
| 157 |
+
prompt_toolkit/key_binding/bindings/__pycache__/page_navigation.cpython-312.pyc,,
|
| 158 |
+
prompt_toolkit/key_binding/bindings/__pycache__/search.cpython-312.pyc,,
|
| 159 |
+
prompt_toolkit/key_binding/bindings/__pycache__/vi.cpython-312.pyc,,
|
| 160 |
+
prompt_toolkit/key_binding/bindings/auto_suggest.py,sha256=4PrJVgIK_Nt2o3RtVtuRm2aFPGrackhuMCBVNtjPj7M,1855
|
| 161 |
+
prompt_toolkit/key_binding/bindings/basic.py,sha256=Fp9mj-RYZlGmAU9UV9wIIEnlxELN7NJ0qakMVH7MuRU,7229
|
| 162 |
+
prompt_toolkit/key_binding/bindings/completion.py,sha256=6nR3WfGe7FDsjq1xTsDazeajkV9KBLpCYQi3klujdLU,6903
|
| 163 |
+
prompt_toolkit/key_binding/bindings/cpr.py,sha256=181XQNZ0-sgL-vV2B67aRitTFHadogvMUh6LWVMUTV0,786
|
| 164 |
+
prompt_toolkit/key_binding/bindings/emacs.py,sha256=trIZUu8e5kJGSaq6Ndb-Exz4NdHV9SjUsfsw_UM8c6o,19634
|
| 165 |
+
prompt_toolkit/key_binding/bindings/focus.py,sha256=LIP4InccUUvD7I4NZrqtY9WjVfO_wJLyrVcoxAw92uU,507
|
| 166 |
+
prompt_toolkit/key_binding/bindings/mouse.py,sha256=6JPr0BqzFfLEVb7Ek_WO0CejUcwq0jIrrNwvSGkHeus,18586
|
| 167 |
+
prompt_toolkit/key_binding/bindings/named_commands.py,sha256=jdkqQ-ltNYC2BgIW1QdG7Qx4mWIod2Ps6C2TpL6NJ-Y,18407
|
| 168 |
+
prompt_toolkit/key_binding/bindings/open_in_editor.py,sha256=bgVmeDmVtHsgzJnc59b-dOZ-nO6WydBYI_7aOWMpe5c,1356
|
| 169 |
+
prompt_toolkit/key_binding/bindings/page_navigation.py,sha256=RPLUEZuZvekErPazex7pK0c6LxeV9LshewBHp012iMI,2392
|
| 170 |
+
prompt_toolkit/key_binding/bindings/scroll.py,sha256=hQeQ0m2AStUKjVNDXfa9DTMw3WS5qzW1n3gU0fkfWFk,5613
|
| 171 |
+
prompt_toolkit/key_binding/bindings/search.py,sha256=rU6VYra1IDzN6mG4mrbGivrZ-hjy_kZcjsKqmdVJKAE,2632
|
| 172 |
+
prompt_toolkit/key_binding/bindings/vi.py,sha256=TSglqzPZU9VMernOvn09GVxObFXpXuyCSiH9i1MpIIo,75602
|
| 173 |
+
prompt_toolkit/key_binding/defaults.py,sha256=JZJTshyBV39cWH2AT7xDP9AXOiyXQpjaI-ckePTi7os,1975
|
| 174 |
+
prompt_toolkit/key_binding/digraphs.py,sha256=rZvh9AdY5Te6bSlIHRQNskJYVIONYahYuu-w9Pti5yM,32785
|
| 175 |
+
prompt_toolkit/key_binding/emacs_state.py,sha256=ZJBWcLTzgtRkUW9UiDuI-SRrnlLsxu3IrTOK0_UQt5Y,884
|
| 176 |
+
prompt_toolkit/key_binding/key_bindings.py,sha256=0QDWvFuct2vAIHK-hrQmEipmiRMBQbWP4JB1PsXVZKY,20927
|
| 177 |
+
prompt_toolkit/key_binding/key_processor.py,sha256=0WLK4dcU8klL2Xna_RKxOpsW7t8ld67Y9Xmto3U-n0E,17555
|
| 178 |
+
prompt_toolkit/key_binding/vi_state.py,sha256=p-JuzwYtWl25tMmfRZ6e7UQWDi7RlXnAggir7ZSi07I,3341
|
| 179 |
+
prompt_toolkit/keys.py,sha256=nDkIqJbm_dRsVjArp7oItGKIFAAnSxcSniSwc1O-BYA,4916
|
| 180 |
+
prompt_toolkit/layout/__init__.py,sha256=gNbniLmlvkWwPE6Kg2ykyZJRTOKsWnHbwUjyO-VFDP8,3603
|
| 181 |
+
prompt_toolkit/layout/__pycache__/__init__.cpython-312.pyc,,
|
| 182 |
+
prompt_toolkit/layout/__pycache__/containers.cpython-312.pyc,,
|
| 183 |
+
prompt_toolkit/layout/__pycache__/controls.cpython-312.pyc,,
|
| 184 |
+
prompt_toolkit/layout/__pycache__/dimension.cpython-312.pyc,,
|
| 185 |
+
prompt_toolkit/layout/__pycache__/dummy.cpython-312.pyc,,
|
| 186 |
+
prompt_toolkit/layout/__pycache__/layout.cpython-312.pyc,,
|
| 187 |
+
prompt_toolkit/layout/__pycache__/margins.cpython-312.pyc,,
|
| 188 |
+
prompt_toolkit/layout/__pycache__/menus.cpython-312.pyc,,
|
| 189 |
+
prompt_toolkit/layout/__pycache__/mouse_handlers.cpython-312.pyc,,
|
| 190 |
+
prompt_toolkit/layout/__pycache__/processors.cpython-312.pyc,,
|
| 191 |
+
prompt_toolkit/layout/__pycache__/scrollable_pane.cpython-312.pyc,,
|
| 192 |
+
prompt_toolkit/layout/__pycache__/utils.cpython-312.pyc,,
|
| 193 |
+
prompt_toolkit/layout/containers.py,sha256=ZdpJEFJT11_CDWJEV6fDv5w0NmjnG8kpB4s_JMay-_s,99206
|
| 194 |
+
prompt_toolkit/layout/controls.py,sha256=9h6425oGeBwLO85MBNdHSh6XsrtEay5JwuIX-fuzsVI,35993
|
| 195 |
+
prompt_toolkit/layout/dimension.py,sha256=e1Zbptz3dRcG7khlC3I3DbIhXnFfpLxYOOBoELAiZ20,7052
|
| 196 |
+
prompt_toolkit/layout/dummy.py,sha256=8zB3MwDDd4RpI880WUKhv719tTzy5bXS9gm9zdkBZ10,1047
|
| 197 |
+
prompt_toolkit/layout/layout.py,sha256=VXqWAoL3EviGn4CxtOrFJekMALvl9xff1bTSnE-gXF8,13960
|
| 198 |
+
prompt_toolkit/layout/margins.py,sha256=bt-IvD03uQvmLVYvGZLqPLluR6kUlBRBAGJwCc8F7II,10375
|
| 199 |
+
prompt_toolkit/layout/menus.py,sha256=B4H2oCPF48gLy9cB0vkdGIoH_8gGyj95TDHtfxXRVSw,27195
|
| 200 |
+
prompt_toolkit/layout/mouse_handlers.py,sha256=lwbGSdpn6_pcK7HQWJ6IvHsxTf1_ahBew4pkmtU6zUM,1589
|
| 201 |
+
prompt_toolkit/layout/processors.py,sha256=0VE4UIGRzyXvDO4XqCB7LXNG9WkSxLz7FW7toOvHDSE,34296
|
| 202 |
+
prompt_toolkit/layout/screen.py,sha256=2PWdPDkQxtJrMSv9oqdZrWa7ChCnC7J4SvfVIithi5E,10113
|
| 203 |
+
prompt_toolkit/layout/scrollable_pane.py,sha256=JQtPfafU61RJt3MzGW2wsw96o1sjJH0g2DSVyO7J6qA,19264
|
| 204 |
+
prompt_toolkit/layout/utils.py,sha256=qot9clyeG3FoplfAS2O6QxmnnA_PDln4-dUJ5Hu76fQ,2371
|
| 205 |
+
prompt_toolkit/lexers/__init__.py,sha256=QldV9b8J2Kb9Exyv2fDss-YRzP07z2FYAhwPN4coWn8,409
|
| 206 |
+
prompt_toolkit/lexers/__pycache__/__init__.cpython-312.pyc,,
|
| 207 |
+
prompt_toolkit/lexers/base.py,sha256=XdyKLj4rD25CVCqSCfElWE3ppBN1LGQ9fRLPi1oYfl0,2350
|
| 208 |
+
prompt_toolkit/lexers/pygments.py,sha256=it89LjsltZpzlQJPb95GX4GdMu7gq1J1QzWC29lCQi4,11922
|
| 209 |
+
prompt_toolkit/log.py,sha256=6typpL_jnewdUc3j2OoplVLwnP9dSMOkIsJy_sgR9IY,153
|
| 210 |
+
prompt_toolkit/mouse_events.py,sha256=4mUHJbG8WrrQznw7z_jsOrdmldC5ZMRM4gDDUy51pyk,2473
|
| 211 |
+
prompt_toolkit/output/__init__.py,sha256=GVlT-U_W0EuIP-c1Qjyp0DN6Fl2PsCEhFzjUMRHsGWI,280
|
| 212 |
+
prompt_toolkit/output/__pycache__/__init__.cpython-312.pyc,,
|
| 213 |
+
prompt_toolkit/output/__pycache__/color_depth.cpython-312.pyc,,
|
| 214 |
+
prompt_toolkit/output/__pycache__/conemu.cpython-312.pyc,,
|
| 215 |
+
prompt_toolkit/output/__pycache__/defaults.cpython-312.pyc,,
|
| 216 |
+
prompt_toolkit/output/__pycache__/flush_stdout.cpython-312.pyc,,
|
| 217 |
+
prompt_toolkit/output/__pycache__/plain_text.cpython-312.pyc,,
|
| 218 |
+
prompt_toolkit/output/__pycache__/windows10.cpython-312.pyc,,
|
| 219 |
+
prompt_toolkit/output/base.py,sha256=o74Vok7cXLxgHoAaqKHQAGcNZILn5B5g6Z0pUXU6x7s,8348
|
| 220 |
+
prompt_toolkit/output/color_depth.py,sha256=KEFTlxCYTqOvA-VDx4wUb8G6HaYD5Hbf5GKmPZwssCs,1569
|
| 221 |
+
prompt_toolkit/output/conemu.py,sha256=_w2IEFR-mXsaMFINgZITiJNRCS9QowLUxeskPEpz2GE,1865
|
| 222 |
+
prompt_toolkit/output/defaults.py,sha256=72RecTuugrjvfZinbvsFRYDwMcczE9Zw3ttmmiG0Ivg,3689
|
| 223 |
+
prompt_toolkit/output/flush_stdout.py,sha256=ReT0j0IwVJEcth7VJj2zE6UcY0OVz5Ut1rpANnbCyYQ,3236
|
| 224 |
+
prompt_toolkit/output/plain_text.py,sha256=VnjoDmy0pKQoubXXQJQ_MljoDYi1FcLdNZB2KN_TQIs,3296
|
| 225 |
+
prompt_toolkit/output/vt100.py,sha256=db6G9uoSDaNbGVejjnYNffZ4nPsDDcL_X70yrJCb9Ds,23383
|
| 226 |
+
prompt_toolkit/output/win32.py,sha256=d5LG_3dLLcHomJ_eJiFAcmdSe1VSjgneWR9xg2EHz-M,22622
|
| 227 |
+
prompt_toolkit/output/windows10.py,sha256=yf0i1xAs-mbqOCwq25K78hkJjju1jXZ5b0e-w9aSBBA,4362
|
| 228 |
+
prompt_toolkit/patch_stdout.py,sha256=8gEaQdqykdBczlvp3FrOjDlEG02yeXoYKrDAGqj48Wg,9477
|
| 229 |
+
prompt_toolkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 230 |
+
prompt_toolkit/renderer.py,sha256=h4r7bShanQyvh9nrSQfxvHZUPT6ZPUH1kD5Nbeu2RwY,29398
|
| 231 |
+
prompt_toolkit/search.py,sha256=6Go_LtBeBlIMkdUCqb-WFCBKLchd70kgtccqP5dyv08,6951
|
| 232 |
+
prompt_toolkit/selection.py,sha256=P6zQOahBqqt1YZmfQ2-V9iJjOo4cxl0bdmU_-0jezJI,1274
|
| 233 |
+
prompt_toolkit/shortcuts/__init__.py,sha256=AOdDyiuu4t2itpHhFcBdvY-Tgzzi1HwQNnu2la3yaXw,949
|
| 234 |
+
prompt_toolkit/shortcuts/__pycache__/__init__.cpython-312.pyc,,
|
| 235 |
+
prompt_toolkit/shortcuts/__pycache__/utils.cpython-312.pyc,,
|
| 236 |
+
prompt_toolkit/shortcuts/dialogs.py,sha256=gFibLlbaii8ijuurk9TpbNi5fMTHu99T6m1wfFilbE8,9007
|
| 237 |
+
prompt_toolkit/shortcuts/progress_bar/__init__.py,sha256=QeAssmFBDPCC5VRoObAp4UkebwETP3qS7-na4acstWM,540
|
| 238 |
+
prompt_toolkit/shortcuts/progress_bar/__pycache__/__init__.cpython-312.pyc,,
|
| 239 |
+
prompt_toolkit/shortcuts/progress_bar/__pycache__/base.cpython-312.pyc,,
|
| 240 |
+
prompt_toolkit/shortcuts/progress_bar/__pycache__/formatters.cpython-312.pyc,,
|
| 241 |
+
prompt_toolkit/shortcuts/progress_bar/base.py,sha256=_cqp7coZMFDc7ZoAUL1iz3fL1Dt5hw3hi1HEfBvUpK8,14402
|
| 242 |
+
prompt_toolkit/shortcuts/progress_bar/formatters.py,sha256=VfRADwUm8op-DzoM51UrKI8pSa1T1LAz5q9VMUW2siI,11739
|
| 243 |
+
prompt_toolkit/shortcuts/prompt.py,sha256=IbbpTaV71ER8c920U5cf9CTkGHudfHtWnWuu4A_TjPE,60235
|
| 244 |
+
prompt_toolkit/shortcuts/utils.py,sha256=NNjBY0Brkcb13Gxhh7Yc72_YpDFsQbkIlm7ZXvW3rK0,6950
|
| 245 |
+
prompt_toolkit/styles/__init__.py,sha256=7N1NNE1gTQo5mjT9f7mRwRodkrBoNpT9pmqWK-lrSeY,1640
|
| 246 |
+
prompt_toolkit/styles/__pycache__/__init__.cpython-312.pyc,,
|
| 247 |
+
prompt_toolkit/styles/__pycache__/base.cpython-312.pyc,,
|
| 248 |
+
prompt_toolkit/styles/__pycache__/defaults.cpython-312.pyc,,
|
| 249 |
+
prompt_toolkit/styles/__pycache__/named_colors.cpython-312.pyc,,
|
| 250 |
+
prompt_toolkit/styles/__pycache__/pygments.cpython-312.pyc,,
|
| 251 |
+
prompt_toolkit/styles/__pycache__/style.cpython-312.pyc,,
|
| 252 |
+
prompt_toolkit/styles/__pycache__/style_transformation.cpython-312.pyc,,
|
| 253 |
+
prompt_toolkit/styles/base.py,sha256=9oTmvqg0Rxy9VEVbRxq_4_P_NnPWVr9QedK56kea2Ro,5014
|
| 254 |
+
prompt_toolkit/styles/defaults.py,sha256=TRnP1PeuauYa_Ru1PpJ_ImsfaldvLE1JjmPV8tvfJjs,8699
|
| 255 |
+
prompt_toolkit/styles/named_colors.py,sha256=yZ30oKB-fCRk6RMASYg8q3Uz2zgdfy_YNbuQWYpyYas,4367
|
| 256 |
+
prompt_toolkit/styles/pygments.py,sha256=yWJEcvYCFo1e2EN9IF5HWpxHQ104J0HOJg1LUsSA9oM,1974
|
| 257 |
+
prompt_toolkit/styles/style.py,sha256=ve7MBciSq6cBOXhboC_RLrlrEqQlq5kWn0XgFI6wNVU,13043
|
| 258 |
+
prompt_toolkit/styles/style_transformation.py,sha256=cGaOo-jqhP79QoEHLQxrOZo9QMrxWxtXgfXKsHlx1Jg,12427
|
| 259 |
+
prompt_toolkit/token.py,sha256=do3EnxLrCDVbq47MzJ2vqSYps-CjVKWNCWzCZgdf5Jo,121
|
| 260 |
+
prompt_toolkit/utils.py,sha256=7O8hILpI2VZb0KoC7J-5z1S2aXICf_kwtmRq5xdfDTg,8631
|
| 261 |
+
prompt_toolkit/validation.py,sha256=XTdmExMgaqj-Whym9yYyQxOAaKce97KYyyGXwCxMr-A,5807
|
| 262 |
+
prompt_toolkit/widgets/__init__.py,sha256=RZXj6UzZWFuxOQXc1TwHLIwwZYJU-YBAaV4oLrC2dCA,1218
|
| 263 |
+
prompt_toolkit/widgets/__pycache__/base.cpython-312.pyc,,
|
| 264 |
+
prompt_toolkit/widgets/__pycache__/dialogs.cpython-312.pyc,,
|
| 265 |
+
prompt_toolkit/widgets/__pycache__/menus.cpython-312.pyc,,
|
| 266 |
+
prompt_toolkit/widgets/__pycache__/toolbars.cpython-312.pyc,,
|
| 267 |
+
prompt_toolkit/widgets/base.py,sha256=zdfJ9-kMGRpocEi-GqDIOgt9prxRkN8GB8D1uT0409U,32351
|
| 268 |
+
prompt_toolkit/widgets/dialogs.py,sha256=K2ACcf0rKXwpBQGQcjSTq2aNeSInGmklzZRPnhdtZTc,3380
|
| 269 |
+
prompt_toolkit/widgets/menus.py,sha256=SeX-llaTpF1pVak2lw37mAP0SFDONIRZT5oq23mARg8,13419
|
| 270 |
+
prompt_toolkit/widgets/toolbars.py,sha256=MoxOxaa8Yi3nJvH4G8OCwlNuwx3XWUJ07J0a7D17_w0,12178
|
| 271 |
+
prompt_toolkit/win32_types.py,sha256=3xVjabRA3Q-RN2x3DLqTOrstuYj4_uCq6w2i8t6LZ6E,5551
|
.venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (78.1.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
.venv/Lib/site-packages/prompt_toolkit-3.0.51.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
prompt_toolkit
|
.venv/Lib/site-packages/prompt_toolkit/__init__.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
prompt_toolkit
|
| 3 |
+
==============
|
| 4 |
+
|
| 5 |
+
Author: Jonathan Slenders
|
| 6 |
+
|
| 7 |
+
Description: prompt_toolkit is a Library for building powerful interactive
|
| 8 |
+
command lines in Python. It can be a replacement for GNU
|
| 9 |
+
Readline, but it can be much more than that.
|
| 10 |
+
|
| 11 |
+
See the examples directory to learn about the usage.
|
| 12 |
+
|
| 13 |
+
Probably, to get started, you might also want to have a look at
|
| 14 |
+
`prompt_toolkit.shortcuts.prompt`.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import re
|
| 20 |
+
from importlib import metadata
|
| 21 |
+
|
| 22 |
+
# note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number
|
| 23 |
+
pep440 = re.compile(
|
| 24 |
+
r"^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*)?)?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$",
|
| 25 |
+
re.UNICODE,
|
| 26 |
+
)
|
| 27 |
+
from .application import Application
|
| 28 |
+
from .formatted_text import ANSI, HTML
|
| 29 |
+
from .shortcuts import PromptSession, print_formatted_text, prompt
|
| 30 |
+
|
| 31 |
+
# Don't forget to update in `docs/conf.py`!
|
| 32 |
+
__version__ = metadata.version("prompt_toolkit")
|
| 33 |
+
|
| 34 |
+
assert pep440.match(__version__)
|
| 35 |
+
|
| 36 |
+
# Version tuple.
|
| 37 |
+
VERSION = tuple(int(v.rstrip("abrc")) for v in __version__.split(".")[:3])
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
__all__ = [
|
| 41 |
+
# Application.
|
| 42 |
+
"Application",
|
| 43 |
+
# Shortcuts.
|
| 44 |
+
"prompt",
|
| 45 |
+
"PromptSession",
|
| 46 |
+
"print_formatted_text",
|
| 47 |
+
# Formatted text.
|
| 48 |
+
"HTML",
|
| 49 |
+
"ANSI",
|
| 50 |
+
# Version info.
|
| 51 |
+
"__version__",
|
| 52 |
+
"VERSION",
|
| 53 |
+
]
|
.venv/Lib/site-packages/prompt_toolkit/document.py
ADDED
|
@@ -0,0 +1,1182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
The `Document` that implements all the text operations/querying.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import bisect
|
| 8 |
+
import re
|
| 9 |
+
import string
|
| 10 |
+
import weakref
|
| 11 |
+
from typing import Callable, Dict, Iterable, List, NoReturn, Pattern, cast
|
| 12 |
+
|
| 13 |
+
from .clipboard import ClipboardData
|
| 14 |
+
from .filters import vi_mode
|
| 15 |
+
from .selection import PasteMode, SelectionState, SelectionType
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
"Document",
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# Regex for finding "words" in documents. (We consider a group of alnum
|
| 23 |
+
# characters a word, but also a group of special characters a word, as long as
|
| 24 |
+
# it doesn't contain a space.)
|
| 25 |
+
# (This is a 'word' in Vi.)
|
| 26 |
+
_FIND_WORD_RE = re.compile(r"([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)")
|
| 27 |
+
_FIND_CURRENT_WORD_RE = re.compile(r"^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)")
|
| 28 |
+
_FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(
|
| 29 |
+
r"^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)"
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
# Regex for finding "WORDS" in documents.
|
| 33 |
+
# (This is a 'WORD in Vi.)
|
| 34 |
+
_FIND_BIG_WORD_RE = re.compile(r"([^\s]+)")
|
| 35 |
+
_FIND_CURRENT_BIG_WORD_RE = re.compile(r"^([^\s]+)")
|
| 36 |
+
_FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r"^([^\s]+\s*)")
|
| 37 |
+
|
| 38 |
+
# Share the Document._cache between all Document instances.
|
| 39 |
+
# (Document instances are considered immutable. That means that if another
|
| 40 |
+
# `Document` is constructed with the same text, it should have the same
|
| 41 |
+
# `_DocumentCache`.)
|
| 42 |
+
_text_to_document_cache: dict[str, _DocumentCache] = cast(
|
| 43 |
+
Dict[str, "_DocumentCache"],
|
| 44 |
+
weakref.WeakValueDictionary(), # Maps document.text to DocumentCache instance.
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class _ImmutableLineList(List[str]):
|
| 49 |
+
"""
|
| 50 |
+
Some protection for our 'lines' list, which is assumed to be immutable in the cache.
|
| 51 |
+
(Useful for detecting obvious bugs.)
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
def _error(self, *a: object, **kw: object) -> NoReturn:
|
| 55 |
+
raise NotImplementedError("Attempt to modify an immutable list.")
|
| 56 |
+
|
| 57 |
+
__setitem__ = _error # type: ignore
|
| 58 |
+
append = _error
|
| 59 |
+
clear = _error
|
| 60 |
+
extend = _error
|
| 61 |
+
insert = _error
|
| 62 |
+
pop = _error
|
| 63 |
+
remove = _error
|
| 64 |
+
reverse = _error
|
| 65 |
+
sort = _error # type: ignore
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class _DocumentCache:
|
| 69 |
+
def __init__(self) -> None:
|
| 70 |
+
#: List of lines for the Document text.
|
| 71 |
+
self.lines: _ImmutableLineList | None = None
|
| 72 |
+
|
| 73 |
+
#: List of index positions, pointing to the start of all the lines.
|
| 74 |
+
self.line_indexes: list[int] | None = None
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class Document:
|
| 78 |
+
"""
|
| 79 |
+
This is a immutable class around the text and cursor position, and contains
|
| 80 |
+
methods for querying this data, e.g. to give the text before the cursor.
|
| 81 |
+
|
| 82 |
+
This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer`
|
| 83 |
+
object, and accessed as the `document` property of that class.
|
| 84 |
+
|
| 85 |
+
:param text: string
|
| 86 |
+
:param cursor_position: int
|
| 87 |
+
:param selection: :class:`.SelectionState`
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
__slots__ = ("_text", "_cursor_position", "_selection", "_cache")
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
text: str = "",
|
| 95 |
+
cursor_position: int | None = None,
|
| 96 |
+
selection: SelectionState | None = None,
|
| 97 |
+
) -> None:
|
| 98 |
+
# Check cursor position. It can also be right after the end. (Where we
|
| 99 |
+
# insert text.)
|
| 100 |
+
assert cursor_position is None or cursor_position <= len(text), AssertionError(
|
| 101 |
+
f"cursor_position={cursor_position!r}, len_text={len(text)!r}"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
# By default, if no cursor position was given, make sure to put the
|
| 105 |
+
# cursor position is at the end of the document. This is what makes
|
| 106 |
+
# sense in most places.
|
| 107 |
+
if cursor_position is None:
|
| 108 |
+
cursor_position = len(text)
|
| 109 |
+
|
| 110 |
+
# Keep these attributes private. A `Document` really has to be
|
| 111 |
+
# considered to be immutable, because otherwise the caching will break
|
| 112 |
+
# things. Because of that, we wrap these into read-only properties.
|
| 113 |
+
self._text = text
|
| 114 |
+
self._cursor_position = cursor_position
|
| 115 |
+
self._selection = selection
|
| 116 |
+
|
| 117 |
+
# Cache for lines/indexes. (Shared with other Document instances that
|
| 118 |
+
# contain the same text.
|
| 119 |
+
try:
|
| 120 |
+
self._cache = _text_to_document_cache[self.text]
|
| 121 |
+
except KeyError:
|
| 122 |
+
self._cache = _DocumentCache()
|
| 123 |
+
_text_to_document_cache[self.text] = self._cache
|
| 124 |
+
|
| 125 |
+
# XX: For some reason, above, we can't use 'WeakValueDictionary.setdefault'.
|
| 126 |
+
# This fails in Pypy3. `self._cache` becomes None, because that's what
|
| 127 |
+
# 'setdefault' returns.
|
| 128 |
+
# self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache())
|
| 129 |
+
# assert self._cache
|
| 130 |
+
|
| 131 |
+
def __repr__(self) -> str:
|
| 132 |
+
return f"{self.__class__.__name__}({self.text!r}, {self.cursor_position!r})"
|
| 133 |
+
|
| 134 |
+
def __eq__(self, other: object) -> bool:
|
| 135 |
+
if not isinstance(other, Document):
|
| 136 |
+
return False
|
| 137 |
+
|
| 138 |
+
return (
|
| 139 |
+
self.text == other.text
|
| 140 |
+
and self.cursor_position == other.cursor_position
|
| 141 |
+
and self.selection == other.selection
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
@property
|
| 145 |
+
def text(self) -> str:
|
| 146 |
+
"The document text."
|
| 147 |
+
return self._text
|
| 148 |
+
|
| 149 |
+
@property
|
| 150 |
+
def cursor_position(self) -> int:
|
| 151 |
+
"The document cursor position."
|
| 152 |
+
return self._cursor_position
|
| 153 |
+
|
| 154 |
+
@property
|
| 155 |
+
def selection(self) -> SelectionState | None:
|
| 156 |
+
":class:`.SelectionState` object."
|
| 157 |
+
return self._selection
|
| 158 |
+
|
| 159 |
+
@property
|
| 160 |
+
def current_char(self) -> str:
|
| 161 |
+
"""Return character under cursor or an empty string."""
|
| 162 |
+
return self._get_char_relative_to_cursor(0) or ""
|
| 163 |
+
|
| 164 |
+
@property
|
| 165 |
+
def char_before_cursor(self) -> str:
|
| 166 |
+
"""Return character before the cursor or an empty string."""
|
| 167 |
+
return self._get_char_relative_to_cursor(-1) or ""
|
| 168 |
+
|
| 169 |
+
@property
|
| 170 |
+
def text_before_cursor(self) -> str:
|
| 171 |
+
return self.text[: self.cursor_position :]
|
| 172 |
+
|
| 173 |
+
@property
|
| 174 |
+
def text_after_cursor(self) -> str:
|
| 175 |
+
return self.text[self.cursor_position :]
|
| 176 |
+
|
| 177 |
+
@property
|
| 178 |
+
def current_line_before_cursor(self) -> str:
|
| 179 |
+
"""Text from the start of the line until the cursor."""
|
| 180 |
+
_, _, text = self.text_before_cursor.rpartition("\n")
|
| 181 |
+
return text
|
| 182 |
+
|
| 183 |
+
@property
|
| 184 |
+
def current_line_after_cursor(self) -> str:
|
| 185 |
+
"""Text from the cursor until the end of the line."""
|
| 186 |
+
text, _, _ = self.text_after_cursor.partition("\n")
|
| 187 |
+
return text
|
| 188 |
+
|
| 189 |
+
@property
|
| 190 |
+
def lines(self) -> list[str]:
|
| 191 |
+
"""
|
| 192 |
+
Array of all the lines.
|
| 193 |
+
"""
|
| 194 |
+
# Cache, because this one is reused very often.
|
| 195 |
+
if self._cache.lines is None:
|
| 196 |
+
self._cache.lines = _ImmutableLineList(self.text.split("\n"))
|
| 197 |
+
|
| 198 |
+
return self._cache.lines
|
| 199 |
+
|
| 200 |
+
@property
|
| 201 |
+
def _line_start_indexes(self) -> list[int]:
|
| 202 |
+
"""
|
| 203 |
+
Array pointing to the start indexes of all the lines.
|
| 204 |
+
"""
|
| 205 |
+
# Cache, because this is often reused. (If it is used, it's often used
|
| 206 |
+
# many times. And this has to be fast for editing big documents!)
|
| 207 |
+
if self._cache.line_indexes is None:
|
| 208 |
+
# Create list of line lengths.
|
| 209 |
+
line_lengths = map(len, self.lines)
|
| 210 |
+
|
| 211 |
+
# Calculate cumulative sums.
|
| 212 |
+
indexes = [0]
|
| 213 |
+
append = indexes.append
|
| 214 |
+
pos = 0
|
| 215 |
+
|
| 216 |
+
for line_length in line_lengths:
|
| 217 |
+
pos += line_length + 1
|
| 218 |
+
append(pos)
|
| 219 |
+
|
| 220 |
+
# Remove the last item. (This is not a new line.)
|
| 221 |
+
if len(indexes) > 1:
|
| 222 |
+
indexes.pop()
|
| 223 |
+
|
| 224 |
+
self._cache.line_indexes = indexes
|
| 225 |
+
|
| 226 |
+
return self._cache.line_indexes
|
| 227 |
+
|
| 228 |
+
@property
|
| 229 |
+
def lines_from_current(self) -> list[str]:
|
| 230 |
+
"""
|
| 231 |
+
Array of the lines starting from the current line, until the last line.
|
| 232 |
+
"""
|
| 233 |
+
return self.lines[self.cursor_position_row :]
|
| 234 |
+
|
| 235 |
+
@property
|
| 236 |
+
def line_count(self) -> int:
|
| 237 |
+
r"""Return the number of lines in this document. If the document ends
|
| 238 |
+
with a trailing \n, that counts as the beginning of a new line."""
|
| 239 |
+
return len(self.lines)
|
| 240 |
+
|
| 241 |
+
@property
|
| 242 |
+
def current_line(self) -> str:
|
| 243 |
+
"""Return the text on the line where the cursor is. (when the input
|
| 244 |
+
consists of just one line, it equals `text`."""
|
| 245 |
+
return self.current_line_before_cursor + self.current_line_after_cursor
|
| 246 |
+
|
| 247 |
+
@property
|
| 248 |
+
def leading_whitespace_in_current_line(self) -> str:
|
| 249 |
+
"""The leading whitespace in the left margin of the current line."""
|
| 250 |
+
current_line = self.current_line
|
| 251 |
+
length = len(current_line) - len(current_line.lstrip())
|
| 252 |
+
return current_line[:length]
|
| 253 |
+
|
| 254 |
+
def _get_char_relative_to_cursor(self, offset: int = 0) -> str:
|
| 255 |
+
"""
|
| 256 |
+
Return character relative to cursor position, or empty string
|
| 257 |
+
"""
|
| 258 |
+
try:
|
| 259 |
+
return self.text[self.cursor_position + offset]
|
| 260 |
+
except IndexError:
|
| 261 |
+
return ""
|
| 262 |
+
|
| 263 |
+
@property
|
| 264 |
+
def on_first_line(self) -> bool:
|
| 265 |
+
"""
|
| 266 |
+
True when we are at the first line.
|
| 267 |
+
"""
|
| 268 |
+
return self.cursor_position_row == 0
|
| 269 |
+
|
| 270 |
+
@property
|
| 271 |
+
def on_last_line(self) -> bool:
|
| 272 |
+
"""
|
| 273 |
+
True when we are at the last line.
|
| 274 |
+
"""
|
| 275 |
+
return self.cursor_position_row == self.line_count - 1
|
| 276 |
+
|
| 277 |
+
@property
|
| 278 |
+
def cursor_position_row(self) -> int:
|
| 279 |
+
"""
|
| 280 |
+
Current row. (0-based.)
|
| 281 |
+
"""
|
| 282 |
+
row, _ = self._find_line_start_index(self.cursor_position)
|
| 283 |
+
return row
|
| 284 |
+
|
| 285 |
+
@property
|
| 286 |
+
def cursor_position_col(self) -> int:
|
| 287 |
+
"""
|
| 288 |
+
Current column. (0-based.)
|
| 289 |
+
"""
|
| 290 |
+
# (Don't use self.text_before_cursor to calculate this. Creating
|
| 291 |
+
# substrings and doing rsplit is too expensive for getting the cursor
|
| 292 |
+
# position.)
|
| 293 |
+
_, line_start_index = self._find_line_start_index(self.cursor_position)
|
| 294 |
+
return self.cursor_position - line_start_index
|
| 295 |
+
|
| 296 |
+
def _find_line_start_index(self, index: int) -> tuple[int, int]:
|
| 297 |
+
"""
|
| 298 |
+
For the index of a character at a certain line, calculate the index of
|
| 299 |
+
the first character on that line.
|
| 300 |
+
|
| 301 |
+
Return (row, index) tuple.
|
| 302 |
+
"""
|
| 303 |
+
indexes = self._line_start_indexes
|
| 304 |
+
|
| 305 |
+
pos = bisect.bisect_right(indexes, index) - 1
|
| 306 |
+
return pos, indexes[pos]
|
| 307 |
+
|
| 308 |
+
def translate_index_to_position(self, index: int) -> tuple[int, int]:
|
| 309 |
+
"""
|
| 310 |
+
Given an index for the text, return the corresponding (row, col) tuple.
|
| 311 |
+
(0-based. Returns (0, 0) for index=0.)
|
| 312 |
+
"""
|
| 313 |
+
# Find start of this line.
|
| 314 |
+
row, row_index = self._find_line_start_index(index)
|
| 315 |
+
col = index - row_index
|
| 316 |
+
|
| 317 |
+
return row, col
|
| 318 |
+
|
| 319 |
+
def translate_row_col_to_index(self, row: int, col: int) -> int:
|
| 320 |
+
"""
|
| 321 |
+
Given a (row, col) tuple, return the corresponding index.
|
| 322 |
+
(Row and col params are 0-based.)
|
| 323 |
+
|
| 324 |
+
Negative row/col values are turned into zero.
|
| 325 |
+
"""
|
| 326 |
+
try:
|
| 327 |
+
result = self._line_start_indexes[row]
|
| 328 |
+
line = self.lines[row]
|
| 329 |
+
except IndexError:
|
| 330 |
+
if row < 0:
|
| 331 |
+
result = self._line_start_indexes[0]
|
| 332 |
+
line = self.lines[0]
|
| 333 |
+
else:
|
| 334 |
+
result = self._line_start_indexes[-1]
|
| 335 |
+
line = self.lines[-1]
|
| 336 |
+
|
| 337 |
+
result += max(0, min(col, len(line)))
|
| 338 |
+
|
| 339 |
+
# Keep in range. (len(self.text) is included, because the cursor can be
|
| 340 |
+
# right after the end of the text as well.)
|
| 341 |
+
result = max(0, min(result, len(self.text)))
|
| 342 |
+
return result
|
| 343 |
+
|
| 344 |
+
@property
|
| 345 |
+
def is_cursor_at_the_end(self) -> bool:
|
| 346 |
+
"""True when the cursor is at the end of the text."""
|
| 347 |
+
return self.cursor_position == len(self.text)
|
| 348 |
+
|
| 349 |
+
@property
|
| 350 |
+
def is_cursor_at_the_end_of_line(self) -> bool:
|
| 351 |
+
"""True when the cursor is at the end of this line."""
|
| 352 |
+
return self.current_char in ("\n", "")
|
| 353 |
+
|
| 354 |
+
def has_match_at_current_position(self, sub: str) -> bool:
|
| 355 |
+
"""
|
| 356 |
+
`True` when this substring is found at the cursor position.
|
| 357 |
+
"""
|
| 358 |
+
return self.text.find(sub, self.cursor_position) == self.cursor_position
|
| 359 |
+
|
| 360 |
+
def find(
|
| 361 |
+
self,
|
| 362 |
+
sub: str,
|
| 363 |
+
in_current_line: bool = False,
|
| 364 |
+
include_current_position: bool = False,
|
| 365 |
+
ignore_case: bool = False,
|
| 366 |
+
count: int = 1,
|
| 367 |
+
) -> int | None:
|
| 368 |
+
"""
|
| 369 |
+
Find `text` after the cursor, return position relative to the cursor
|
| 370 |
+
position. Return `None` if nothing was found.
|
| 371 |
+
|
| 372 |
+
:param count: Find the n-th occurrence.
|
| 373 |
+
"""
|
| 374 |
+
assert isinstance(ignore_case, bool)
|
| 375 |
+
|
| 376 |
+
if in_current_line:
|
| 377 |
+
text = self.current_line_after_cursor
|
| 378 |
+
else:
|
| 379 |
+
text = self.text_after_cursor
|
| 380 |
+
|
| 381 |
+
if not include_current_position:
|
| 382 |
+
if len(text) == 0:
|
| 383 |
+
return None # (Otherwise, we always get a match for the empty string.)
|
| 384 |
+
else:
|
| 385 |
+
text = text[1:]
|
| 386 |
+
|
| 387 |
+
flags = re.IGNORECASE if ignore_case else 0
|
| 388 |
+
iterator = re.finditer(re.escape(sub), text, flags)
|
| 389 |
+
|
| 390 |
+
try:
|
| 391 |
+
for i, match in enumerate(iterator):
|
| 392 |
+
if i + 1 == count:
|
| 393 |
+
if include_current_position:
|
| 394 |
+
return match.start(0)
|
| 395 |
+
else:
|
| 396 |
+
return match.start(0) + 1
|
| 397 |
+
except StopIteration:
|
| 398 |
+
pass
|
| 399 |
+
return None
|
| 400 |
+
|
| 401 |
+
def find_all(self, sub: str, ignore_case: bool = False) -> list[int]:
|
| 402 |
+
"""
|
| 403 |
+
Find all occurrences of the substring. Return a list of absolute
|
| 404 |
+
positions in the document.
|
| 405 |
+
"""
|
| 406 |
+
flags = re.IGNORECASE if ignore_case else 0
|
| 407 |
+
return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)]
|
| 408 |
+
|
| 409 |
+
def find_backwards(
|
| 410 |
+
self,
|
| 411 |
+
sub: str,
|
| 412 |
+
in_current_line: bool = False,
|
| 413 |
+
ignore_case: bool = False,
|
| 414 |
+
count: int = 1,
|
| 415 |
+
) -> int | None:
|
| 416 |
+
"""
|
| 417 |
+
Find `text` before the cursor, return position relative to the cursor
|
| 418 |
+
position. Return `None` if nothing was found.
|
| 419 |
+
|
| 420 |
+
:param count: Find the n-th occurrence.
|
| 421 |
+
"""
|
| 422 |
+
if in_current_line:
|
| 423 |
+
before_cursor = self.current_line_before_cursor[::-1]
|
| 424 |
+
else:
|
| 425 |
+
before_cursor = self.text_before_cursor[::-1]
|
| 426 |
+
|
| 427 |
+
flags = re.IGNORECASE if ignore_case else 0
|
| 428 |
+
iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags)
|
| 429 |
+
|
| 430 |
+
try:
|
| 431 |
+
for i, match in enumerate(iterator):
|
| 432 |
+
if i + 1 == count:
|
| 433 |
+
return -match.start(0) - len(sub)
|
| 434 |
+
except StopIteration:
|
| 435 |
+
pass
|
| 436 |
+
return None
|
| 437 |
+
|
| 438 |
+
def get_word_before_cursor(
|
| 439 |
+
self, WORD: bool = False, pattern: Pattern[str] | None = None
|
| 440 |
+
) -> str:
|
| 441 |
+
"""
|
| 442 |
+
Give the word before the cursor.
|
| 443 |
+
If we have whitespace before the cursor this returns an empty string.
|
| 444 |
+
|
| 445 |
+
:param pattern: (None or compiled regex). When given, use this regex
|
| 446 |
+
pattern.
|
| 447 |
+
"""
|
| 448 |
+
if self._is_word_before_cursor_complete(WORD=WORD, pattern=pattern):
|
| 449 |
+
# Space before the cursor or no text before cursor.
|
| 450 |
+
return ""
|
| 451 |
+
|
| 452 |
+
text_before_cursor = self.text_before_cursor
|
| 453 |
+
start = self.find_start_of_previous_word(WORD=WORD, pattern=pattern) or 0
|
| 454 |
+
|
| 455 |
+
return text_before_cursor[len(text_before_cursor) + start :]
|
| 456 |
+
|
| 457 |
+
def _is_word_before_cursor_complete(
|
| 458 |
+
self, WORD: bool = False, pattern: Pattern[str] | None = None
|
| 459 |
+
) -> bool:
|
| 460 |
+
if pattern:
|
| 461 |
+
return self.find_start_of_previous_word(WORD=WORD, pattern=pattern) is None
|
| 462 |
+
else:
|
| 463 |
+
return (
|
| 464 |
+
self.text_before_cursor == "" or self.text_before_cursor[-1:].isspace()
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
def find_start_of_previous_word(
|
| 468 |
+
self, count: int = 1, WORD: bool = False, pattern: Pattern[str] | None = None
|
| 469 |
+
) -> int | None:
|
| 470 |
+
"""
|
| 471 |
+
Return an index relative to the cursor position pointing to the start
|
| 472 |
+
of the previous word. Return `None` if nothing was found.
|
| 473 |
+
|
| 474 |
+
:param pattern: (None or compiled regex). When given, use this regex
|
| 475 |
+
pattern.
|
| 476 |
+
"""
|
| 477 |
+
assert not (WORD and pattern)
|
| 478 |
+
|
| 479 |
+
# Reverse the text before the cursor, in order to do an efficient
|
| 480 |
+
# backwards search.
|
| 481 |
+
text_before_cursor = self.text_before_cursor[::-1]
|
| 482 |
+
|
| 483 |
+
if pattern:
|
| 484 |
+
regex = pattern
|
| 485 |
+
elif WORD:
|
| 486 |
+
regex = _FIND_BIG_WORD_RE
|
| 487 |
+
else:
|
| 488 |
+
regex = _FIND_WORD_RE
|
| 489 |
+
|
| 490 |
+
iterator = regex.finditer(text_before_cursor)
|
| 491 |
+
|
| 492 |
+
try:
|
| 493 |
+
for i, match in enumerate(iterator):
|
| 494 |
+
if i + 1 == count:
|
| 495 |
+
return -match.end(0)
|
| 496 |
+
except StopIteration:
|
| 497 |
+
pass
|
| 498 |
+
return None
|
| 499 |
+
|
| 500 |
+
def find_boundaries_of_current_word(
|
| 501 |
+
self,
|
| 502 |
+
WORD: bool = False,
|
| 503 |
+
include_leading_whitespace: bool = False,
|
| 504 |
+
include_trailing_whitespace: bool = False,
|
| 505 |
+
) -> tuple[int, int]:
|
| 506 |
+
"""
|
| 507 |
+
Return the relative boundaries (startpos, endpos) of the current word under the
|
| 508 |
+
cursor. (This is at the current line, because line boundaries obviously
|
| 509 |
+
don't belong to any word.)
|
| 510 |
+
If not on a word, this returns (0,0)
|
| 511 |
+
"""
|
| 512 |
+
text_before_cursor = self.current_line_before_cursor[::-1]
|
| 513 |
+
text_after_cursor = self.current_line_after_cursor
|
| 514 |
+
|
| 515 |
+
def get_regex(include_whitespace: bool) -> Pattern[str]:
|
| 516 |
+
return {
|
| 517 |
+
(False, False): _FIND_CURRENT_WORD_RE,
|
| 518 |
+
(False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE,
|
| 519 |
+
(True, False): _FIND_CURRENT_BIG_WORD_RE,
|
| 520 |
+
(True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE,
|
| 521 |
+
}[(WORD, include_whitespace)]
|
| 522 |
+
|
| 523 |
+
match_before = get_regex(include_leading_whitespace).search(text_before_cursor)
|
| 524 |
+
match_after = get_regex(include_trailing_whitespace).search(text_after_cursor)
|
| 525 |
+
|
| 526 |
+
# When there is a match before and after, and we're not looking for
|
| 527 |
+
# WORDs, make sure that both the part before and after the cursor are
|
| 528 |
+
# either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part
|
| 529 |
+
# before the cursor.
|
| 530 |
+
if not WORD and match_before and match_after:
|
| 531 |
+
c1 = self.text[self.cursor_position - 1]
|
| 532 |
+
c2 = self.text[self.cursor_position]
|
| 533 |
+
alphabet = string.ascii_letters + "0123456789_"
|
| 534 |
+
|
| 535 |
+
if (c1 in alphabet) != (c2 in alphabet):
|
| 536 |
+
match_before = None
|
| 537 |
+
|
| 538 |
+
return (
|
| 539 |
+
-match_before.end(1) if match_before else 0,
|
| 540 |
+
match_after.end(1) if match_after else 0,
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
def get_word_under_cursor(self, WORD: bool = False) -> str:
|
| 544 |
+
"""
|
| 545 |
+
Return the word, currently below the cursor.
|
| 546 |
+
This returns an empty string when the cursor is on a whitespace region.
|
| 547 |
+
"""
|
| 548 |
+
start, end = self.find_boundaries_of_current_word(WORD=WORD)
|
| 549 |
+
return self.text[self.cursor_position + start : self.cursor_position + end]
|
| 550 |
+
|
| 551 |
+
def find_next_word_beginning(
|
| 552 |
+
self, count: int = 1, WORD: bool = False
|
| 553 |
+
) -> int | None:
|
| 554 |
+
"""
|
| 555 |
+
Return an index relative to the cursor position pointing to the start
|
| 556 |
+
of the next word. Return `None` if nothing was found.
|
| 557 |
+
"""
|
| 558 |
+
if count < 0:
|
| 559 |
+
return self.find_previous_word_beginning(count=-count, WORD=WORD)
|
| 560 |
+
|
| 561 |
+
regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
|
| 562 |
+
iterator = regex.finditer(self.text_after_cursor)
|
| 563 |
+
|
| 564 |
+
try:
|
| 565 |
+
for i, match in enumerate(iterator):
|
| 566 |
+
# Take first match, unless it's the word on which we're right now.
|
| 567 |
+
if i == 0 and match.start(1) == 0:
|
| 568 |
+
count += 1
|
| 569 |
+
|
| 570 |
+
if i + 1 == count:
|
| 571 |
+
return match.start(1)
|
| 572 |
+
except StopIteration:
|
| 573 |
+
pass
|
| 574 |
+
return None
|
| 575 |
+
|
| 576 |
+
def find_next_word_ending(
|
| 577 |
+
self, include_current_position: bool = False, count: int = 1, WORD: bool = False
|
| 578 |
+
) -> int | None:
|
| 579 |
+
"""
|
| 580 |
+
Return an index relative to the cursor position pointing to the end
|
| 581 |
+
of the next word. Return `None` if nothing was found.
|
| 582 |
+
"""
|
| 583 |
+
if count < 0:
|
| 584 |
+
return self.find_previous_word_ending(count=-count, WORD=WORD)
|
| 585 |
+
|
| 586 |
+
if include_current_position:
|
| 587 |
+
text = self.text_after_cursor
|
| 588 |
+
else:
|
| 589 |
+
text = self.text_after_cursor[1:]
|
| 590 |
+
|
| 591 |
+
regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
|
| 592 |
+
iterable = regex.finditer(text)
|
| 593 |
+
|
| 594 |
+
try:
|
| 595 |
+
for i, match in enumerate(iterable):
|
| 596 |
+
if i + 1 == count:
|
| 597 |
+
value = match.end(1)
|
| 598 |
+
|
| 599 |
+
if include_current_position:
|
| 600 |
+
return value
|
| 601 |
+
else:
|
| 602 |
+
return value + 1
|
| 603 |
+
|
| 604 |
+
except StopIteration:
|
| 605 |
+
pass
|
| 606 |
+
return None
|
| 607 |
+
|
| 608 |
+
def find_previous_word_beginning(
|
| 609 |
+
self, count: int = 1, WORD: bool = False
|
| 610 |
+
) -> int | None:
|
| 611 |
+
"""
|
| 612 |
+
Return an index relative to the cursor position pointing to the start
|
| 613 |
+
of the previous word. Return `None` if nothing was found.
|
| 614 |
+
"""
|
| 615 |
+
if count < 0:
|
| 616 |
+
return self.find_next_word_beginning(count=-count, WORD=WORD)
|
| 617 |
+
|
| 618 |
+
regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
|
| 619 |
+
iterator = regex.finditer(self.text_before_cursor[::-1])
|
| 620 |
+
|
| 621 |
+
try:
|
| 622 |
+
for i, match in enumerate(iterator):
|
| 623 |
+
if i + 1 == count:
|
| 624 |
+
return -match.end(1)
|
| 625 |
+
except StopIteration:
|
| 626 |
+
pass
|
| 627 |
+
return None
|
| 628 |
+
|
| 629 |
+
def find_previous_word_ending(
|
| 630 |
+
self, count: int = 1, WORD: bool = False
|
| 631 |
+
) -> int | None:
|
| 632 |
+
"""
|
| 633 |
+
Return an index relative to the cursor position pointing to the end
|
| 634 |
+
of the previous word. Return `None` if nothing was found.
|
| 635 |
+
"""
|
| 636 |
+
if count < 0:
|
| 637 |
+
return self.find_next_word_ending(count=-count, WORD=WORD)
|
| 638 |
+
|
| 639 |
+
text_before_cursor = self.text_after_cursor[:1] + self.text_before_cursor[::-1]
|
| 640 |
+
|
| 641 |
+
regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE
|
| 642 |
+
iterator = regex.finditer(text_before_cursor)
|
| 643 |
+
|
| 644 |
+
try:
|
| 645 |
+
for i, match in enumerate(iterator):
|
| 646 |
+
# Take first match, unless it's the word on which we're right now.
|
| 647 |
+
if i == 0 and match.start(1) == 0:
|
| 648 |
+
count += 1
|
| 649 |
+
|
| 650 |
+
if i + 1 == count:
|
| 651 |
+
return -match.start(1) + 1
|
| 652 |
+
except StopIteration:
|
| 653 |
+
pass
|
| 654 |
+
return None
|
| 655 |
+
|
| 656 |
+
def find_next_matching_line(
|
| 657 |
+
self, match_func: Callable[[str], bool], count: int = 1
|
| 658 |
+
) -> int | None:
|
| 659 |
+
"""
|
| 660 |
+
Look downwards for empty lines.
|
| 661 |
+
Return the line index, relative to the current line.
|
| 662 |
+
"""
|
| 663 |
+
result = None
|
| 664 |
+
|
| 665 |
+
for index, line in enumerate(self.lines[self.cursor_position_row + 1 :]):
|
| 666 |
+
if match_func(line):
|
| 667 |
+
result = 1 + index
|
| 668 |
+
count -= 1
|
| 669 |
+
|
| 670 |
+
if count == 0:
|
| 671 |
+
break
|
| 672 |
+
|
| 673 |
+
return result
|
| 674 |
+
|
| 675 |
+
def find_previous_matching_line(
|
| 676 |
+
self, match_func: Callable[[str], bool], count: int = 1
|
| 677 |
+
) -> int | None:
|
| 678 |
+
"""
|
| 679 |
+
Look upwards for empty lines.
|
| 680 |
+
Return the line index, relative to the current line.
|
| 681 |
+
"""
|
| 682 |
+
result = None
|
| 683 |
+
|
| 684 |
+
for index, line in enumerate(self.lines[: self.cursor_position_row][::-1]):
|
| 685 |
+
if match_func(line):
|
| 686 |
+
result = -1 - index
|
| 687 |
+
count -= 1
|
| 688 |
+
|
| 689 |
+
if count == 0:
|
| 690 |
+
break
|
| 691 |
+
|
| 692 |
+
return result
|
| 693 |
+
|
| 694 |
+
def get_cursor_left_position(self, count: int = 1) -> int:
|
| 695 |
+
"""
|
| 696 |
+
Relative position for cursor left.
|
| 697 |
+
"""
|
| 698 |
+
if count < 0:
|
| 699 |
+
return self.get_cursor_right_position(-count)
|
| 700 |
+
|
| 701 |
+
return -min(self.cursor_position_col, count)
|
| 702 |
+
|
| 703 |
+
def get_cursor_right_position(self, count: int = 1) -> int:
|
| 704 |
+
"""
|
| 705 |
+
Relative position for cursor_right.
|
| 706 |
+
"""
|
| 707 |
+
if count < 0:
|
| 708 |
+
return self.get_cursor_left_position(-count)
|
| 709 |
+
|
| 710 |
+
return min(count, len(self.current_line_after_cursor))
|
| 711 |
+
|
| 712 |
+
def get_cursor_up_position(
|
| 713 |
+
self, count: int = 1, preferred_column: int | None = None
|
| 714 |
+
) -> int:
|
| 715 |
+
"""
|
| 716 |
+
Return the relative cursor position (character index) where we would be if the
|
| 717 |
+
user pressed the arrow-up button.
|
| 718 |
+
|
| 719 |
+
:param preferred_column: When given, go to this column instead of
|
| 720 |
+
staying at the current column.
|
| 721 |
+
"""
|
| 722 |
+
assert count >= 1
|
| 723 |
+
column = (
|
| 724 |
+
self.cursor_position_col if preferred_column is None else preferred_column
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
return (
|
| 728 |
+
self.translate_row_col_to_index(
|
| 729 |
+
max(0, self.cursor_position_row - count), column
|
| 730 |
+
)
|
| 731 |
+
- self.cursor_position
|
| 732 |
+
)
|
| 733 |
+
|
| 734 |
+
def get_cursor_down_position(
|
| 735 |
+
self, count: int = 1, preferred_column: int | None = None
|
| 736 |
+
) -> int:
|
| 737 |
+
"""
|
| 738 |
+
Return the relative cursor position (character index) where we would be if the
|
| 739 |
+
user pressed the arrow-down button.
|
| 740 |
+
|
| 741 |
+
:param preferred_column: When given, go to this column instead of
|
| 742 |
+
staying at the current column.
|
| 743 |
+
"""
|
| 744 |
+
assert count >= 1
|
| 745 |
+
column = (
|
| 746 |
+
self.cursor_position_col if preferred_column is None else preferred_column
|
| 747 |
+
)
|
| 748 |
+
|
| 749 |
+
return (
|
| 750 |
+
self.translate_row_col_to_index(self.cursor_position_row + count, column)
|
| 751 |
+
- self.cursor_position
|
| 752 |
+
)
|
| 753 |
+
|
| 754 |
+
def find_enclosing_bracket_right(
|
| 755 |
+
self, left_ch: str, right_ch: str, end_pos: int | None = None
|
| 756 |
+
) -> int | None:
|
| 757 |
+
"""
|
| 758 |
+
Find the right bracket enclosing current position. Return the relative
|
| 759 |
+
position to the cursor position.
|
| 760 |
+
|
| 761 |
+
When `end_pos` is given, don't look past the position.
|
| 762 |
+
"""
|
| 763 |
+
if self.current_char == right_ch:
|
| 764 |
+
return 0
|
| 765 |
+
|
| 766 |
+
if end_pos is None:
|
| 767 |
+
end_pos = len(self.text)
|
| 768 |
+
else:
|
| 769 |
+
end_pos = min(len(self.text), end_pos)
|
| 770 |
+
|
| 771 |
+
stack = 1
|
| 772 |
+
|
| 773 |
+
# Look forward.
|
| 774 |
+
for i in range(self.cursor_position + 1, end_pos):
|
| 775 |
+
c = self.text[i]
|
| 776 |
+
|
| 777 |
+
if c == left_ch:
|
| 778 |
+
stack += 1
|
| 779 |
+
elif c == right_ch:
|
| 780 |
+
stack -= 1
|
| 781 |
+
|
| 782 |
+
if stack == 0:
|
| 783 |
+
return i - self.cursor_position
|
| 784 |
+
|
| 785 |
+
return None
|
| 786 |
+
|
| 787 |
+
def find_enclosing_bracket_left(
|
| 788 |
+
self, left_ch: str, right_ch: str, start_pos: int | None = None
|
| 789 |
+
) -> int | None:
|
| 790 |
+
"""
|
| 791 |
+
Find the left bracket enclosing current position. Return the relative
|
| 792 |
+
position to the cursor position.
|
| 793 |
+
|
| 794 |
+
When `start_pos` is given, don't look past the position.
|
| 795 |
+
"""
|
| 796 |
+
if self.current_char == left_ch:
|
| 797 |
+
return 0
|
| 798 |
+
|
| 799 |
+
if start_pos is None:
|
| 800 |
+
start_pos = 0
|
| 801 |
+
else:
|
| 802 |
+
start_pos = max(0, start_pos)
|
| 803 |
+
|
| 804 |
+
stack = 1
|
| 805 |
+
|
| 806 |
+
# Look backward.
|
| 807 |
+
for i in range(self.cursor_position - 1, start_pos - 1, -1):
|
| 808 |
+
c = self.text[i]
|
| 809 |
+
|
| 810 |
+
if c == right_ch:
|
| 811 |
+
stack += 1
|
| 812 |
+
elif c == left_ch:
|
| 813 |
+
stack -= 1
|
| 814 |
+
|
| 815 |
+
if stack == 0:
|
| 816 |
+
return i - self.cursor_position
|
| 817 |
+
|
| 818 |
+
return None
|
| 819 |
+
|
| 820 |
+
def find_matching_bracket_position(
|
| 821 |
+
self, start_pos: int | None = None, end_pos: int | None = None
|
| 822 |
+
) -> int:
|
| 823 |
+
"""
|
| 824 |
+
Return relative cursor position of matching [, (, { or < bracket.
|
| 825 |
+
|
| 826 |
+
When `start_pos` or `end_pos` are given. Don't look past the positions.
|
| 827 |
+
"""
|
| 828 |
+
|
| 829 |
+
# Look for a match.
|
| 830 |
+
for pair in "()", "[]", "{}", "<>":
|
| 831 |
+
A = pair[0]
|
| 832 |
+
B = pair[1]
|
| 833 |
+
if self.current_char == A:
|
| 834 |
+
return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0
|
| 835 |
+
elif self.current_char == B:
|
| 836 |
+
return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0
|
| 837 |
+
|
| 838 |
+
return 0
|
| 839 |
+
|
| 840 |
+
def get_start_of_document_position(self) -> int:
|
| 841 |
+
"""Relative position for the start of the document."""
|
| 842 |
+
return -self.cursor_position
|
| 843 |
+
|
| 844 |
+
def get_end_of_document_position(self) -> int:
|
| 845 |
+
"""Relative position for the end of the document."""
|
| 846 |
+
return len(self.text) - self.cursor_position
|
| 847 |
+
|
| 848 |
+
def get_start_of_line_position(self, after_whitespace: bool = False) -> int:
|
| 849 |
+
"""Relative position for the start of this line."""
|
| 850 |
+
if after_whitespace:
|
| 851 |
+
current_line = self.current_line
|
| 852 |
+
return (
|
| 853 |
+
len(current_line)
|
| 854 |
+
- len(current_line.lstrip())
|
| 855 |
+
- self.cursor_position_col
|
| 856 |
+
)
|
| 857 |
+
else:
|
| 858 |
+
return -len(self.current_line_before_cursor)
|
| 859 |
+
|
| 860 |
+
def get_end_of_line_position(self) -> int:
|
| 861 |
+
"""Relative position for the end of this line."""
|
| 862 |
+
return len(self.current_line_after_cursor)
|
| 863 |
+
|
| 864 |
+
def last_non_blank_of_current_line_position(self) -> int:
|
| 865 |
+
"""
|
| 866 |
+
Relative position for the last non blank character of this line.
|
| 867 |
+
"""
|
| 868 |
+
return len(self.current_line.rstrip()) - self.cursor_position_col - 1
|
| 869 |
+
|
| 870 |
+
def get_column_cursor_position(self, column: int) -> int:
|
| 871 |
+
"""
|
| 872 |
+
Return the relative cursor position for this column at the current
|
| 873 |
+
line. (It will stay between the boundaries of the line in case of a
|
| 874 |
+
larger number.)
|
| 875 |
+
"""
|
| 876 |
+
line_length = len(self.current_line)
|
| 877 |
+
current_column = self.cursor_position_col
|
| 878 |
+
column = max(0, min(line_length, column))
|
| 879 |
+
|
| 880 |
+
return column - current_column
|
| 881 |
+
|
| 882 |
+
def selection_range(
|
| 883 |
+
self,
|
| 884 |
+
) -> tuple[
|
| 885 |
+
int, int
|
| 886 |
+
]: # XXX: shouldn't this return `None` if there is no selection???
|
| 887 |
+
"""
|
| 888 |
+
Return (from, to) tuple of the selection.
|
| 889 |
+
start and end position are included.
|
| 890 |
+
|
| 891 |
+
This doesn't take the selection type into account. Use
|
| 892 |
+
`selection_ranges` instead.
|
| 893 |
+
"""
|
| 894 |
+
if self.selection:
|
| 895 |
+
from_, to = sorted(
|
| 896 |
+
[self.cursor_position, self.selection.original_cursor_position]
|
| 897 |
+
)
|
| 898 |
+
else:
|
| 899 |
+
from_, to = self.cursor_position, self.cursor_position
|
| 900 |
+
|
| 901 |
+
return from_, to
|
| 902 |
+
|
| 903 |
+
def selection_ranges(self) -> Iterable[tuple[int, int]]:
|
| 904 |
+
"""
|
| 905 |
+
Return a list of `(from, to)` tuples for the selection or none if
|
| 906 |
+
nothing was selected. The upper boundary is not included.
|
| 907 |
+
|
| 908 |
+
This will yield several (from, to) tuples in case of a BLOCK selection.
|
| 909 |
+
This will return zero ranges, like (8,8) for empty lines in a block
|
| 910 |
+
selection.
|
| 911 |
+
"""
|
| 912 |
+
if self.selection:
|
| 913 |
+
from_, to = sorted(
|
| 914 |
+
[self.cursor_position, self.selection.original_cursor_position]
|
| 915 |
+
)
|
| 916 |
+
|
| 917 |
+
if self.selection.type == SelectionType.BLOCK:
|
| 918 |
+
from_line, from_column = self.translate_index_to_position(from_)
|
| 919 |
+
to_line, to_column = self.translate_index_to_position(to)
|
| 920 |
+
from_column, to_column = sorted([from_column, to_column])
|
| 921 |
+
lines = self.lines
|
| 922 |
+
|
| 923 |
+
if vi_mode():
|
| 924 |
+
to_column += 1
|
| 925 |
+
|
| 926 |
+
for l in range(from_line, to_line + 1):
|
| 927 |
+
line_length = len(lines[l])
|
| 928 |
+
|
| 929 |
+
if from_column <= line_length:
|
| 930 |
+
yield (
|
| 931 |
+
self.translate_row_col_to_index(l, from_column),
|
| 932 |
+
self.translate_row_col_to_index(
|
| 933 |
+
l, min(line_length, to_column)
|
| 934 |
+
),
|
| 935 |
+
)
|
| 936 |
+
else:
|
| 937 |
+
# In case of a LINES selection, go to the start/end of the lines.
|
| 938 |
+
if self.selection.type == SelectionType.LINES:
|
| 939 |
+
from_ = max(0, self.text.rfind("\n", 0, from_) + 1)
|
| 940 |
+
|
| 941 |
+
if self.text.find("\n", to) >= 0:
|
| 942 |
+
to = self.text.find("\n", to)
|
| 943 |
+
else:
|
| 944 |
+
to = len(self.text) - 1
|
| 945 |
+
|
| 946 |
+
# In Vi mode, the upper boundary is always included. For Emacs,
|
| 947 |
+
# that's not the case.
|
| 948 |
+
if vi_mode():
|
| 949 |
+
to += 1
|
| 950 |
+
|
| 951 |
+
yield from_, to
|
| 952 |
+
|
| 953 |
+
def selection_range_at_line(self, row: int) -> tuple[int, int] | None:
|
| 954 |
+
"""
|
| 955 |
+
If the selection spans a portion of the given line, return a (from, to) tuple.
|
| 956 |
+
|
| 957 |
+
The returned upper boundary is not included in the selection, so
|
| 958 |
+
`(0, 0)` is an empty selection. `(0, 1)`, is a one character selection.
|
| 959 |
+
|
| 960 |
+
Returns None if the selection doesn't cover this line at all.
|
| 961 |
+
"""
|
| 962 |
+
if self.selection:
|
| 963 |
+
line = self.lines[row]
|
| 964 |
+
|
| 965 |
+
row_start = self.translate_row_col_to_index(row, 0)
|
| 966 |
+
row_end = self.translate_row_col_to_index(row, len(line))
|
| 967 |
+
|
| 968 |
+
from_, to = sorted(
|
| 969 |
+
[self.cursor_position, self.selection.original_cursor_position]
|
| 970 |
+
)
|
| 971 |
+
|
| 972 |
+
# Take the intersection of the current line and the selection.
|
| 973 |
+
intersection_start = max(row_start, from_)
|
| 974 |
+
intersection_end = min(row_end, to)
|
| 975 |
+
|
| 976 |
+
if intersection_start <= intersection_end:
|
| 977 |
+
if self.selection.type == SelectionType.LINES:
|
| 978 |
+
intersection_start = row_start
|
| 979 |
+
intersection_end = row_end
|
| 980 |
+
|
| 981 |
+
elif self.selection.type == SelectionType.BLOCK:
|
| 982 |
+
_, col1 = self.translate_index_to_position(from_)
|
| 983 |
+
_, col2 = self.translate_index_to_position(to)
|
| 984 |
+
col1, col2 = sorted([col1, col2])
|
| 985 |
+
|
| 986 |
+
if col1 > len(line):
|
| 987 |
+
return None # Block selection doesn't cross this line.
|
| 988 |
+
|
| 989 |
+
intersection_start = self.translate_row_col_to_index(row, col1)
|
| 990 |
+
intersection_end = self.translate_row_col_to_index(row, col2)
|
| 991 |
+
|
| 992 |
+
_, from_column = self.translate_index_to_position(intersection_start)
|
| 993 |
+
_, to_column = self.translate_index_to_position(intersection_end)
|
| 994 |
+
|
| 995 |
+
# In Vi mode, the upper boundary is always included. For Emacs
|
| 996 |
+
# mode, that's not the case.
|
| 997 |
+
if vi_mode():
|
| 998 |
+
to_column += 1
|
| 999 |
+
|
| 1000 |
+
return from_column, to_column
|
| 1001 |
+
return None
|
| 1002 |
+
|
| 1003 |
+
def cut_selection(self) -> tuple[Document, ClipboardData]:
|
| 1004 |
+
"""
|
| 1005 |
+
Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the
|
| 1006 |
+
document represents the new document when the selection is cut, and the
|
| 1007 |
+
clipboard data, represents whatever has to be put on the clipboard.
|
| 1008 |
+
"""
|
| 1009 |
+
if self.selection:
|
| 1010 |
+
cut_parts = []
|
| 1011 |
+
remaining_parts = []
|
| 1012 |
+
new_cursor_position = self.cursor_position
|
| 1013 |
+
|
| 1014 |
+
last_to = 0
|
| 1015 |
+
for from_, to in self.selection_ranges():
|
| 1016 |
+
if last_to == 0:
|
| 1017 |
+
new_cursor_position = from_
|
| 1018 |
+
|
| 1019 |
+
remaining_parts.append(self.text[last_to:from_])
|
| 1020 |
+
cut_parts.append(self.text[from_:to])
|
| 1021 |
+
last_to = to
|
| 1022 |
+
|
| 1023 |
+
remaining_parts.append(self.text[last_to:])
|
| 1024 |
+
|
| 1025 |
+
cut_text = "\n".join(cut_parts)
|
| 1026 |
+
remaining_text = "".join(remaining_parts)
|
| 1027 |
+
|
| 1028 |
+
# In case of a LINES selection, don't include the trailing newline.
|
| 1029 |
+
if self.selection.type == SelectionType.LINES and cut_text.endswith("\n"):
|
| 1030 |
+
cut_text = cut_text[:-1]
|
| 1031 |
+
|
| 1032 |
+
return (
|
| 1033 |
+
Document(text=remaining_text, cursor_position=new_cursor_position),
|
| 1034 |
+
ClipboardData(cut_text, self.selection.type),
|
| 1035 |
+
)
|
| 1036 |
+
else:
|
| 1037 |
+
return self, ClipboardData("")
|
| 1038 |
+
|
| 1039 |
+
def paste_clipboard_data(
|
| 1040 |
+
self,
|
| 1041 |
+
data: ClipboardData,
|
| 1042 |
+
paste_mode: PasteMode = PasteMode.EMACS,
|
| 1043 |
+
count: int = 1,
|
| 1044 |
+
) -> Document:
|
| 1045 |
+
"""
|
| 1046 |
+
Return a new :class:`.Document` instance which contains the result if
|
| 1047 |
+
we would paste this data at the current cursor position.
|
| 1048 |
+
|
| 1049 |
+
:param paste_mode: Where to paste. (Before/after/emacs.)
|
| 1050 |
+
:param count: When >1, Paste multiple times.
|
| 1051 |
+
"""
|
| 1052 |
+
before = paste_mode == PasteMode.VI_BEFORE
|
| 1053 |
+
after = paste_mode == PasteMode.VI_AFTER
|
| 1054 |
+
|
| 1055 |
+
if data.type == SelectionType.CHARACTERS:
|
| 1056 |
+
if after:
|
| 1057 |
+
new_text = (
|
| 1058 |
+
self.text[: self.cursor_position + 1]
|
| 1059 |
+
+ data.text * count
|
| 1060 |
+
+ self.text[self.cursor_position + 1 :]
|
| 1061 |
+
)
|
| 1062 |
+
else:
|
| 1063 |
+
new_text = (
|
| 1064 |
+
self.text_before_cursor + data.text * count + self.text_after_cursor
|
| 1065 |
+
)
|
| 1066 |
+
|
| 1067 |
+
new_cursor_position = self.cursor_position + len(data.text) * count
|
| 1068 |
+
if before:
|
| 1069 |
+
new_cursor_position -= 1
|
| 1070 |
+
|
| 1071 |
+
elif data.type == SelectionType.LINES:
|
| 1072 |
+
l = self.cursor_position_row
|
| 1073 |
+
if before:
|
| 1074 |
+
lines = self.lines[:l] + [data.text] * count + self.lines[l:]
|
| 1075 |
+
new_text = "\n".join(lines)
|
| 1076 |
+
new_cursor_position = len("".join(self.lines[:l])) + l
|
| 1077 |
+
else:
|
| 1078 |
+
lines = self.lines[: l + 1] + [data.text] * count + self.lines[l + 1 :]
|
| 1079 |
+
new_cursor_position = len("".join(self.lines[: l + 1])) + l + 1
|
| 1080 |
+
new_text = "\n".join(lines)
|
| 1081 |
+
|
| 1082 |
+
elif data.type == SelectionType.BLOCK:
|
| 1083 |
+
lines = self.lines[:]
|
| 1084 |
+
start_line = self.cursor_position_row
|
| 1085 |
+
start_column = self.cursor_position_col + (0 if before else 1)
|
| 1086 |
+
|
| 1087 |
+
for i, line in enumerate(data.text.split("\n")):
|
| 1088 |
+
index = i + start_line
|
| 1089 |
+
if index >= len(lines):
|
| 1090 |
+
lines.append("")
|
| 1091 |
+
|
| 1092 |
+
lines[index] = lines[index].ljust(start_column)
|
| 1093 |
+
lines[index] = (
|
| 1094 |
+
lines[index][:start_column]
|
| 1095 |
+
+ line * count
|
| 1096 |
+
+ lines[index][start_column:]
|
| 1097 |
+
)
|
| 1098 |
+
|
| 1099 |
+
new_text = "\n".join(lines)
|
| 1100 |
+
new_cursor_position = self.cursor_position + (0 if before else 1)
|
| 1101 |
+
|
| 1102 |
+
return Document(text=new_text, cursor_position=new_cursor_position)
|
| 1103 |
+
|
| 1104 |
+
def empty_line_count_at_the_end(self) -> int:
|
| 1105 |
+
"""
|
| 1106 |
+
Return number of empty lines at the end of the document.
|
| 1107 |
+
"""
|
| 1108 |
+
count = 0
|
| 1109 |
+
for line in self.lines[::-1]:
|
| 1110 |
+
if not line or line.isspace():
|
| 1111 |
+
count += 1
|
| 1112 |
+
else:
|
| 1113 |
+
break
|
| 1114 |
+
|
| 1115 |
+
return count
|
| 1116 |
+
|
| 1117 |
+
def start_of_paragraph(self, count: int = 1, before: bool = False) -> int:
|
| 1118 |
+
"""
|
| 1119 |
+
Return the start of the current paragraph. (Relative cursor position.)
|
| 1120 |
+
"""
|
| 1121 |
+
|
| 1122 |
+
def match_func(text: str) -> bool:
|
| 1123 |
+
return not text or text.isspace()
|
| 1124 |
+
|
| 1125 |
+
line_index = self.find_previous_matching_line(
|
| 1126 |
+
match_func=match_func, count=count
|
| 1127 |
+
)
|
| 1128 |
+
|
| 1129 |
+
if line_index:
|
| 1130 |
+
add = 0 if before else 1
|
| 1131 |
+
return min(0, self.get_cursor_up_position(count=-line_index) + add)
|
| 1132 |
+
else:
|
| 1133 |
+
return -self.cursor_position
|
| 1134 |
+
|
| 1135 |
+
def end_of_paragraph(self, count: int = 1, after: bool = False) -> int:
|
| 1136 |
+
"""
|
| 1137 |
+
Return the end of the current paragraph. (Relative cursor position.)
|
| 1138 |
+
"""
|
| 1139 |
+
|
| 1140 |
+
def match_func(text: str) -> bool:
|
| 1141 |
+
return not text or text.isspace()
|
| 1142 |
+
|
| 1143 |
+
line_index = self.find_next_matching_line(match_func=match_func, count=count)
|
| 1144 |
+
|
| 1145 |
+
if line_index:
|
| 1146 |
+
add = 0 if after else 1
|
| 1147 |
+
return max(0, self.get_cursor_down_position(count=line_index) - add)
|
| 1148 |
+
else:
|
| 1149 |
+
return len(self.text_after_cursor)
|
| 1150 |
+
|
| 1151 |
+
# Modifiers.
|
| 1152 |
+
|
| 1153 |
+
def insert_after(self, text: str) -> Document:
|
| 1154 |
+
"""
|
| 1155 |
+
Create a new document, with this text inserted after the buffer.
|
| 1156 |
+
It keeps selection ranges and cursor position in sync.
|
| 1157 |
+
"""
|
| 1158 |
+
return Document(
|
| 1159 |
+
text=self.text + text,
|
| 1160 |
+
cursor_position=self.cursor_position,
|
| 1161 |
+
selection=self.selection,
|
| 1162 |
+
)
|
| 1163 |
+
|
| 1164 |
+
def insert_before(self, text: str) -> Document:
|
| 1165 |
+
"""
|
| 1166 |
+
Create a new document, with this text inserted before the buffer.
|
| 1167 |
+
It keeps selection ranges and cursor position in sync.
|
| 1168 |
+
"""
|
| 1169 |
+
selection_state = self.selection
|
| 1170 |
+
|
| 1171 |
+
if selection_state:
|
| 1172 |
+
selection_state = SelectionState(
|
| 1173 |
+
original_cursor_position=selection_state.original_cursor_position
|
| 1174 |
+
+ len(text),
|
| 1175 |
+
type=selection_state.type,
|
| 1176 |
+
)
|
| 1177 |
+
|
| 1178 |
+
return Document(
|
| 1179 |
+
text=text + self.text,
|
| 1180 |
+
cursor_position=self.cursor_position + len(text),
|
| 1181 |
+
selection=selection_state,
|
| 1182 |
+
)
|
.venv/Lib/site-packages/prompt_toolkit/enums.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from enum import Enum
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class EditingMode(Enum):
|
| 7 |
+
# The set of key bindings that is active.
|
| 8 |
+
VI = "VI"
|
| 9 |
+
EMACS = "EMACS"
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
#: Name of the search buffer.
|
| 13 |
+
SEARCH_BUFFER = "SEARCH_BUFFER"
|
| 14 |
+
|
| 15 |
+
#: Name of the default buffer.
|
| 16 |
+
DEFAULT_BUFFER = "DEFAULT_BUFFER"
|
| 17 |
+
|
| 18 |
+
#: Name of the system buffer.
|
| 19 |
+
SYSTEM_BUFFER = "SYSTEM_BUFFER"
|
.venv/Lib/site-packages/prompt_toolkit/history.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Implementations for the history of a `Buffer`.
|
| 3 |
+
|
| 4 |
+
NOTE: There is no `DynamicHistory`:
|
| 5 |
+
This doesn't work well, because the `Buffer` needs to be able to attach
|
| 6 |
+
an event handler to the event when a history entry is loaded. This
|
| 7 |
+
loading can be done asynchronously and making the history swappable would
|
| 8 |
+
probably break this.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import datetime
|
| 14 |
+
import os
|
| 15 |
+
import threading
|
| 16 |
+
from abc import ABCMeta, abstractmethod
|
| 17 |
+
from asyncio import get_running_loop
|
| 18 |
+
from typing import AsyncGenerator, Iterable, Sequence, Union
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
"History",
|
| 22 |
+
"ThreadedHistory",
|
| 23 |
+
"DummyHistory",
|
| 24 |
+
"FileHistory",
|
| 25 |
+
"InMemoryHistory",
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class History(metaclass=ABCMeta):
|
| 30 |
+
"""
|
| 31 |
+
Base ``History`` class.
|
| 32 |
+
|
| 33 |
+
This also includes abstract methods for loading/storing history.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(self) -> None:
|
| 37 |
+
# In memory storage for strings.
|
| 38 |
+
self._loaded = False
|
| 39 |
+
|
| 40 |
+
# History that's loaded already, in reverse order. Latest, most recent
|
| 41 |
+
# item first.
|
| 42 |
+
self._loaded_strings: list[str] = []
|
| 43 |
+
|
| 44 |
+
#
|
| 45 |
+
# Methods expected by `Buffer`.
|
| 46 |
+
#
|
| 47 |
+
|
| 48 |
+
async def load(self) -> AsyncGenerator[str, None]:
|
| 49 |
+
"""
|
| 50 |
+
Load the history and yield all the entries in reverse order (latest,
|
| 51 |
+
most recent history entry first).
|
| 52 |
+
|
| 53 |
+
This method can be called multiple times from the `Buffer` to
|
| 54 |
+
repopulate the history when prompting for a new input. So we are
|
| 55 |
+
responsible here for both caching, and making sure that strings that
|
| 56 |
+
were were appended to the history will be incorporated next time this
|
| 57 |
+
method is called.
|
| 58 |
+
"""
|
| 59 |
+
if not self._loaded:
|
| 60 |
+
self._loaded_strings = list(self.load_history_strings())
|
| 61 |
+
self._loaded = True
|
| 62 |
+
|
| 63 |
+
for item in self._loaded_strings:
|
| 64 |
+
yield item
|
| 65 |
+
|
| 66 |
+
def get_strings(self) -> list[str]:
|
| 67 |
+
"""
|
| 68 |
+
Get the strings from the history that are loaded so far.
|
| 69 |
+
(In order. Oldest item first.)
|
| 70 |
+
"""
|
| 71 |
+
return self._loaded_strings[::-1]
|
| 72 |
+
|
| 73 |
+
def append_string(self, string: str) -> None:
|
| 74 |
+
"Add string to the history."
|
| 75 |
+
self._loaded_strings.insert(0, string)
|
| 76 |
+
self.store_string(string)
|
| 77 |
+
|
| 78 |
+
#
|
| 79 |
+
# Implementation for specific backends.
|
| 80 |
+
#
|
| 81 |
+
|
| 82 |
+
@abstractmethod
|
| 83 |
+
def load_history_strings(self) -> Iterable[str]:
|
| 84 |
+
"""
|
| 85 |
+
This should be a generator that yields `str` instances.
|
| 86 |
+
|
| 87 |
+
It should yield the most recent items first, because they are the most
|
| 88 |
+
important. (The history can already be used, even when it's only
|
| 89 |
+
partially loaded.)
|
| 90 |
+
"""
|
| 91 |
+
while False:
|
| 92 |
+
yield
|
| 93 |
+
|
| 94 |
+
@abstractmethod
|
| 95 |
+
def store_string(self, string: str) -> None:
|
| 96 |
+
"""
|
| 97 |
+
Store the string in persistent storage.
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
class ThreadedHistory(History):
|
| 102 |
+
"""
|
| 103 |
+
Wrapper around `History` implementations that run the `load()` generator in
|
| 104 |
+
a thread.
|
| 105 |
+
|
| 106 |
+
Use this to increase the start-up time of prompt_toolkit applications.
|
| 107 |
+
History entries are available as soon as they are loaded. We don't have to
|
| 108 |
+
wait for everything to be loaded.
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
def __init__(self, history: History) -> None:
|
| 112 |
+
super().__init__()
|
| 113 |
+
|
| 114 |
+
self.history = history
|
| 115 |
+
|
| 116 |
+
self._load_thread: threading.Thread | None = None
|
| 117 |
+
|
| 118 |
+
# Lock for accessing/manipulating `_loaded_strings` and `_loaded`
|
| 119 |
+
# together in a consistent state.
|
| 120 |
+
self._lock = threading.Lock()
|
| 121 |
+
|
| 122 |
+
# Events created by each `load()` call. Used to wait for new history
|
| 123 |
+
# entries from the loader thread.
|
| 124 |
+
self._string_load_events: list[threading.Event] = []
|
| 125 |
+
|
| 126 |
+
async def load(self) -> AsyncGenerator[str, None]:
|
| 127 |
+
"""
|
| 128 |
+
Like `History.load(), but call `self.load_history_strings()` in a
|
| 129 |
+
background thread.
|
| 130 |
+
"""
|
| 131 |
+
# Start the load thread, if this is called for the first time.
|
| 132 |
+
if not self._load_thread:
|
| 133 |
+
self._load_thread = threading.Thread(
|
| 134 |
+
target=self._in_load_thread,
|
| 135 |
+
daemon=True,
|
| 136 |
+
)
|
| 137 |
+
self._load_thread.start()
|
| 138 |
+
|
| 139 |
+
# Consume the `_loaded_strings` list, using asyncio.
|
| 140 |
+
loop = get_running_loop()
|
| 141 |
+
|
| 142 |
+
# Create threading Event so that we can wait for new items.
|
| 143 |
+
event = threading.Event()
|
| 144 |
+
event.set()
|
| 145 |
+
self._string_load_events.append(event)
|
| 146 |
+
|
| 147 |
+
items_yielded = 0
|
| 148 |
+
|
| 149 |
+
try:
|
| 150 |
+
while True:
|
| 151 |
+
# Wait for new items to be available.
|
| 152 |
+
# (Use a timeout, because the executor thread is not a daemon
|
| 153 |
+
# thread. The "slow-history.py" example would otherwise hang if
|
| 154 |
+
# Control-C is pressed before the history is fully loaded,
|
| 155 |
+
# because there's still this non-daemon executor thread waiting
|
| 156 |
+
# for this event.)
|
| 157 |
+
got_timeout = await loop.run_in_executor(
|
| 158 |
+
None, lambda: event.wait(timeout=0.5)
|
| 159 |
+
)
|
| 160 |
+
if not got_timeout:
|
| 161 |
+
continue
|
| 162 |
+
|
| 163 |
+
# Read new items (in lock).
|
| 164 |
+
def in_executor() -> tuple[list[str], bool]:
|
| 165 |
+
with self._lock:
|
| 166 |
+
new_items = self._loaded_strings[items_yielded:]
|
| 167 |
+
done = self._loaded
|
| 168 |
+
event.clear()
|
| 169 |
+
return new_items, done
|
| 170 |
+
|
| 171 |
+
new_items, done = await loop.run_in_executor(None, in_executor)
|
| 172 |
+
|
| 173 |
+
items_yielded += len(new_items)
|
| 174 |
+
|
| 175 |
+
for item in new_items:
|
| 176 |
+
yield item
|
| 177 |
+
|
| 178 |
+
if done:
|
| 179 |
+
break
|
| 180 |
+
finally:
|
| 181 |
+
self._string_load_events.remove(event)
|
| 182 |
+
|
| 183 |
+
def _in_load_thread(self) -> None:
|
| 184 |
+
try:
|
| 185 |
+
# Start with an empty list. In case `append_string()` was called
|
| 186 |
+
# before `load()` happened. Then `.store_string()` will have
|
| 187 |
+
# written these entries back to disk and we will reload it.
|
| 188 |
+
self._loaded_strings = []
|
| 189 |
+
|
| 190 |
+
for item in self.history.load_history_strings():
|
| 191 |
+
with self._lock:
|
| 192 |
+
self._loaded_strings.append(item)
|
| 193 |
+
|
| 194 |
+
for event in self._string_load_events:
|
| 195 |
+
event.set()
|
| 196 |
+
finally:
|
| 197 |
+
with self._lock:
|
| 198 |
+
self._loaded = True
|
| 199 |
+
for event in self._string_load_events:
|
| 200 |
+
event.set()
|
| 201 |
+
|
| 202 |
+
def append_string(self, string: str) -> None:
|
| 203 |
+
with self._lock:
|
| 204 |
+
self._loaded_strings.insert(0, string)
|
| 205 |
+
self.store_string(string)
|
| 206 |
+
|
| 207 |
+
# All of the following are proxied to `self.history`.
|
| 208 |
+
|
| 209 |
+
def load_history_strings(self) -> Iterable[str]:
|
| 210 |
+
return self.history.load_history_strings()
|
| 211 |
+
|
| 212 |
+
def store_string(self, string: str) -> None:
|
| 213 |
+
self.history.store_string(string)
|
| 214 |
+
|
| 215 |
+
def __repr__(self) -> str:
|
| 216 |
+
return f"ThreadedHistory({self.history!r})"
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class InMemoryHistory(History):
|
| 220 |
+
"""
|
| 221 |
+
:class:`.History` class that keeps a list of all strings in memory.
|
| 222 |
+
|
| 223 |
+
In order to prepopulate the history, it's possible to call either
|
| 224 |
+
`append_string` for all items or pass a list of strings to `__init__` here.
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
def __init__(self, history_strings: Sequence[str] | None = None) -> None:
|
| 228 |
+
super().__init__()
|
| 229 |
+
# Emulating disk storage.
|
| 230 |
+
if history_strings is None:
|
| 231 |
+
self._storage = []
|
| 232 |
+
else:
|
| 233 |
+
self._storage = list(history_strings)
|
| 234 |
+
|
| 235 |
+
def load_history_strings(self) -> Iterable[str]:
|
| 236 |
+
yield from self._storage[::-1]
|
| 237 |
+
|
| 238 |
+
def store_string(self, string: str) -> None:
|
| 239 |
+
self._storage.append(string)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
class DummyHistory(History):
|
| 243 |
+
"""
|
| 244 |
+
:class:`.History` object that doesn't remember anything.
|
| 245 |
+
"""
|
| 246 |
+
|
| 247 |
+
def load_history_strings(self) -> Iterable[str]:
|
| 248 |
+
return []
|
| 249 |
+
|
| 250 |
+
def store_string(self, string: str) -> None:
|
| 251 |
+
pass
|
| 252 |
+
|
| 253 |
+
def append_string(self, string: str) -> None:
|
| 254 |
+
# Don't remember this.
|
| 255 |
+
pass
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
_StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
class FileHistory(History):
|
| 262 |
+
"""
|
| 263 |
+
:class:`.History` class that stores all strings in a file.
|
| 264 |
+
"""
|
| 265 |
+
|
| 266 |
+
def __init__(self, filename: _StrOrBytesPath) -> None:
|
| 267 |
+
self.filename = filename
|
| 268 |
+
super().__init__()
|
| 269 |
+
|
| 270 |
+
def load_history_strings(self) -> Iterable[str]:
|
| 271 |
+
strings: list[str] = []
|
| 272 |
+
lines: list[str] = []
|
| 273 |
+
|
| 274 |
+
def add() -> None:
|
| 275 |
+
if lines:
|
| 276 |
+
# Join and drop trailing newline.
|
| 277 |
+
string = "".join(lines)[:-1]
|
| 278 |
+
|
| 279 |
+
strings.append(string)
|
| 280 |
+
|
| 281 |
+
if os.path.exists(self.filename):
|
| 282 |
+
with open(self.filename, "rb") as f:
|
| 283 |
+
for line_bytes in f:
|
| 284 |
+
line = line_bytes.decode("utf-8", errors="replace")
|
| 285 |
+
|
| 286 |
+
if line.startswith("+"):
|
| 287 |
+
lines.append(line[1:])
|
| 288 |
+
else:
|
| 289 |
+
add()
|
| 290 |
+
lines = []
|
| 291 |
+
|
| 292 |
+
add()
|
| 293 |
+
|
| 294 |
+
# Reverse the order, because newest items have to go first.
|
| 295 |
+
return reversed(strings)
|
| 296 |
+
|
| 297 |
+
def store_string(self, string: str) -> None:
|
| 298 |
+
# Save to file.
|
| 299 |
+
with open(self.filename, "ab") as f:
|
| 300 |
+
|
| 301 |
+
def write(t: str) -> None:
|
| 302 |
+
f.write(t.encode("utf-8"))
|
| 303 |
+
|
| 304 |
+
write(f"\n# {datetime.datetime.now()}\n")
|
| 305 |
+
for line in string.split("\n"):
|
| 306 |
+
write(f"+{line}\n")
|
.venv/Lib/site-packages/prompt_toolkit/keys.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from enum import Enum
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
"Keys",
|
| 7 |
+
"ALL_KEYS",
|
| 8 |
+
]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Keys(str, Enum):
|
| 12 |
+
"""
|
| 13 |
+
List of keys for use in key bindings.
|
| 14 |
+
|
| 15 |
+
Note that this is an "StrEnum", all values can be compared against
|
| 16 |
+
strings.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
value: str
|
| 20 |
+
|
| 21 |
+
Escape = "escape" # Also Control-[
|
| 22 |
+
ShiftEscape = "s-escape"
|
| 23 |
+
|
| 24 |
+
ControlAt = "c-@" # Also Control-Space.
|
| 25 |
+
|
| 26 |
+
ControlA = "c-a"
|
| 27 |
+
ControlB = "c-b"
|
| 28 |
+
ControlC = "c-c"
|
| 29 |
+
ControlD = "c-d"
|
| 30 |
+
ControlE = "c-e"
|
| 31 |
+
ControlF = "c-f"
|
| 32 |
+
ControlG = "c-g"
|
| 33 |
+
ControlH = "c-h"
|
| 34 |
+
ControlI = "c-i" # Tab
|
| 35 |
+
ControlJ = "c-j" # Newline
|
| 36 |
+
ControlK = "c-k"
|
| 37 |
+
ControlL = "c-l"
|
| 38 |
+
ControlM = "c-m" # Carriage return
|
| 39 |
+
ControlN = "c-n"
|
| 40 |
+
ControlO = "c-o"
|
| 41 |
+
ControlP = "c-p"
|
| 42 |
+
ControlQ = "c-q"
|
| 43 |
+
ControlR = "c-r"
|
| 44 |
+
ControlS = "c-s"
|
| 45 |
+
ControlT = "c-t"
|
| 46 |
+
ControlU = "c-u"
|
| 47 |
+
ControlV = "c-v"
|
| 48 |
+
ControlW = "c-w"
|
| 49 |
+
ControlX = "c-x"
|
| 50 |
+
ControlY = "c-y"
|
| 51 |
+
ControlZ = "c-z"
|
| 52 |
+
|
| 53 |
+
Control1 = "c-1"
|
| 54 |
+
Control2 = "c-2"
|
| 55 |
+
Control3 = "c-3"
|
| 56 |
+
Control4 = "c-4"
|
| 57 |
+
Control5 = "c-5"
|
| 58 |
+
Control6 = "c-6"
|
| 59 |
+
Control7 = "c-7"
|
| 60 |
+
Control8 = "c-8"
|
| 61 |
+
Control9 = "c-9"
|
| 62 |
+
Control0 = "c-0"
|
| 63 |
+
|
| 64 |
+
ControlShift1 = "c-s-1"
|
| 65 |
+
ControlShift2 = "c-s-2"
|
| 66 |
+
ControlShift3 = "c-s-3"
|
| 67 |
+
ControlShift4 = "c-s-4"
|
| 68 |
+
ControlShift5 = "c-s-5"
|
| 69 |
+
ControlShift6 = "c-s-6"
|
| 70 |
+
ControlShift7 = "c-s-7"
|
| 71 |
+
ControlShift8 = "c-s-8"
|
| 72 |
+
ControlShift9 = "c-s-9"
|
| 73 |
+
ControlShift0 = "c-s-0"
|
| 74 |
+
|
| 75 |
+
ControlBackslash = "c-\\"
|
| 76 |
+
ControlSquareClose = "c-]"
|
| 77 |
+
ControlCircumflex = "c-^"
|
| 78 |
+
ControlUnderscore = "c-_"
|
| 79 |
+
|
| 80 |
+
Left = "left"
|
| 81 |
+
Right = "right"
|
| 82 |
+
Up = "up"
|
| 83 |
+
Down = "down"
|
| 84 |
+
Home = "home"
|
| 85 |
+
End = "end"
|
| 86 |
+
Insert = "insert"
|
| 87 |
+
Delete = "delete"
|
| 88 |
+
PageUp = "pageup"
|
| 89 |
+
PageDown = "pagedown"
|
| 90 |
+
|
| 91 |
+
ControlLeft = "c-left"
|
| 92 |
+
ControlRight = "c-right"
|
| 93 |
+
ControlUp = "c-up"
|
| 94 |
+
ControlDown = "c-down"
|
| 95 |
+
ControlHome = "c-home"
|
| 96 |
+
ControlEnd = "c-end"
|
| 97 |
+
ControlInsert = "c-insert"
|
| 98 |
+
ControlDelete = "c-delete"
|
| 99 |
+
ControlPageUp = "c-pageup"
|
| 100 |
+
ControlPageDown = "c-pagedown"
|
| 101 |
+
|
| 102 |
+
ShiftLeft = "s-left"
|
| 103 |
+
ShiftRight = "s-right"
|
| 104 |
+
ShiftUp = "s-up"
|
| 105 |
+
ShiftDown = "s-down"
|
| 106 |
+
ShiftHome = "s-home"
|
| 107 |
+
ShiftEnd = "s-end"
|
| 108 |
+
ShiftInsert = "s-insert"
|
| 109 |
+
ShiftDelete = "s-delete"
|
| 110 |
+
ShiftPageUp = "s-pageup"
|
| 111 |
+
ShiftPageDown = "s-pagedown"
|
| 112 |
+
|
| 113 |
+
ControlShiftLeft = "c-s-left"
|
| 114 |
+
ControlShiftRight = "c-s-right"
|
| 115 |
+
ControlShiftUp = "c-s-up"
|
| 116 |
+
ControlShiftDown = "c-s-down"
|
| 117 |
+
ControlShiftHome = "c-s-home"
|
| 118 |
+
ControlShiftEnd = "c-s-end"
|
| 119 |
+
ControlShiftInsert = "c-s-insert"
|
| 120 |
+
ControlShiftDelete = "c-s-delete"
|
| 121 |
+
ControlShiftPageUp = "c-s-pageup"
|
| 122 |
+
ControlShiftPageDown = "c-s-pagedown"
|
| 123 |
+
|
| 124 |
+
BackTab = "s-tab" # shift + tab
|
| 125 |
+
|
| 126 |
+
F1 = "f1"
|
| 127 |
+
F2 = "f2"
|
| 128 |
+
F3 = "f3"
|
| 129 |
+
F4 = "f4"
|
| 130 |
+
F5 = "f5"
|
| 131 |
+
F6 = "f6"
|
| 132 |
+
F7 = "f7"
|
| 133 |
+
F8 = "f8"
|
| 134 |
+
F9 = "f9"
|
| 135 |
+
F10 = "f10"
|
| 136 |
+
F11 = "f11"
|
| 137 |
+
F12 = "f12"
|
| 138 |
+
F13 = "f13"
|
| 139 |
+
F14 = "f14"
|
| 140 |
+
F15 = "f15"
|
| 141 |
+
F16 = "f16"
|
| 142 |
+
F17 = "f17"
|
| 143 |
+
F18 = "f18"
|
| 144 |
+
F19 = "f19"
|
| 145 |
+
F20 = "f20"
|
| 146 |
+
F21 = "f21"
|
| 147 |
+
F22 = "f22"
|
| 148 |
+
F23 = "f23"
|
| 149 |
+
F24 = "f24"
|
| 150 |
+
|
| 151 |
+
ControlF1 = "c-f1"
|
| 152 |
+
ControlF2 = "c-f2"
|
| 153 |
+
ControlF3 = "c-f3"
|
| 154 |
+
ControlF4 = "c-f4"
|
| 155 |
+
ControlF5 = "c-f5"
|
| 156 |
+
ControlF6 = "c-f6"
|
| 157 |
+
ControlF7 = "c-f7"
|
| 158 |
+
ControlF8 = "c-f8"
|
| 159 |
+
ControlF9 = "c-f9"
|
| 160 |
+
ControlF10 = "c-f10"
|
| 161 |
+
ControlF11 = "c-f11"
|
| 162 |
+
ControlF12 = "c-f12"
|
| 163 |
+
ControlF13 = "c-f13"
|
| 164 |
+
ControlF14 = "c-f14"
|
| 165 |
+
ControlF15 = "c-f15"
|
| 166 |
+
ControlF16 = "c-f16"
|
| 167 |
+
ControlF17 = "c-f17"
|
| 168 |
+
ControlF18 = "c-f18"
|
| 169 |
+
ControlF19 = "c-f19"
|
| 170 |
+
ControlF20 = "c-f20"
|
| 171 |
+
ControlF21 = "c-f21"
|
| 172 |
+
ControlF22 = "c-f22"
|
| 173 |
+
ControlF23 = "c-f23"
|
| 174 |
+
ControlF24 = "c-f24"
|
| 175 |
+
|
| 176 |
+
# Matches any key.
|
| 177 |
+
Any = "<any>"
|
| 178 |
+
|
| 179 |
+
# Special.
|
| 180 |
+
ScrollUp = "<scroll-up>"
|
| 181 |
+
ScrollDown = "<scroll-down>"
|
| 182 |
+
|
| 183 |
+
CPRResponse = "<cursor-position-response>"
|
| 184 |
+
Vt100MouseEvent = "<vt100-mouse-event>"
|
| 185 |
+
WindowsMouseEvent = "<windows-mouse-event>"
|
| 186 |
+
BracketedPaste = "<bracketed-paste>"
|
| 187 |
+
|
| 188 |
+
SIGINT = "<sigint>"
|
| 189 |
+
|
| 190 |
+
# For internal use: key which is ignored.
|
| 191 |
+
# (The key binding for this key should not do anything.)
|
| 192 |
+
Ignore = "<ignore>"
|
| 193 |
+
|
| 194 |
+
# Some 'Key' aliases (for backwards-compatibility).
|
| 195 |
+
ControlSpace = ControlAt
|
| 196 |
+
Tab = ControlI
|
| 197 |
+
Enter = ControlM
|
| 198 |
+
Backspace = ControlH
|
| 199 |
+
|
| 200 |
+
# ShiftControl was renamed to ControlShift in
|
| 201 |
+
# 888fcb6fa4efea0de8333177e1bbc792f3ff3c24 (20 Feb 2020).
|
| 202 |
+
ShiftControlLeft = ControlShiftLeft
|
| 203 |
+
ShiftControlRight = ControlShiftRight
|
| 204 |
+
ShiftControlHome = ControlShiftHome
|
| 205 |
+
ShiftControlEnd = ControlShiftEnd
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
ALL_KEYS: list[str] = [k.value for k in Keys]
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# Aliases.
|
| 212 |
+
KEY_ALIASES: dict[str, str] = {
|
| 213 |
+
"backspace": "c-h",
|
| 214 |
+
"c-space": "c-@",
|
| 215 |
+
"enter": "c-m",
|
| 216 |
+
"tab": "c-i",
|
| 217 |
+
# ShiftControl was renamed to ControlShift.
|
| 218 |
+
"s-c-left": "c-s-left",
|
| 219 |
+
"s-c-right": "c-s-right",
|
| 220 |
+
"s-c-home": "c-s-home",
|
| 221 |
+
"s-c-end": "c-s-end",
|
| 222 |
+
}
|
.venv/Lib/site-packages/prompt_toolkit/log.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Logging configuration.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"logger",
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__package__)
|
.venv/Lib/site-packages/prompt_toolkit/mouse_events.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Mouse events.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
How it works
|
| 6 |
+
------------
|
| 7 |
+
|
| 8 |
+
The renderer has a 2 dimensional grid of mouse event handlers.
|
| 9 |
+
(`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the
|
| 10 |
+
`Window` class will make sure that this grid will also be filled with
|
| 11 |
+
callbacks. For vt100 terminals, mouse events are received through stdin, just
|
| 12 |
+
like any other key press. There is a handler among the key bindings that
|
| 13 |
+
catches these events and forwards them to such a mouse event handler. It passes
|
| 14 |
+
through the `Window` class where the coordinates are translated from absolute
|
| 15 |
+
coordinates to coordinates relative to the user control, and there
|
| 16 |
+
`UIControl.mouse_handler` is called.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from enum import Enum
|
| 22 |
+
|
| 23 |
+
from .data_structures import Point
|
| 24 |
+
|
| 25 |
+
__all__ = ["MouseEventType", "MouseButton", "MouseModifier", "MouseEvent"]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class MouseEventType(Enum):
|
| 29 |
+
# Mouse up: This same event type is fired for all three events: left mouse
|
| 30 |
+
# up, right mouse up, or middle mouse up
|
| 31 |
+
MOUSE_UP = "MOUSE_UP"
|
| 32 |
+
|
| 33 |
+
# Mouse down: This implicitly refers to the left mouse down (this event is
|
| 34 |
+
# not fired upon pressing the middle or right mouse buttons).
|
| 35 |
+
MOUSE_DOWN = "MOUSE_DOWN"
|
| 36 |
+
|
| 37 |
+
SCROLL_UP = "SCROLL_UP"
|
| 38 |
+
SCROLL_DOWN = "SCROLL_DOWN"
|
| 39 |
+
|
| 40 |
+
# Triggered when the left mouse button is held down, and the mouse moves
|
| 41 |
+
MOUSE_MOVE = "MOUSE_MOVE"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class MouseButton(Enum):
|
| 45 |
+
LEFT = "LEFT"
|
| 46 |
+
MIDDLE = "MIDDLE"
|
| 47 |
+
RIGHT = "RIGHT"
|
| 48 |
+
|
| 49 |
+
# When we're scrolling, or just moving the mouse and not pressing a button.
|
| 50 |
+
NONE = "NONE"
|
| 51 |
+
|
| 52 |
+
# This is for when we don't know which mouse button was pressed, but we do
|
| 53 |
+
# know that one has been pressed during this mouse event (as opposed to
|
| 54 |
+
# scrolling, for example)
|
| 55 |
+
UNKNOWN = "UNKNOWN"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class MouseModifier(Enum):
|
| 59 |
+
SHIFT = "SHIFT"
|
| 60 |
+
ALT = "ALT"
|
| 61 |
+
CONTROL = "CONTROL"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class MouseEvent:
|
| 65 |
+
"""
|
| 66 |
+
Mouse event, sent to `UIControl.mouse_handler`.
|
| 67 |
+
|
| 68 |
+
:param position: `Point` instance.
|
| 69 |
+
:param event_type: `MouseEventType`.
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
def __init__(
|
| 73 |
+
self,
|
| 74 |
+
position: Point,
|
| 75 |
+
event_type: MouseEventType,
|
| 76 |
+
button: MouseButton,
|
| 77 |
+
modifiers: frozenset[MouseModifier],
|
| 78 |
+
) -> None:
|
| 79 |
+
self.position = position
|
| 80 |
+
self.event_type = event_type
|
| 81 |
+
self.button = button
|
| 82 |
+
self.modifiers = modifiers
|
| 83 |
+
|
| 84 |
+
def __repr__(self) -> str:
|
| 85 |
+
return f"MouseEvent({self.position!r},{self.event_type!r},{self.button!r},{self.modifiers!r})"
|
.venv/Lib/site-packages/prompt_toolkit/patch_stdout.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
patch_stdout
|
| 3 |
+
============
|
| 4 |
+
|
| 5 |
+
This implements a context manager that ensures that print statements within
|
| 6 |
+
it won't destroy the user interface. The context manager will replace
|
| 7 |
+
`sys.stdout` by something that draws the output above the current prompt,
|
| 8 |
+
rather than overwriting the UI.
|
| 9 |
+
|
| 10 |
+
Usage::
|
| 11 |
+
|
| 12 |
+
with patch_stdout(application):
|
| 13 |
+
...
|
| 14 |
+
application.run()
|
| 15 |
+
...
|
| 16 |
+
|
| 17 |
+
Multiple applications can run in the body of the context manager, one after the
|
| 18 |
+
other.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
import asyncio
|
| 24 |
+
import queue
|
| 25 |
+
import sys
|
| 26 |
+
import threading
|
| 27 |
+
import time
|
| 28 |
+
from contextlib import contextmanager
|
| 29 |
+
from typing import Generator, TextIO, cast
|
| 30 |
+
|
| 31 |
+
from .application import get_app_session, run_in_terminal
|
| 32 |
+
from .output import Output
|
| 33 |
+
|
| 34 |
+
__all__ = [
|
| 35 |
+
"patch_stdout",
|
| 36 |
+
"StdoutProxy",
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@contextmanager
|
| 41 |
+
def patch_stdout(raw: bool = False) -> Generator[None, None, None]:
|
| 42 |
+
"""
|
| 43 |
+
Replace `sys.stdout` by an :class:`_StdoutProxy` instance.
|
| 44 |
+
|
| 45 |
+
Writing to this proxy will make sure that the text appears above the
|
| 46 |
+
prompt, and that it doesn't destroy the output from the renderer. If no
|
| 47 |
+
application is curring, the behavior should be identical to writing to
|
| 48 |
+
`sys.stdout` directly.
|
| 49 |
+
|
| 50 |
+
Warning: If a new event loop is installed using `asyncio.set_event_loop()`,
|
| 51 |
+
then make sure that the context manager is applied after the event loop
|
| 52 |
+
is changed. Printing to stdout will be scheduled in the event loop
|
| 53 |
+
that's active when the context manager is created.
|
| 54 |
+
|
| 55 |
+
:param raw: (`bool`) When True, vt100 terminal escape sequences are not
|
| 56 |
+
removed/escaped.
|
| 57 |
+
"""
|
| 58 |
+
with StdoutProxy(raw=raw) as proxy:
|
| 59 |
+
original_stdout = sys.stdout
|
| 60 |
+
original_stderr = sys.stderr
|
| 61 |
+
|
| 62 |
+
# Enter.
|
| 63 |
+
sys.stdout = cast(TextIO, proxy)
|
| 64 |
+
sys.stderr = cast(TextIO, proxy)
|
| 65 |
+
|
| 66 |
+
try:
|
| 67 |
+
yield
|
| 68 |
+
finally:
|
| 69 |
+
sys.stdout = original_stdout
|
| 70 |
+
sys.stderr = original_stderr
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class _Done:
|
| 74 |
+
"Sentinel value for stopping the stdout proxy."
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class StdoutProxy:
|
| 78 |
+
"""
|
| 79 |
+
File-like object, which prints everything written to it, output above the
|
| 80 |
+
current application/prompt. This class is compatible with other file
|
| 81 |
+
objects and can be used as a drop-in replacement for `sys.stdout` or can
|
| 82 |
+
for instance be passed to `logging.StreamHandler`.
|
| 83 |
+
|
| 84 |
+
The current application, above which we print, is determined by looking
|
| 85 |
+
what application currently runs in the `AppSession` that is active during
|
| 86 |
+
the creation of this instance.
|
| 87 |
+
|
| 88 |
+
This class can be used as a context manager.
|
| 89 |
+
|
| 90 |
+
In order to avoid having to repaint the prompt continuously for every
|
| 91 |
+
little write, a short delay of `sleep_between_writes` seconds will be added
|
| 92 |
+
between writes in order to bundle many smaller writes in a short timespan.
|
| 93 |
+
"""
|
| 94 |
+
|
| 95 |
+
def __init__(
|
| 96 |
+
self,
|
| 97 |
+
sleep_between_writes: float = 0.2,
|
| 98 |
+
raw: bool = False,
|
| 99 |
+
) -> None:
|
| 100 |
+
self.sleep_between_writes = sleep_between_writes
|
| 101 |
+
self.raw = raw
|
| 102 |
+
|
| 103 |
+
self._lock = threading.RLock()
|
| 104 |
+
self._buffer: list[str] = []
|
| 105 |
+
|
| 106 |
+
# Keep track of the curret app session.
|
| 107 |
+
self.app_session = get_app_session()
|
| 108 |
+
|
| 109 |
+
# See what output is active *right now*. We should do it at this point,
|
| 110 |
+
# before this `StdoutProxy` instance is possibly assigned to `sys.stdout`.
|
| 111 |
+
# Otherwise, if `patch_stdout` is used, and no `Output` instance has
|
| 112 |
+
# been created, then the default output creation code will see this
|
| 113 |
+
# proxy object as `sys.stdout`, and get in a recursive loop trying to
|
| 114 |
+
# access `StdoutProxy.isatty()` which will again retrieve the output.
|
| 115 |
+
self._output: Output = self.app_session.output
|
| 116 |
+
|
| 117 |
+
# Flush thread
|
| 118 |
+
self._flush_queue: queue.Queue[str | _Done] = queue.Queue()
|
| 119 |
+
self._flush_thread = self._start_write_thread()
|
| 120 |
+
self.closed = False
|
| 121 |
+
|
| 122 |
+
def __enter__(self) -> StdoutProxy:
|
| 123 |
+
return self
|
| 124 |
+
|
| 125 |
+
def __exit__(self, *args: object) -> None:
|
| 126 |
+
self.close()
|
| 127 |
+
|
| 128 |
+
def close(self) -> None:
|
| 129 |
+
"""
|
| 130 |
+
Stop `StdoutProxy` proxy.
|
| 131 |
+
|
| 132 |
+
This will terminate the write thread, make sure everything is flushed
|
| 133 |
+
and wait for the write thread to finish.
|
| 134 |
+
"""
|
| 135 |
+
if not self.closed:
|
| 136 |
+
self._flush_queue.put(_Done())
|
| 137 |
+
self._flush_thread.join()
|
| 138 |
+
self.closed = True
|
| 139 |
+
|
| 140 |
+
def _start_write_thread(self) -> threading.Thread:
|
| 141 |
+
thread = threading.Thread(
|
| 142 |
+
target=self._write_thread,
|
| 143 |
+
name="patch-stdout-flush-thread",
|
| 144 |
+
daemon=True,
|
| 145 |
+
)
|
| 146 |
+
thread.start()
|
| 147 |
+
return thread
|
| 148 |
+
|
| 149 |
+
def _write_thread(self) -> None:
|
| 150 |
+
done = False
|
| 151 |
+
|
| 152 |
+
while not done:
|
| 153 |
+
item = self._flush_queue.get()
|
| 154 |
+
|
| 155 |
+
if isinstance(item, _Done):
|
| 156 |
+
break
|
| 157 |
+
|
| 158 |
+
# Don't bother calling when we got an empty string.
|
| 159 |
+
if not item:
|
| 160 |
+
continue
|
| 161 |
+
|
| 162 |
+
text = []
|
| 163 |
+
text.append(item)
|
| 164 |
+
|
| 165 |
+
# Read the rest of the queue if more data was queued up.
|
| 166 |
+
while True:
|
| 167 |
+
try:
|
| 168 |
+
item = self._flush_queue.get_nowait()
|
| 169 |
+
except queue.Empty:
|
| 170 |
+
break
|
| 171 |
+
else:
|
| 172 |
+
if isinstance(item, _Done):
|
| 173 |
+
done = True
|
| 174 |
+
else:
|
| 175 |
+
text.append(item)
|
| 176 |
+
|
| 177 |
+
app_loop = self._get_app_loop()
|
| 178 |
+
self._write_and_flush(app_loop, "".join(text))
|
| 179 |
+
|
| 180 |
+
# If an application was running that requires repainting, then wait
|
| 181 |
+
# for a very short time, in order to bundle actual writes and avoid
|
| 182 |
+
# having to repaint to often.
|
| 183 |
+
if app_loop is not None:
|
| 184 |
+
time.sleep(self.sleep_between_writes)
|
| 185 |
+
|
| 186 |
+
def _get_app_loop(self) -> asyncio.AbstractEventLoop | None:
|
| 187 |
+
"""
|
| 188 |
+
Return the event loop for the application currently running in our
|
| 189 |
+
`AppSession`.
|
| 190 |
+
"""
|
| 191 |
+
app = self.app_session.app
|
| 192 |
+
|
| 193 |
+
if app is None:
|
| 194 |
+
return None
|
| 195 |
+
|
| 196 |
+
return app.loop
|
| 197 |
+
|
| 198 |
+
def _write_and_flush(
|
| 199 |
+
self, loop: asyncio.AbstractEventLoop | None, text: str
|
| 200 |
+
) -> None:
|
| 201 |
+
"""
|
| 202 |
+
Write the given text to stdout and flush.
|
| 203 |
+
If an application is running, use `run_in_terminal`.
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
def write_and_flush() -> None:
|
| 207 |
+
# Ensure that autowrap is enabled before calling `write`.
|
| 208 |
+
# XXX: On Windows, the `Windows10_Output` enables/disables VT
|
| 209 |
+
# terminal processing for every flush. It turns out that this
|
| 210 |
+
# causes autowrap to be reset (disabled) after each flush. So,
|
| 211 |
+
# we have to enable it again before writing text.
|
| 212 |
+
self._output.enable_autowrap()
|
| 213 |
+
|
| 214 |
+
if self.raw:
|
| 215 |
+
self._output.write_raw(text)
|
| 216 |
+
else:
|
| 217 |
+
self._output.write(text)
|
| 218 |
+
|
| 219 |
+
self._output.flush()
|
| 220 |
+
|
| 221 |
+
def write_and_flush_in_loop() -> None:
|
| 222 |
+
# If an application is running, use `run_in_terminal`, otherwise
|
| 223 |
+
# call it directly.
|
| 224 |
+
run_in_terminal(write_and_flush, in_executor=False)
|
| 225 |
+
|
| 226 |
+
if loop is None:
|
| 227 |
+
# No loop, write immediately.
|
| 228 |
+
write_and_flush()
|
| 229 |
+
else:
|
| 230 |
+
# Make sure `write_and_flush` is executed *in* the event loop, not
|
| 231 |
+
# in another thread.
|
| 232 |
+
loop.call_soon_threadsafe(write_and_flush_in_loop)
|
| 233 |
+
|
| 234 |
+
def _write(self, data: str) -> None:
|
| 235 |
+
"""
|
| 236 |
+
Note: print()-statements cause to multiple write calls.
|
| 237 |
+
(write('line') and write('\n')). Of course we don't want to call
|
| 238 |
+
`run_in_terminal` for every individual call, because that's too
|
| 239 |
+
expensive, and as long as the newline hasn't been written, the
|
| 240 |
+
text itself is again overwritten by the rendering of the input
|
| 241 |
+
command line. Therefor, we have a little buffer which holds the
|
| 242 |
+
text until a newline is written to stdout.
|
| 243 |
+
"""
|
| 244 |
+
if "\n" in data:
|
| 245 |
+
# When there is a newline in the data, write everything before the
|
| 246 |
+
# newline, including the newline itself.
|
| 247 |
+
before, after = data.rsplit("\n", 1)
|
| 248 |
+
to_write = self._buffer + [before, "\n"]
|
| 249 |
+
self._buffer = [after]
|
| 250 |
+
|
| 251 |
+
text = "".join(to_write)
|
| 252 |
+
self._flush_queue.put(text)
|
| 253 |
+
else:
|
| 254 |
+
# Otherwise, cache in buffer.
|
| 255 |
+
self._buffer.append(data)
|
| 256 |
+
|
| 257 |
+
def _flush(self) -> None:
|
| 258 |
+
text = "".join(self._buffer)
|
| 259 |
+
self._buffer = []
|
| 260 |
+
self._flush_queue.put(text)
|
| 261 |
+
|
| 262 |
+
def write(self, data: str) -> int:
|
| 263 |
+
with self._lock:
|
| 264 |
+
self._write(data)
|
| 265 |
+
|
| 266 |
+
return len(data) # Pretend everything was written.
|
| 267 |
+
|
| 268 |
+
def flush(self) -> None:
|
| 269 |
+
"""
|
| 270 |
+
Flush buffered output.
|
| 271 |
+
"""
|
| 272 |
+
with self._lock:
|
| 273 |
+
self._flush()
|
| 274 |
+
|
| 275 |
+
@property
|
| 276 |
+
def original_stdout(self) -> TextIO | None:
|
| 277 |
+
return self._output.stdout or sys.__stdout__
|
| 278 |
+
|
| 279 |
+
# Attributes for compatibility with sys.__stdout__:
|
| 280 |
+
|
| 281 |
+
def fileno(self) -> int:
|
| 282 |
+
return self._output.fileno()
|
| 283 |
+
|
| 284 |
+
def isatty(self) -> bool:
|
| 285 |
+
stdout = self._output.stdout
|
| 286 |
+
if stdout is None:
|
| 287 |
+
return False
|
| 288 |
+
|
| 289 |
+
return stdout.isatty()
|
| 290 |
+
|
| 291 |
+
@property
|
| 292 |
+
def encoding(self) -> str:
|
| 293 |
+
return self._output.encoding()
|
| 294 |
+
|
| 295 |
+
@property
|
| 296 |
+
def errors(self) -> str:
|
| 297 |
+
return "strict"
|
.venv/Lib/site-packages/prompt_toolkit/py.typed
ADDED
|
File without changes
|
.venv/Lib/site-packages/prompt_toolkit/renderer.py
ADDED
|
@@ -0,0 +1,820 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Renders the command line on the console.
|
| 3 |
+
(Redraws parts of the input line that were changed.)
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait
|
| 9 |
+
from collections import deque
|
| 10 |
+
from enum import Enum
|
| 11 |
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Hashable
|
| 12 |
+
|
| 13 |
+
from prompt_toolkit.application.current import get_app
|
| 14 |
+
from prompt_toolkit.cursor_shapes import CursorShape
|
| 15 |
+
from prompt_toolkit.data_structures import Point, Size
|
| 16 |
+
from prompt_toolkit.filters import FilterOrBool, to_filter
|
| 17 |
+
from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text
|
| 18 |
+
from prompt_toolkit.layout.mouse_handlers import MouseHandlers
|
| 19 |
+
from prompt_toolkit.layout.screen import Char, Screen, WritePosition
|
| 20 |
+
from prompt_toolkit.output import ColorDepth, Output
|
| 21 |
+
from prompt_toolkit.styles import (
|
| 22 |
+
Attrs,
|
| 23 |
+
BaseStyle,
|
| 24 |
+
DummyStyleTransformation,
|
| 25 |
+
StyleTransformation,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
if TYPE_CHECKING:
|
| 29 |
+
from prompt_toolkit.application import Application
|
| 30 |
+
from prompt_toolkit.layout.layout import Layout
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"Renderer",
|
| 35 |
+
"print_formatted_text",
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _output_screen_diff(
|
| 40 |
+
app: Application[Any],
|
| 41 |
+
output: Output,
|
| 42 |
+
screen: Screen,
|
| 43 |
+
current_pos: Point,
|
| 44 |
+
color_depth: ColorDepth,
|
| 45 |
+
previous_screen: Screen | None,
|
| 46 |
+
last_style: str | None,
|
| 47 |
+
is_done: bool, # XXX: drop is_done
|
| 48 |
+
full_screen: bool,
|
| 49 |
+
attrs_for_style_string: _StyleStringToAttrsCache,
|
| 50 |
+
style_string_has_style: _StyleStringHasStyleCache,
|
| 51 |
+
size: Size,
|
| 52 |
+
previous_width: int,
|
| 53 |
+
) -> tuple[Point, str | None]:
|
| 54 |
+
"""
|
| 55 |
+
Render the diff between this screen and the previous screen.
|
| 56 |
+
|
| 57 |
+
This takes two `Screen` instances. The one that represents the output like
|
| 58 |
+
it was during the last rendering and one that represents the current
|
| 59 |
+
output raster. Looking at these two `Screen` instances, this function will
|
| 60 |
+
render the difference by calling the appropriate methods of the `Output`
|
| 61 |
+
object that only paint the changes to the terminal.
|
| 62 |
+
|
| 63 |
+
This is some performance-critical code which is heavily optimized.
|
| 64 |
+
Don't change things without profiling first.
|
| 65 |
+
|
| 66 |
+
:param current_pos: Current cursor position.
|
| 67 |
+
:param last_style: The style string, used for drawing the last drawn
|
| 68 |
+
character. (Color/attributes.)
|
| 69 |
+
:param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance.
|
| 70 |
+
:param width: The width of the terminal.
|
| 71 |
+
:param previous_width: The width of the terminal during the last rendering.
|
| 72 |
+
"""
|
| 73 |
+
width, height = size.columns, size.rows
|
| 74 |
+
|
| 75 |
+
#: Variable for capturing the output.
|
| 76 |
+
write = output.write
|
| 77 |
+
write_raw = output.write_raw
|
| 78 |
+
|
| 79 |
+
# Create locals for the most used output methods.
|
| 80 |
+
# (Save expensive attribute lookups.)
|
| 81 |
+
_output_set_attributes = output.set_attributes
|
| 82 |
+
_output_reset_attributes = output.reset_attributes
|
| 83 |
+
_output_cursor_forward = output.cursor_forward
|
| 84 |
+
_output_cursor_up = output.cursor_up
|
| 85 |
+
_output_cursor_backward = output.cursor_backward
|
| 86 |
+
|
| 87 |
+
# Hide cursor before rendering. (Avoid flickering.)
|
| 88 |
+
output.hide_cursor()
|
| 89 |
+
|
| 90 |
+
def reset_attributes() -> None:
|
| 91 |
+
"Wrapper around Output.reset_attributes."
|
| 92 |
+
nonlocal last_style
|
| 93 |
+
_output_reset_attributes()
|
| 94 |
+
last_style = None # Forget last char after resetting attributes.
|
| 95 |
+
|
| 96 |
+
def move_cursor(new: Point) -> Point:
|
| 97 |
+
"Move cursor to this `new` point. Returns the given Point."
|
| 98 |
+
current_x, current_y = current_pos.x, current_pos.y
|
| 99 |
+
|
| 100 |
+
if new.y > current_y:
|
| 101 |
+
# Use newlines instead of CURSOR_DOWN, because this might add new lines.
|
| 102 |
+
# CURSOR_DOWN will never create new lines at the bottom.
|
| 103 |
+
# Also reset attributes, otherwise the newline could draw a
|
| 104 |
+
# background color.
|
| 105 |
+
reset_attributes()
|
| 106 |
+
write("\r\n" * (new.y - current_y))
|
| 107 |
+
current_x = 0
|
| 108 |
+
_output_cursor_forward(new.x)
|
| 109 |
+
return new
|
| 110 |
+
elif new.y < current_y:
|
| 111 |
+
_output_cursor_up(current_y - new.y)
|
| 112 |
+
|
| 113 |
+
if current_x >= width - 1:
|
| 114 |
+
write("\r")
|
| 115 |
+
_output_cursor_forward(new.x)
|
| 116 |
+
elif new.x < current_x or current_x >= width - 1:
|
| 117 |
+
_output_cursor_backward(current_x - new.x)
|
| 118 |
+
elif new.x > current_x:
|
| 119 |
+
_output_cursor_forward(new.x - current_x)
|
| 120 |
+
|
| 121 |
+
return new
|
| 122 |
+
|
| 123 |
+
def output_char(char: Char) -> None:
|
| 124 |
+
"""
|
| 125 |
+
Write the output of this character.
|
| 126 |
+
"""
|
| 127 |
+
nonlocal last_style
|
| 128 |
+
|
| 129 |
+
# If the last printed character has the same style, don't output the
|
| 130 |
+
# style again.
|
| 131 |
+
if last_style == char.style:
|
| 132 |
+
write(char.char)
|
| 133 |
+
else:
|
| 134 |
+
# Look up `Attr` for this style string. Only set attributes if different.
|
| 135 |
+
# (Two style strings can still have the same formatting.)
|
| 136 |
+
# Note that an empty style string can have formatting that needs to
|
| 137 |
+
# be applied, because of style transformations.
|
| 138 |
+
new_attrs = attrs_for_style_string[char.style]
|
| 139 |
+
if not last_style or new_attrs != attrs_for_style_string[last_style]:
|
| 140 |
+
_output_set_attributes(new_attrs, color_depth)
|
| 141 |
+
|
| 142 |
+
write(char.char)
|
| 143 |
+
last_style = char.style
|
| 144 |
+
|
| 145 |
+
def get_max_column_index(row: dict[int, Char]) -> int:
|
| 146 |
+
"""
|
| 147 |
+
Return max used column index, ignoring whitespace (without style) at
|
| 148 |
+
the end of the line. This is important for people that copy/paste
|
| 149 |
+
terminal output.
|
| 150 |
+
|
| 151 |
+
There are two reasons we are sometimes seeing whitespace at the end:
|
| 152 |
+
- `BufferControl` adds a trailing space to each line, because it's a
|
| 153 |
+
possible cursor position, so that the line wrapping won't change if
|
| 154 |
+
the cursor position moves around.
|
| 155 |
+
- The `Window` adds a style class to the current line for highlighting
|
| 156 |
+
(cursor-line).
|
| 157 |
+
"""
|
| 158 |
+
numbers = (
|
| 159 |
+
index
|
| 160 |
+
for index, cell in row.items()
|
| 161 |
+
if cell.char != " " or style_string_has_style[cell.style]
|
| 162 |
+
)
|
| 163 |
+
return max(numbers, default=0)
|
| 164 |
+
|
| 165 |
+
# Render for the first time: reset styling.
|
| 166 |
+
if not previous_screen:
|
| 167 |
+
reset_attributes()
|
| 168 |
+
|
| 169 |
+
# Disable autowrap. (When entering a the alternate screen, or anytime when
|
| 170 |
+
# we have a prompt. - In the case of a REPL, like IPython, people can have
|
| 171 |
+
# background threads, and it's hard for debugging if their output is not
|
| 172 |
+
# wrapped.)
|
| 173 |
+
if not previous_screen or not full_screen:
|
| 174 |
+
output.disable_autowrap()
|
| 175 |
+
|
| 176 |
+
# When the previous screen has a different size, redraw everything anyway.
|
| 177 |
+
# Also when we are done. (We might take up less rows, so clearing is important.)
|
| 178 |
+
if (
|
| 179 |
+
is_done or not previous_screen or previous_width != width
|
| 180 |
+
): # XXX: also consider height??
|
| 181 |
+
current_pos = move_cursor(Point(x=0, y=0))
|
| 182 |
+
reset_attributes()
|
| 183 |
+
output.erase_down()
|
| 184 |
+
|
| 185 |
+
previous_screen = Screen()
|
| 186 |
+
|
| 187 |
+
# Get height of the screen.
|
| 188 |
+
# (height changes as we loop over data_buffer, so remember the current value.)
|
| 189 |
+
# (Also make sure to clip the height to the size of the output.)
|
| 190 |
+
current_height = min(screen.height, height)
|
| 191 |
+
|
| 192 |
+
# Loop over the rows.
|
| 193 |
+
row_count = min(max(screen.height, previous_screen.height), height)
|
| 194 |
+
|
| 195 |
+
for y in range(row_count):
|
| 196 |
+
new_row = screen.data_buffer[y]
|
| 197 |
+
previous_row = previous_screen.data_buffer[y]
|
| 198 |
+
zero_width_escapes_row = screen.zero_width_escapes[y]
|
| 199 |
+
|
| 200 |
+
new_max_line_len = min(width - 1, get_max_column_index(new_row))
|
| 201 |
+
previous_max_line_len = min(width - 1, get_max_column_index(previous_row))
|
| 202 |
+
|
| 203 |
+
# Loop over the columns.
|
| 204 |
+
c = 0 # Column counter.
|
| 205 |
+
while c <= new_max_line_len:
|
| 206 |
+
new_char = new_row[c]
|
| 207 |
+
old_char = previous_row[c]
|
| 208 |
+
char_width = new_char.width or 1
|
| 209 |
+
|
| 210 |
+
# When the old and new character at this position are different,
|
| 211 |
+
# draw the output. (Because of the performance, we don't call
|
| 212 |
+
# `Char.__ne__`, but inline the same expression.)
|
| 213 |
+
if new_char.char != old_char.char or new_char.style != old_char.style:
|
| 214 |
+
current_pos = move_cursor(Point(x=c, y=y))
|
| 215 |
+
|
| 216 |
+
# Send injected escape sequences to output.
|
| 217 |
+
if c in zero_width_escapes_row:
|
| 218 |
+
write_raw(zero_width_escapes_row[c])
|
| 219 |
+
|
| 220 |
+
output_char(new_char)
|
| 221 |
+
current_pos = Point(x=current_pos.x + char_width, y=current_pos.y)
|
| 222 |
+
|
| 223 |
+
c += char_width
|
| 224 |
+
|
| 225 |
+
# If the new line is shorter, trim it.
|
| 226 |
+
if previous_screen and new_max_line_len < previous_max_line_len:
|
| 227 |
+
current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y))
|
| 228 |
+
reset_attributes()
|
| 229 |
+
output.erase_end_of_line()
|
| 230 |
+
|
| 231 |
+
# Correctly reserve vertical space as required by the layout.
|
| 232 |
+
# When this is a new screen (drawn for the first time), or for some reason
|
| 233 |
+
# higher than the previous one. Move the cursor once to the bottom of the
|
| 234 |
+
# output. That way, we're sure that the terminal scrolls up, even when the
|
| 235 |
+
# lower lines of the canvas just contain whitespace.
|
| 236 |
+
|
| 237 |
+
# The most obvious reason that we actually want this behavior is the avoid
|
| 238 |
+
# the artifact of the input scrolling when the completion menu is shown.
|
| 239 |
+
# (If the scrolling is actually wanted, the layout can still be build in a
|
| 240 |
+
# way to behave that way by setting a dynamic height.)
|
| 241 |
+
if current_height > previous_screen.height:
|
| 242 |
+
current_pos = move_cursor(Point(x=0, y=current_height - 1))
|
| 243 |
+
|
| 244 |
+
# Move cursor:
|
| 245 |
+
if is_done:
|
| 246 |
+
current_pos = move_cursor(Point(x=0, y=current_height))
|
| 247 |
+
output.erase_down()
|
| 248 |
+
else:
|
| 249 |
+
current_pos = move_cursor(screen.get_cursor_position(app.layout.current_window))
|
| 250 |
+
|
| 251 |
+
if is_done or not full_screen:
|
| 252 |
+
output.enable_autowrap()
|
| 253 |
+
|
| 254 |
+
# Always reset the color attributes. This is important because a background
|
| 255 |
+
# thread could print data to stdout and we want that to be displayed in the
|
| 256 |
+
# default colors. (Also, if a background color has been set, many terminals
|
| 257 |
+
# give weird artifacts on resize events.)
|
| 258 |
+
reset_attributes()
|
| 259 |
+
|
| 260 |
+
if screen.show_cursor:
|
| 261 |
+
output.show_cursor()
|
| 262 |
+
|
| 263 |
+
return current_pos, last_style
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
class HeightIsUnknownError(Exception):
|
| 267 |
+
"Information unavailable. Did not yet receive the CPR response."
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
class _StyleStringToAttrsCache(Dict[str, Attrs]):
|
| 271 |
+
"""
|
| 272 |
+
A cache structure that maps style strings to :class:`.Attr`.
|
| 273 |
+
(This is an important speed up.)
|
| 274 |
+
"""
|
| 275 |
+
|
| 276 |
+
def __init__(
|
| 277 |
+
self,
|
| 278 |
+
get_attrs_for_style_str: Callable[[str], Attrs],
|
| 279 |
+
style_transformation: StyleTransformation,
|
| 280 |
+
) -> None:
|
| 281 |
+
self.get_attrs_for_style_str = get_attrs_for_style_str
|
| 282 |
+
self.style_transformation = style_transformation
|
| 283 |
+
|
| 284 |
+
def __missing__(self, style_str: str) -> Attrs:
|
| 285 |
+
attrs = self.get_attrs_for_style_str(style_str)
|
| 286 |
+
attrs = self.style_transformation.transform_attrs(attrs)
|
| 287 |
+
|
| 288 |
+
self[style_str] = attrs
|
| 289 |
+
return attrs
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
class _StyleStringHasStyleCache(Dict[str, bool]):
|
| 293 |
+
"""
|
| 294 |
+
Cache for remember which style strings don't render the default output
|
| 295 |
+
style (default fg/bg, no underline and no reverse and no blink). That way
|
| 296 |
+
we know that we should render these cells, even when they're empty (when
|
| 297 |
+
they contain a space).
|
| 298 |
+
|
| 299 |
+
Note: we don't consider bold/italic/hidden because they don't change the
|
| 300 |
+
output if there's no text in the cell.
|
| 301 |
+
"""
|
| 302 |
+
|
| 303 |
+
def __init__(self, style_string_to_attrs: dict[str, Attrs]) -> None:
|
| 304 |
+
self.style_string_to_attrs = style_string_to_attrs
|
| 305 |
+
|
| 306 |
+
def __missing__(self, style_str: str) -> bool:
|
| 307 |
+
attrs = self.style_string_to_attrs[style_str]
|
| 308 |
+
is_default = bool(
|
| 309 |
+
attrs.color
|
| 310 |
+
or attrs.bgcolor
|
| 311 |
+
or attrs.underline
|
| 312 |
+
or attrs.strike
|
| 313 |
+
or attrs.blink
|
| 314 |
+
or attrs.reverse
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
self[style_str] = is_default
|
| 318 |
+
return is_default
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
class CPR_Support(Enum):
|
| 322 |
+
"Enum: whether or not CPR is supported."
|
| 323 |
+
|
| 324 |
+
SUPPORTED = "SUPPORTED"
|
| 325 |
+
NOT_SUPPORTED = "NOT_SUPPORTED"
|
| 326 |
+
UNKNOWN = "UNKNOWN"
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
class Renderer:
|
| 330 |
+
"""
|
| 331 |
+
Typical usage:
|
| 332 |
+
|
| 333 |
+
::
|
| 334 |
+
|
| 335 |
+
output = Vt100_Output.from_pty(sys.stdout)
|
| 336 |
+
r = Renderer(style, output)
|
| 337 |
+
r.render(app, layout=...)
|
| 338 |
+
"""
|
| 339 |
+
|
| 340 |
+
CPR_TIMEOUT = 2 # Time to wait until we consider CPR to be not supported.
|
| 341 |
+
|
| 342 |
+
def __init__(
|
| 343 |
+
self,
|
| 344 |
+
style: BaseStyle,
|
| 345 |
+
output: Output,
|
| 346 |
+
full_screen: bool = False,
|
| 347 |
+
mouse_support: FilterOrBool = False,
|
| 348 |
+
cpr_not_supported_callback: Callable[[], None] | None = None,
|
| 349 |
+
) -> None:
|
| 350 |
+
self.style = style
|
| 351 |
+
self.output = output
|
| 352 |
+
self.full_screen = full_screen
|
| 353 |
+
self.mouse_support = to_filter(mouse_support)
|
| 354 |
+
self.cpr_not_supported_callback = cpr_not_supported_callback
|
| 355 |
+
|
| 356 |
+
# TODO: Move following state flags into `Vt100_Output`, similar to
|
| 357 |
+
# `_cursor_shape_changed` and `_cursor_visible`. But then also
|
| 358 |
+
# adjust the `Win32Output` to not call win32 APIs if nothing has
|
| 359 |
+
# to be changed.
|
| 360 |
+
|
| 361 |
+
self._in_alternate_screen = False
|
| 362 |
+
self._mouse_support_enabled = False
|
| 363 |
+
self._bracketed_paste_enabled = False
|
| 364 |
+
self._cursor_key_mode_reset = False
|
| 365 |
+
|
| 366 |
+
# Future set when we are waiting for a CPR flag.
|
| 367 |
+
self._waiting_for_cpr_futures: deque[Future[None]] = deque()
|
| 368 |
+
self.cpr_support = CPR_Support.UNKNOWN
|
| 369 |
+
|
| 370 |
+
if not output.responds_to_cpr:
|
| 371 |
+
self.cpr_support = CPR_Support.NOT_SUPPORTED
|
| 372 |
+
|
| 373 |
+
# Cache for the style.
|
| 374 |
+
self._attrs_for_style: _StyleStringToAttrsCache | None = None
|
| 375 |
+
self._style_string_has_style: _StyleStringHasStyleCache | None = None
|
| 376 |
+
self._last_style_hash: Hashable | None = None
|
| 377 |
+
self._last_transformation_hash: Hashable | None = None
|
| 378 |
+
self._last_color_depth: ColorDepth | None = None
|
| 379 |
+
|
| 380 |
+
self.reset(_scroll=True)
|
| 381 |
+
|
| 382 |
+
def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None:
|
| 383 |
+
# Reset position
|
| 384 |
+
self._cursor_pos = Point(x=0, y=0)
|
| 385 |
+
|
| 386 |
+
# Remember the last screen instance between renderers. This way,
|
| 387 |
+
# we can create a `diff` between two screens and only output the
|
| 388 |
+
# difference. It's also to remember the last height. (To show for
|
| 389 |
+
# instance a toolbar at the bottom position.)
|
| 390 |
+
self._last_screen: Screen | None = None
|
| 391 |
+
self._last_size: Size | None = None
|
| 392 |
+
self._last_style: str | None = None
|
| 393 |
+
self._last_cursor_shape: CursorShape | None = None
|
| 394 |
+
|
| 395 |
+
# Default MouseHandlers. (Just empty.)
|
| 396 |
+
self.mouse_handlers = MouseHandlers()
|
| 397 |
+
|
| 398 |
+
#: Space from the top of the layout, until the bottom of the terminal.
|
| 399 |
+
#: We don't know this until a `report_absolute_cursor_row` call.
|
| 400 |
+
self._min_available_height = 0
|
| 401 |
+
|
| 402 |
+
# In case of Windows, also make sure to scroll to the current cursor
|
| 403 |
+
# position. (Only when rendering the first time.)
|
| 404 |
+
# It does nothing for vt100 terminals.
|
| 405 |
+
if _scroll:
|
| 406 |
+
self.output.scroll_buffer_to_prompt()
|
| 407 |
+
|
| 408 |
+
# Quit alternate screen.
|
| 409 |
+
if self._in_alternate_screen and leave_alternate_screen:
|
| 410 |
+
self.output.quit_alternate_screen()
|
| 411 |
+
self._in_alternate_screen = False
|
| 412 |
+
|
| 413 |
+
# Disable mouse support.
|
| 414 |
+
if self._mouse_support_enabled:
|
| 415 |
+
self.output.disable_mouse_support()
|
| 416 |
+
self._mouse_support_enabled = False
|
| 417 |
+
|
| 418 |
+
# Disable bracketed paste.
|
| 419 |
+
if self._bracketed_paste_enabled:
|
| 420 |
+
self.output.disable_bracketed_paste()
|
| 421 |
+
self._bracketed_paste_enabled = False
|
| 422 |
+
|
| 423 |
+
self.output.reset_cursor_shape()
|
| 424 |
+
self.output.show_cursor()
|
| 425 |
+
|
| 426 |
+
# NOTE: No need to set/reset cursor key mode here.
|
| 427 |
+
|
| 428 |
+
# Flush output. `disable_mouse_support` needs to write to stdout.
|
| 429 |
+
self.output.flush()
|
| 430 |
+
|
| 431 |
+
@property
|
| 432 |
+
def last_rendered_screen(self) -> Screen | None:
|
| 433 |
+
"""
|
| 434 |
+
The `Screen` class that was generated during the last rendering.
|
| 435 |
+
This can be `None`.
|
| 436 |
+
"""
|
| 437 |
+
return self._last_screen
|
| 438 |
+
|
| 439 |
+
@property
|
| 440 |
+
def height_is_known(self) -> bool:
|
| 441 |
+
"""
|
| 442 |
+
True when the height from the cursor until the bottom of the terminal
|
| 443 |
+
is known. (It's often nicer to draw bottom toolbars only if the height
|
| 444 |
+
is known, in order to avoid flickering when the CPR response arrives.)
|
| 445 |
+
"""
|
| 446 |
+
if self.full_screen or self._min_available_height > 0:
|
| 447 |
+
return True
|
| 448 |
+
try:
|
| 449 |
+
self._min_available_height = self.output.get_rows_below_cursor_position()
|
| 450 |
+
return True
|
| 451 |
+
except NotImplementedError:
|
| 452 |
+
return False
|
| 453 |
+
|
| 454 |
+
@property
|
| 455 |
+
def rows_above_layout(self) -> int:
|
| 456 |
+
"""
|
| 457 |
+
Return the number of rows visible in the terminal above the layout.
|
| 458 |
+
"""
|
| 459 |
+
if self._in_alternate_screen:
|
| 460 |
+
return 0
|
| 461 |
+
elif self._min_available_height > 0:
|
| 462 |
+
total_rows = self.output.get_size().rows
|
| 463 |
+
last_screen_height = self._last_screen.height if self._last_screen else 0
|
| 464 |
+
return total_rows - max(self._min_available_height, last_screen_height)
|
| 465 |
+
else:
|
| 466 |
+
raise HeightIsUnknownError("Rows above layout is unknown.")
|
| 467 |
+
|
| 468 |
+
def request_absolute_cursor_position(self) -> None:
|
| 469 |
+
"""
|
| 470 |
+
Get current cursor position.
|
| 471 |
+
|
| 472 |
+
We do this to calculate the minimum available height that we can
|
| 473 |
+
consume for rendering the prompt. This is the available space below te
|
| 474 |
+
cursor.
|
| 475 |
+
|
| 476 |
+
For vt100: Do CPR request. (answer will arrive later.)
|
| 477 |
+
For win32: Do API call. (Answer comes immediately.)
|
| 478 |
+
"""
|
| 479 |
+
# Only do this request when the cursor is at the top row. (after a
|
| 480 |
+
# clear or reset). We will rely on that in `report_absolute_cursor_row`.
|
| 481 |
+
assert self._cursor_pos.y == 0
|
| 482 |
+
|
| 483 |
+
# In full-screen mode, always use the total height as min-available-height.
|
| 484 |
+
if self.full_screen:
|
| 485 |
+
self._min_available_height = self.output.get_size().rows
|
| 486 |
+
return
|
| 487 |
+
|
| 488 |
+
# For Win32, we have an API call to get the number of rows below the
|
| 489 |
+
# cursor.
|
| 490 |
+
try:
|
| 491 |
+
self._min_available_height = self.output.get_rows_below_cursor_position()
|
| 492 |
+
return
|
| 493 |
+
except NotImplementedError:
|
| 494 |
+
pass
|
| 495 |
+
|
| 496 |
+
# Use CPR.
|
| 497 |
+
if self.cpr_support == CPR_Support.NOT_SUPPORTED:
|
| 498 |
+
return
|
| 499 |
+
|
| 500 |
+
def do_cpr() -> None:
|
| 501 |
+
# Asks for a cursor position report (CPR).
|
| 502 |
+
self._waiting_for_cpr_futures.append(Future())
|
| 503 |
+
self.output.ask_for_cpr()
|
| 504 |
+
|
| 505 |
+
if self.cpr_support == CPR_Support.SUPPORTED:
|
| 506 |
+
do_cpr()
|
| 507 |
+
return
|
| 508 |
+
|
| 509 |
+
# If we don't know whether CPR is supported, only do a request if
|
| 510 |
+
# none is pending, and test it, using a timer.
|
| 511 |
+
if self.waiting_for_cpr:
|
| 512 |
+
return
|
| 513 |
+
|
| 514 |
+
do_cpr()
|
| 515 |
+
|
| 516 |
+
async def timer() -> None:
|
| 517 |
+
await sleep(self.CPR_TIMEOUT)
|
| 518 |
+
|
| 519 |
+
# Not set in the meantime -> not supported.
|
| 520 |
+
if self.cpr_support == CPR_Support.UNKNOWN:
|
| 521 |
+
self.cpr_support = CPR_Support.NOT_SUPPORTED
|
| 522 |
+
|
| 523 |
+
if self.cpr_not_supported_callback:
|
| 524 |
+
# Make sure to call this callback in the main thread.
|
| 525 |
+
self.cpr_not_supported_callback()
|
| 526 |
+
|
| 527 |
+
get_app().create_background_task(timer())
|
| 528 |
+
|
| 529 |
+
def report_absolute_cursor_row(self, row: int) -> None:
|
| 530 |
+
"""
|
| 531 |
+
To be called when we know the absolute cursor position.
|
| 532 |
+
(As an answer of a "Cursor Position Request" response.)
|
| 533 |
+
"""
|
| 534 |
+
self.cpr_support = CPR_Support.SUPPORTED
|
| 535 |
+
|
| 536 |
+
# Calculate the amount of rows from the cursor position until the
|
| 537 |
+
# bottom of the terminal.
|
| 538 |
+
total_rows = self.output.get_size().rows
|
| 539 |
+
rows_below_cursor = total_rows - row + 1
|
| 540 |
+
|
| 541 |
+
# Set the minimum available height.
|
| 542 |
+
self._min_available_height = rows_below_cursor
|
| 543 |
+
|
| 544 |
+
# Pop and set waiting for CPR future.
|
| 545 |
+
try:
|
| 546 |
+
f = self._waiting_for_cpr_futures.popleft()
|
| 547 |
+
except IndexError:
|
| 548 |
+
pass # Received CPR response without having a CPR.
|
| 549 |
+
else:
|
| 550 |
+
f.set_result(None)
|
| 551 |
+
|
| 552 |
+
@property
|
| 553 |
+
def waiting_for_cpr(self) -> bool:
|
| 554 |
+
"""
|
| 555 |
+
Waiting for CPR flag. True when we send the request, but didn't got a
|
| 556 |
+
response.
|
| 557 |
+
"""
|
| 558 |
+
return bool(self._waiting_for_cpr_futures)
|
| 559 |
+
|
| 560 |
+
async def wait_for_cpr_responses(self, timeout: int = 1) -> None:
|
| 561 |
+
"""
|
| 562 |
+
Wait for a CPR response.
|
| 563 |
+
"""
|
| 564 |
+
cpr_futures = list(self._waiting_for_cpr_futures) # Make copy.
|
| 565 |
+
|
| 566 |
+
# When there are no CPRs in the queue. Don't do anything.
|
| 567 |
+
if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED:
|
| 568 |
+
return None
|
| 569 |
+
|
| 570 |
+
async def wait_for_responses() -> None:
|
| 571 |
+
for response_f in cpr_futures:
|
| 572 |
+
await response_f
|
| 573 |
+
|
| 574 |
+
async def wait_for_timeout() -> None:
|
| 575 |
+
await sleep(timeout)
|
| 576 |
+
|
| 577 |
+
# Got timeout, erase queue.
|
| 578 |
+
for response_f in cpr_futures:
|
| 579 |
+
response_f.cancel()
|
| 580 |
+
self._waiting_for_cpr_futures = deque()
|
| 581 |
+
|
| 582 |
+
tasks = {
|
| 583 |
+
ensure_future(wait_for_responses()),
|
| 584 |
+
ensure_future(wait_for_timeout()),
|
| 585 |
+
}
|
| 586 |
+
_, pending = await wait(tasks, return_when=FIRST_COMPLETED)
|
| 587 |
+
for task in pending:
|
| 588 |
+
task.cancel()
|
| 589 |
+
|
| 590 |
+
def render(
|
| 591 |
+
self, app: Application[Any], layout: Layout, is_done: bool = False
|
| 592 |
+
) -> None:
|
| 593 |
+
"""
|
| 594 |
+
Render the current interface to the output.
|
| 595 |
+
|
| 596 |
+
:param is_done: When True, put the cursor at the end of the interface. We
|
| 597 |
+
won't print any changes to this part.
|
| 598 |
+
"""
|
| 599 |
+
output = self.output
|
| 600 |
+
|
| 601 |
+
# Enter alternate screen.
|
| 602 |
+
if self.full_screen and not self._in_alternate_screen:
|
| 603 |
+
self._in_alternate_screen = True
|
| 604 |
+
output.enter_alternate_screen()
|
| 605 |
+
|
| 606 |
+
# Enable bracketed paste.
|
| 607 |
+
if not self._bracketed_paste_enabled:
|
| 608 |
+
self.output.enable_bracketed_paste()
|
| 609 |
+
self._bracketed_paste_enabled = True
|
| 610 |
+
|
| 611 |
+
# Reset cursor key mode.
|
| 612 |
+
if not self._cursor_key_mode_reset:
|
| 613 |
+
self.output.reset_cursor_key_mode()
|
| 614 |
+
self._cursor_key_mode_reset = True
|
| 615 |
+
|
| 616 |
+
# Enable/disable mouse support.
|
| 617 |
+
needs_mouse_support = self.mouse_support()
|
| 618 |
+
|
| 619 |
+
if needs_mouse_support and not self._mouse_support_enabled:
|
| 620 |
+
output.enable_mouse_support()
|
| 621 |
+
self._mouse_support_enabled = True
|
| 622 |
+
|
| 623 |
+
elif not needs_mouse_support and self._mouse_support_enabled:
|
| 624 |
+
output.disable_mouse_support()
|
| 625 |
+
self._mouse_support_enabled = False
|
| 626 |
+
|
| 627 |
+
# Create screen and write layout to it.
|
| 628 |
+
size = output.get_size()
|
| 629 |
+
screen = Screen()
|
| 630 |
+
screen.show_cursor = False # Hide cursor by default, unless one of the
|
| 631 |
+
# containers decides to display it.
|
| 632 |
+
mouse_handlers = MouseHandlers()
|
| 633 |
+
|
| 634 |
+
# Calculate height.
|
| 635 |
+
if self.full_screen:
|
| 636 |
+
height = size.rows
|
| 637 |
+
elif is_done:
|
| 638 |
+
# When we are done, we don't necessary want to fill up until the bottom.
|
| 639 |
+
height = layout.container.preferred_height(
|
| 640 |
+
size.columns, size.rows
|
| 641 |
+
).preferred
|
| 642 |
+
else:
|
| 643 |
+
last_height = self._last_screen.height if self._last_screen else 0
|
| 644 |
+
height = max(
|
| 645 |
+
self._min_available_height,
|
| 646 |
+
last_height,
|
| 647 |
+
layout.container.preferred_height(size.columns, size.rows).preferred,
|
| 648 |
+
)
|
| 649 |
+
|
| 650 |
+
height = min(height, size.rows)
|
| 651 |
+
|
| 652 |
+
# When the size changes, don't consider the previous screen.
|
| 653 |
+
if self._last_size != size:
|
| 654 |
+
self._last_screen = None
|
| 655 |
+
|
| 656 |
+
# When we render using another style or another color depth, do a full
|
| 657 |
+
# repaint. (Forget about the previous rendered screen.)
|
| 658 |
+
# (But note that we still use _last_screen to calculate the height.)
|
| 659 |
+
if (
|
| 660 |
+
self.style.invalidation_hash() != self._last_style_hash
|
| 661 |
+
or app.style_transformation.invalidation_hash()
|
| 662 |
+
!= self._last_transformation_hash
|
| 663 |
+
or app.color_depth != self._last_color_depth
|
| 664 |
+
):
|
| 665 |
+
self._last_screen = None
|
| 666 |
+
self._attrs_for_style = None
|
| 667 |
+
self._style_string_has_style = None
|
| 668 |
+
|
| 669 |
+
if self._attrs_for_style is None:
|
| 670 |
+
self._attrs_for_style = _StyleStringToAttrsCache(
|
| 671 |
+
self.style.get_attrs_for_style_str, app.style_transformation
|
| 672 |
+
)
|
| 673 |
+
if self._style_string_has_style is None:
|
| 674 |
+
self._style_string_has_style = _StyleStringHasStyleCache(
|
| 675 |
+
self._attrs_for_style
|
| 676 |
+
)
|
| 677 |
+
|
| 678 |
+
self._last_style_hash = self.style.invalidation_hash()
|
| 679 |
+
self._last_transformation_hash = app.style_transformation.invalidation_hash()
|
| 680 |
+
self._last_color_depth = app.color_depth
|
| 681 |
+
|
| 682 |
+
layout.container.write_to_screen(
|
| 683 |
+
screen,
|
| 684 |
+
mouse_handlers,
|
| 685 |
+
WritePosition(xpos=0, ypos=0, width=size.columns, height=height),
|
| 686 |
+
parent_style="",
|
| 687 |
+
erase_bg=False,
|
| 688 |
+
z_index=None,
|
| 689 |
+
)
|
| 690 |
+
screen.draw_all_floats()
|
| 691 |
+
|
| 692 |
+
# When grayed. Replace all styles in the new screen.
|
| 693 |
+
if app.exit_style:
|
| 694 |
+
screen.append_style_to_content(app.exit_style)
|
| 695 |
+
|
| 696 |
+
# Process diff and write to output.
|
| 697 |
+
self._cursor_pos, self._last_style = _output_screen_diff(
|
| 698 |
+
app,
|
| 699 |
+
output,
|
| 700 |
+
screen,
|
| 701 |
+
self._cursor_pos,
|
| 702 |
+
app.color_depth,
|
| 703 |
+
self._last_screen,
|
| 704 |
+
self._last_style,
|
| 705 |
+
is_done,
|
| 706 |
+
full_screen=self.full_screen,
|
| 707 |
+
attrs_for_style_string=self._attrs_for_style,
|
| 708 |
+
style_string_has_style=self._style_string_has_style,
|
| 709 |
+
size=size,
|
| 710 |
+
previous_width=(self._last_size.columns if self._last_size else 0),
|
| 711 |
+
)
|
| 712 |
+
self._last_screen = screen
|
| 713 |
+
self._last_size = size
|
| 714 |
+
self.mouse_handlers = mouse_handlers
|
| 715 |
+
|
| 716 |
+
# Handle cursor shapes.
|
| 717 |
+
new_cursor_shape = app.cursor.get_cursor_shape(app)
|
| 718 |
+
if (
|
| 719 |
+
self._last_cursor_shape is None
|
| 720 |
+
or self._last_cursor_shape != new_cursor_shape
|
| 721 |
+
):
|
| 722 |
+
output.set_cursor_shape(new_cursor_shape)
|
| 723 |
+
self._last_cursor_shape = new_cursor_shape
|
| 724 |
+
|
| 725 |
+
# Flush buffered output.
|
| 726 |
+
output.flush()
|
| 727 |
+
|
| 728 |
+
# Set visible windows in layout.
|
| 729 |
+
app.layout.visible_windows = screen.visible_windows
|
| 730 |
+
|
| 731 |
+
if is_done:
|
| 732 |
+
self.reset()
|
| 733 |
+
|
| 734 |
+
def erase(self, leave_alternate_screen: bool = True) -> None:
|
| 735 |
+
"""
|
| 736 |
+
Hide all output and put the cursor back at the first line. This is for
|
| 737 |
+
instance used for running a system command (while hiding the CLI) and
|
| 738 |
+
later resuming the same CLI.)
|
| 739 |
+
|
| 740 |
+
:param leave_alternate_screen: When True, and when inside an alternate
|
| 741 |
+
screen buffer, quit the alternate screen.
|
| 742 |
+
"""
|
| 743 |
+
output = self.output
|
| 744 |
+
|
| 745 |
+
output.cursor_backward(self._cursor_pos.x)
|
| 746 |
+
output.cursor_up(self._cursor_pos.y)
|
| 747 |
+
output.erase_down()
|
| 748 |
+
output.reset_attributes()
|
| 749 |
+
output.enable_autowrap()
|
| 750 |
+
|
| 751 |
+
output.flush()
|
| 752 |
+
|
| 753 |
+
self.reset(leave_alternate_screen=leave_alternate_screen)
|
| 754 |
+
|
| 755 |
+
def clear(self) -> None:
|
| 756 |
+
"""
|
| 757 |
+
Clear screen and go to 0,0
|
| 758 |
+
"""
|
| 759 |
+
# Erase current output first.
|
| 760 |
+
self.erase()
|
| 761 |
+
|
| 762 |
+
# Send "Erase Screen" command and go to (0, 0).
|
| 763 |
+
output = self.output
|
| 764 |
+
|
| 765 |
+
output.erase_screen()
|
| 766 |
+
output.cursor_goto(0, 0)
|
| 767 |
+
output.flush()
|
| 768 |
+
|
| 769 |
+
self.request_absolute_cursor_position()
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
def print_formatted_text(
|
| 773 |
+
output: Output,
|
| 774 |
+
formatted_text: AnyFormattedText,
|
| 775 |
+
style: BaseStyle,
|
| 776 |
+
style_transformation: StyleTransformation | None = None,
|
| 777 |
+
color_depth: ColorDepth | None = None,
|
| 778 |
+
) -> None:
|
| 779 |
+
"""
|
| 780 |
+
Print a list of (style_str, text) tuples in the given style to the output.
|
| 781 |
+
"""
|
| 782 |
+
fragments = to_formatted_text(formatted_text)
|
| 783 |
+
style_transformation = style_transformation or DummyStyleTransformation()
|
| 784 |
+
color_depth = color_depth or output.get_default_color_depth()
|
| 785 |
+
|
| 786 |
+
# Reset first.
|
| 787 |
+
output.reset_attributes()
|
| 788 |
+
output.enable_autowrap()
|
| 789 |
+
last_attrs: Attrs | None = None
|
| 790 |
+
|
| 791 |
+
# Print all (style_str, text) tuples.
|
| 792 |
+
attrs_for_style_string = _StyleStringToAttrsCache(
|
| 793 |
+
style.get_attrs_for_style_str, style_transformation
|
| 794 |
+
)
|
| 795 |
+
|
| 796 |
+
for style_str, text, *_ in fragments:
|
| 797 |
+
attrs = attrs_for_style_string[style_str]
|
| 798 |
+
|
| 799 |
+
# Set style attributes if something changed.
|
| 800 |
+
if attrs != last_attrs:
|
| 801 |
+
if attrs:
|
| 802 |
+
output.set_attributes(attrs, color_depth)
|
| 803 |
+
else:
|
| 804 |
+
output.reset_attributes()
|
| 805 |
+
last_attrs = attrs
|
| 806 |
+
|
| 807 |
+
# Print escape sequences as raw output
|
| 808 |
+
if "[ZeroWidthEscape]" in style_str:
|
| 809 |
+
output.write_raw(text)
|
| 810 |
+
else:
|
| 811 |
+
# Eliminate carriage returns
|
| 812 |
+
text = text.replace("\r", "")
|
| 813 |
+
# Insert a carriage return before every newline (important when the
|
| 814 |
+
# front-end is a telnet client).
|
| 815 |
+
text = text.replace("\n", "\r\n")
|
| 816 |
+
output.write(text)
|
| 817 |
+
|
| 818 |
+
# Reset again.
|
| 819 |
+
output.reset_attributes()
|
| 820 |
+
output.flush()
|
.venv/Lib/site-packages/prompt_toolkit/search.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Search operations.
|
| 3 |
+
|
| 4 |
+
For the key bindings implementation with attached filters, check
|
| 5 |
+
`prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings
|
| 6 |
+
instead of calling these function directly.)
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from enum import Enum
|
| 12 |
+
from typing import TYPE_CHECKING
|
| 13 |
+
|
| 14 |
+
from .application.current import get_app
|
| 15 |
+
from .filters import FilterOrBool, is_searching, to_filter
|
| 16 |
+
from .key_binding.vi_state import InputMode
|
| 17 |
+
|
| 18 |
+
if TYPE_CHECKING:
|
| 19 |
+
from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl
|
| 20 |
+
from prompt_toolkit.layout.layout import Layout
|
| 21 |
+
|
| 22 |
+
__all__ = [
|
| 23 |
+
"SearchDirection",
|
| 24 |
+
"start_search",
|
| 25 |
+
"stop_search",
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class SearchDirection(Enum):
|
| 30 |
+
FORWARD = "FORWARD"
|
| 31 |
+
BACKWARD = "BACKWARD"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class SearchState:
|
| 35 |
+
"""
|
| 36 |
+
A search 'query', associated with a search field (like a SearchToolbar).
|
| 37 |
+
|
| 38 |
+
Every searchable `BufferControl` points to a `search_buffer_control`
|
| 39 |
+
(another `BufferControls`) which represents the search field. The
|
| 40 |
+
`SearchState` attached to that search field is used for storing the current
|
| 41 |
+
search query.
|
| 42 |
+
|
| 43 |
+
It is possible to have one searchfield for multiple `BufferControls`. In
|
| 44 |
+
that case, they'll share the same `SearchState`.
|
| 45 |
+
If there are multiple `BufferControls` that display the same `Buffer`, then
|
| 46 |
+
they can have a different `SearchState` each (if they have a different
|
| 47 |
+
search control).
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
__slots__ = ("text", "direction", "ignore_case")
|
| 51 |
+
|
| 52 |
+
def __init__(
|
| 53 |
+
self,
|
| 54 |
+
text: str = "",
|
| 55 |
+
direction: SearchDirection = SearchDirection.FORWARD,
|
| 56 |
+
ignore_case: FilterOrBool = False,
|
| 57 |
+
) -> None:
|
| 58 |
+
self.text = text
|
| 59 |
+
self.direction = direction
|
| 60 |
+
self.ignore_case = to_filter(ignore_case)
|
| 61 |
+
|
| 62 |
+
def __repr__(self) -> str:
|
| 63 |
+
return f"{self.__class__.__name__}({self.text!r}, direction={self.direction!r}, ignore_case={self.ignore_case!r})"
|
| 64 |
+
|
| 65 |
+
def __invert__(self) -> SearchState:
|
| 66 |
+
"""
|
| 67 |
+
Create a new SearchState where backwards becomes forwards and the other
|
| 68 |
+
way around.
|
| 69 |
+
"""
|
| 70 |
+
if self.direction == SearchDirection.BACKWARD:
|
| 71 |
+
direction = SearchDirection.FORWARD
|
| 72 |
+
else:
|
| 73 |
+
direction = SearchDirection.BACKWARD
|
| 74 |
+
|
| 75 |
+
return SearchState(
|
| 76 |
+
text=self.text, direction=direction, ignore_case=self.ignore_case
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def start_search(
|
| 81 |
+
buffer_control: BufferControl | None = None,
|
| 82 |
+
direction: SearchDirection = SearchDirection.FORWARD,
|
| 83 |
+
) -> None:
|
| 84 |
+
"""
|
| 85 |
+
Start search through the given `buffer_control` using the
|
| 86 |
+
`search_buffer_control`.
|
| 87 |
+
|
| 88 |
+
:param buffer_control: Start search for this `BufferControl`. If not given,
|
| 89 |
+
search through the current control.
|
| 90 |
+
"""
|
| 91 |
+
from prompt_toolkit.layout.controls import BufferControl
|
| 92 |
+
|
| 93 |
+
assert buffer_control is None or isinstance(buffer_control, BufferControl)
|
| 94 |
+
|
| 95 |
+
layout = get_app().layout
|
| 96 |
+
|
| 97 |
+
# When no control is given, use the current control if that's a BufferControl.
|
| 98 |
+
if buffer_control is None:
|
| 99 |
+
if not isinstance(layout.current_control, BufferControl):
|
| 100 |
+
return
|
| 101 |
+
buffer_control = layout.current_control
|
| 102 |
+
|
| 103 |
+
# Only if this control is searchable.
|
| 104 |
+
search_buffer_control = buffer_control.search_buffer_control
|
| 105 |
+
|
| 106 |
+
if search_buffer_control:
|
| 107 |
+
buffer_control.search_state.direction = direction
|
| 108 |
+
|
| 109 |
+
# Make sure to focus the search BufferControl
|
| 110 |
+
layout.focus(search_buffer_control)
|
| 111 |
+
|
| 112 |
+
# Remember search link.
|
| 113 |
+
layout.search_links[search_buffer_control] = buffer_control
|
| 114 |
+
|
| 115 |
+
# If we're in Vi mode, make sure to go into insert mode.
|
| 116 |
+
get_app().vi_state.input_mode = InputMode.INSERT
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def stop_search(buffer_control: BufferControl | None = None) -> None:
|
| 120 |
+
"""
|
| 121 |
+
Stop search through the given `buffer_control`.
|
| 122 |
+
"""
|
| 123 |
+
layout = get_app().layout
|
| 124 |
+
|
| 125 |
+
if buffer_control is None:
|
| 126 |
+
buffer_control = layout.search_target_buffer_control
|
| 127 |
+
if buffer_control is None:
|
| 128 |
+
# (Should not happen, but possible when `stop_search` is called
|
| 129 |
+
# when we're not searching.)
|
| 130 |
+
return
|
| 131 |
+
search_buffer_control = buffer_control.search_buffer_control
|
| 132 |
+
else:
|
| 133 |
+
assert buffer_control in layout.search_links.values()
|
| 134 |
+
search_buffer_control = _get_reverse_search_links(layout)[buffer_control]
|
| 135 |
+
|
| 136 |
+
# Focus the original buffer again.
|
| 137 |
+
layout.focus(buffer_control)
|
| 138 |
+
|
| 139 |
+
if search_buffer_control is not None:
|
| 140 |
+
# Remove the search link.
|
| 141 |
+
del layout.search_links[search_buffer_control]
|
| 142 |
+
|
| 143 |
+
# Reset content of search control.
|
| 144 |
+
search_buffer_control.buffer.reset()
|
| 145 |
+
|
| 146 |
+
# If we're in Vi mode, go back to navigation mode.
|
| 147 |
+
get_app().vi_state.input_mode = InputMode.NAVIGATION
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def do_incremental_search(direction: SearchDirection, count: int = 1) -> None:
|
| 151 |
+
"""
|
| 152 |
+
Apply search, but keep search buffer focused.
|
| 153 |
+
"""
|
| 154 |
+
assert is_searching()
|
| 155 |
+
|
| 156 |
+
layout = get_app().layout
|
| 157 |
+
|
| 158 |
+
# Only search if the current control is a `BufferControl`.
|
| 159 |
+
from prompt_toolkit.layout.controls import BufferControl
|
| 160 |
+
|
| 161 |
+
search_control = layout.current_control
|
| 162 |
+
if not isinstance(search_control, BufferControl):
|
| 163 |
+
return
|
| 164 |
+
|
| 165 |
+
prev_control = layout.search_target_buffer_control
|
| 166 |
+
if prev_control is None:
|
| 167 |
+
return
|
| 168 |
+
search_state = prev_control.search_state
|
| 169 |
+
|
| 170 |
+
# Update search_state.
|
| 171 |
+
direction_changed = search_state.direction != direction
|
| 172 |
+
|
| 173 |
+
search_state.text = search_control.buffer.text
|
| 174 |
+
search_state.direction = direction
|
| 175 |
+
|
| 176 |
+
# Apply search to current buffer.
|
| 177 |
+
if not direction_changed:
|
| 178 |
+
prev_control.buffer.apply_search(
|
| 179 |
+
search_state, include_current_position=False, count=count
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def accept_search() -> None:
|
| 184 |
+
"""
|
| 185 |
+
Accept current search query. Focus original `BufferControl` again.
|
| 186 |
+
"""
|
| 187 |
+
layout = get_app().layout
|
| 188 |
+
|
| 189 |
+
search_control = layout.current_control
|
| 190 |
+
target_buffer_control = layout.search_target_buffer_control
|
| 191 |
+
|
| 192 |
+
from prompt_toolkit.layout.controls import BufferControl
|
| 193 |
+
|
| 194 |
+
if not isinstance(search_control, BufferControl):
|
| 195 |
+
return
|
| 196 |
+
if target_buffer_control is None:
|
| 197 |
+
return
|
| 198 |
+
|
| 199 |
+
search_state = target_buffer_control.search_state
|
| 200 |
+
|
| 201 |
+
# Update search state.
|
| 202 |
+
if search_control.buffer.text:
|
| 203 |
+
search_state.text = search_control.buffer.text
|
| 204 |
+
|
| 205 |
+
# Apply search.
|
| 206 |
+
target_buffer_control.buffer.apply_search(
|
| 207 |
+
search_state, include_current_position=True
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
# Add query to history of search line.
|
| 211 |
+
search_control.buffer.append_to_history()
|
| 212 |
+
|
| 213 |
+
# Stop search and focus previous control again.
|
| 214 |
+
stop_search(target_buffer_control)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def _get_reverse_search_links(
|
| 218 |
+
layout: Layout,
|
| 219 |
+
) -> dict[BufferControl, SearchBufferControl]:
|
| 220 |
+
"""
|
| 221 |
+
Return mapping from BufferControl to SearchBufferControl.
|
| 222 |
+
"""
|
| 223 |
+
return {
|
| 224 |
+
buffer_control: search_buffer_control
|
| 225 |
+
for search_buffer_control, buffer_control in layout.search_links.items()
|
| 226 |
+
}
|
.venv/Lib/site-packages/prompt_toolkit/selection.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data structures for the selection.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
from enum import Enum
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"SelectionType",
|
| 11 |
+
"PasteMode",
|
| 12 |
+
"SelectionState",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class SelectionType(Enum):
|
| 17 |
+
"""
|
| 18 |
+
Type of selection.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
#: Characters. (Visual in Vi.)
|
| 22 |
+
CHARACTERS = "CHARACTERS"
|
| 23 |
+
|
| 24 |
+
#: Whole lines. (Visual-Line in Vi.)
|
| 25 |
+
LINES = "LINES"
|
| 26 |
+
|
| 27 |
+
#: A block selection. (Visual-Block in Vi.)
|
| 28 |
+
BLOCK = "BLOCK"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class PasteMode(Enum):
|
| 32 |
+
EMACS = "EMACS" # Yank like emacs.
|
| 33 |
+
VI_AFTER = "VI_AFTER" # When pressing 'p' in Vi.
|
| 34 |
+
VI_BEFORE = "VI_BEFORE" # When pressing 'P' in Vi.
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class SelectionState:
|
| 38 |
+
"""
|
| 39 |
+
State of the current selection.
|
| 40 |
+
|
| 41 |
+
:param original_cursor_position: int
|
| 42 |
+
:param type: :class:`~.SelectionType`
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
def __init__(
|
| 46 |
+
self,
|
| 47 |
+
original_cursor_position: int = 0,
|
| 48 |
+
type: SelectionType = SelectionType.CHARACTERS,
|
| 49 |
+
) -> None:
|
| 50 |
+
self.original_cursor_position = original_cursor_position
|
| 51 |
+
self.type = type
|
| 52 |
+
self.shift_mode = False
|
| 53 |
+
|
| 54 |
+
def enter_shift_mode(self) -> None:
|
| 55 |
+
self.shift_mode = True
|
| 56 |
+
|
| 57 |
+
def __repr__(self) -> str:
|
| 58 |
+
return f"{self.__class__.__name__}(original_cursor_position={self.original_cursor_position!r}, type={self.type!r})"
|
.venv/Lib/site-packages/prompt_toolkit/token.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" """
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
"ZeroWidthEscape",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
ZeroWidthEscape = "[ZeroWidthEscape]"
|
.venv/Lib/site-packages/prompt_toolkit/utils.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import signal
|
| 5 |
+
import sys
|
| 6 |
+
import threading
|
| 7 |
+
from collections import deque
|
| 8 |
+
from typing import (
|
| 9 |
+
Callable,
|
| 10 |
+
ContextManager,
|
| 11 |
+
Dict,
|
| 12 |
+
Generator,
|
| 13 |
+
Generic,
|
| 14 |
+
TypeVar,
|
| 15 |
+
Union,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
from wcwidth import wcwidth
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
"Event",
|
| 22 |
+
"DummyContext",
|
| 23 |
+
"get_cwidth",
|
| 24 |
+
"suspend_to_background_supported",
|
| 25 |
+
"is_conemu_ansi",
|
| 26 |
+
"is_windows",
|
| 27 |
+
"in_main_thread",
|
| 28 |
+
"get_bell_environment_variable",
|
| 29 |
+
"get_term_environment_variable",
|
| 30 |
+
"take_using_weights",
|
| 31 |
+
"to_str",
|
| 32 |
+
"to_int",
|
| 33 |
+
"AnyFloat",
|
| 34 |
+
"to_float",
|
| 35 |
+
"is_dumb_terminal",
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
# Used to ensure sphinx autodoc does not try to import platform-specific
|
| 39 |
+
# stuff when documenting win32.py modules.
|
| 40 |
+
SPHINX_AUTODOC_RUNNING = "sphinx.ext.autodoc" in sys.modules
|
| 41 |
+
|
| 42 |
+
_Sender = TypeVar("_Sender", covariant=True)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class Event(Generic[_Sender]):
|
| 46 |
+
"""
|
| 47 |
+
Simple event to which event handlers can be attached. For instance::
|
| 48 |
+
|
| 49 |
+
class Cls:
|
| 50 |
+
def __init__(self):
|
| 51 |
+
# Define event. The first parameter is the sender.
|
| 52 |
+
self.event = Event(self)
|
| 53 |
+
|
| 54 |
+
obj = Cls()
|
| 55 |
+
|
| 56 |
+
def handler(sender):
|
| 57 |
+
pass
|
| 58 |
+
|
| 59 |
+
# Add event handler by using the += operator.
|
| 60 |
+
obj.event += handler
|
| 61 |
+
|
| 62 |
+
# Fire event.
|
| 63 |
+
obj.event()
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self, sender: _Sender, handler: Callable[[_Sender], None] | None = None
|
| 68 |
+
) -> None:
|
| 69 |
+
self.sender = sender
|
| 70 |
+
self._handlers: list[Callable[[_Sender], None]] = []
|
| 71 |
+
|
| 72 |
+
if handler is not None:
|
| 73 |
+
self += handler
|
| 74 |
+
|
| 75 |
+
def __call__(self) -> None:
|
| 76 |
+
"Fire event."
|
| 77 |
+
for handler in self._handlers:
|
| 78 |
+
handler(self.sender)
|
| 79 |
+
|
| 80 |
+
def fire(self) -> None:
|
| 81 |
+
"Alias for just calling the event."
|
| 82 |
+
self()
|
| 83 |
+
|
| 84 |
+
def add_handler(self, handler: Callable[[_Sender], None]) -> None:
|
| 85 |
+
"""
|
| 86 |
+
Add another handler to this callback.
|
| 87 |
+
(Handler should be a callable that takes exactly one parameter: the
|
| 88 |
+
sender object.)
|
| 89 |
+
"""
|
| 90 |
+
# Add to list of event handlers.
|
| 91 |
+
self._handlers.append(handler)
|
| 92 |
+
|
| 93 |
+
def remove_handler(self, handler: Callable[[_Sender], None]) -> None:
|
| 94 |
+
"""
|
| 95 |
+
Remove a handler from this callback.
|
| 96 |
+
"""
|
| 97 |
+
if handler in self._handlers:
|
| 98 |
+
self._handlers.remove(handler)
|
| 99 |
+
|
| 100 |
+
def __iadd__(self, handler: Callable[[_Sender], None]) -> Event[_Sender]:
|
| 101 |
+
"""
|
| 102 |
+
`event += handler` notation for adding a handler.
|
| 103 |
+
"""
|
| 104 |
+
self.add_handler(handler)
|
| 105 |
+
return self
|
| 106 |
+
|
| 107 |
+
def __isub__(self, handler: Callable[[_Sender], None]) -> Event[_Sender]:
|
| 108 |
+
"""
|
| 109 |
+
`event -= handler` notation for removing a handler.
|
| 110 |
+
"""
|
| 111 |
+
self.remove_handler(handler)
|
| 112 |
+
return self
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class DummyContext(ContextManager[None]):
|
| 116 |
+
"""
|
| 117 |
+
(contextlib.nested is not available on Py3)
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
def __enter__(self) -> None:
|
| 121 |
+
pass
|
| 122 |
+
|
| 123 |
+
def __exit__(self, *a: object) -> None:
|
| 124 |
+
pass
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class _CharSizesCache(Dict[str, int]):
|
| 128 |
+
"""
|
| 129 |
+
Cache for wcwidth sizes.
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
LONG_STRING_MIN_LEN = 64 # Minimum string length for considering it long.
|
| 133 |
+
MAX_LONG_STRINGS = 16 # Maximum number of long strings to remember.
|
| 134 |
+
|
| 135 |
+
def __init__(self) -> None:
|
| 136 |
+
super().__init__()
|
| 137 |
+
# Keep track of the "long" strings in this cache.
|
| 138 |
+
self._long_strings: deque[str] = deque()
|
| 139 |
+
|
| 140 |
+
def __missing__(self, string: str) -> int:
|
| 141 |
+
# Note: We use the `max(0, ...` because some non printable control
|
| 142 |
+
# characters, like e.g. Ctrl-underscore get a -1 wcwidth value.
|
| 143 |
+
# It can be possible that these characters end up in the input
|
| 144 |
+
# text.
|
| 145 |
+
result: int
|
| 146 |
+
if len(string) == 1:
|
| 147 |
+
result = max(0, wcwidth(string))
|
| 148 |
+
else:
|
| 149 |
+
result = sum(self[c] for c in string)
|
| 150 |
+
|
| 151 |
+
# Store in cache.
|
| 152 |
+
self[string] = result
|
| 153 |
+
|
| 154 |
+
# Rotate long strings.
|
| 155 |
+
# (It's hard to tell what we can consider short...)
|
| 156 |
+
if len(string) > self.LONG_STRING_MIN_LEN:
|
| 157 |
+
long_strings = self._long_strings
|
| 158 |
+
long_strings.append(string)
|
| 159 |
+
|
| 160 |
+
if len(long_strings) > self.MAX_LONG_STRINGS:
|
| 161 |
+
key_to_remove = long_strings.popleft()
|
| 162 |
+
if key_to_remove in self:
|
| 163 |
+
del self[key_to_remove]
|
| 164 |
+
|
| 165 |
+
return result
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
_CHAR_SIZES_CACHE = _CharSizesCache()
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def get_cwidth(string: str) -> int:
|
| 172 |
+
"""
|
| 173 |
+
Return width of a string. Wrapper around ``wcwidth``.
|
| 174 |
+
"""
|
| 175 |
+
return _CHAR_SIZES_CACHE[string]
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def suspend_to_background_supported() -> bool:
|
| 179 |
+
"""
|
| 180 |
+
Returns `True` when the Python implementation supports
|
| 181 |
+
suspend-to-background. This is typically `False' on Windows systems.
|
| 182 |
+
"""
|
| 183 |
+
return hasattr(signal, "SIGTSTP")
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def is_windows() -> bool:
|
| 187 |
+
"""
|
| 188 |
+
True when we are using Windows.
|
| 189 |
+
"""
|
| 190 |
+
return sys.platform == "win32" # Not 'darwin' or 'linux2'
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def is_windows_vt100_supported() -> bool:
|
| 194 |
+
"""
|
| 195 |
+
True when we are using Windows, but VT100 escape sequences are supported.
|
| 196 |
+
"""
|
| 197 |
+
if sys.platform == "win32":
|
| 198 |
+
# Import needs to be inline. Windows libraries are not always available.
|
| 199 |
+
from prompt_toolkit.output.windows10 import is_win_vt100_enabled
|
| 200 |
+
|
| 201 |
+
return is_win_vt100_enabled()
|
| 202 |
+
|
| 203 |
+
return False
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def is_conemu_ansi() -> bool:
|
| 207 |
+
"""
|
| 208 |
+
True when the ConEmu Windows console is used.
|
| 209 |
+
"""
|
| 210 |
+
return sys.platform == "win32" and os.environ.get("ConEmuANSI", "OFF") == "ON"
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def in_main_thread() -> bool:
|
| 214 |
+
"""
|
| 215 |
+
True when the current thread is the main thread.
|
| 216 |
+
"""
|
| 217 |
+
return threading.current_thread().__class__.__name__ == "_MainThread"
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def get_bell_environment_variable() -> bool:
|
| 221 |
+
"""
|
| 222 |
+
True if env variable is set to true (true, TRUE, True, 1).
|
| 223 |
+
"""
|
| 224 |
+
value = os.environ.get("PROMPT_TOOLKIT_BELL", "true")
|
| 225 |
+
return value.lower() in ("1", "true")
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def get_term_environment_variable() -> str:
|
| 229 |
+
"Return the $TERM environment variable."
|
| 230 |
+
return os.environ.get("TERM", "")
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
_T = TypeVar("_T")
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def take_using_weights(
|
| 237 |
+
items: list[_T], weights: list[int]
|
| 238 |
+
) -> Generator[_T, None, None]:
|
| 239 |
+
"""
|
| 240 |
+
Generator that keeps yielding items from the items list, in proportion to
|
| 241 |
+
their weight. For instance::
|
| 242 |
+
|
| 243 |
+
# Getting the first 70 items from this generator should have yielded 10
|
| 244 |
+
# times A, 20 times B and 40 times C, all distributed equally..
|
| 245 |
+
take_using_weights(['A', 'B', 'C'], [5, 10, 20])
|
| 246 |
+
|
| 247 |
+
:param items: List of items to take from.
|
| 248 |
+
:param weights: Integers representing the weight. (Numbers have to be
|
| 249 |
+
integers, not floats.)
|
| 250 |
+
"""
|
| 251 |
+
assert len(items) == len(weights)
|
| 252 |
+
assert len(items) > 0
|
| 253 |
+
|
| 254 |
+
# Remove items with zero-weight.
|
| 255 |
+
items2 = []
|
| 256 |
+
weights2 = []
|
| 257 |
+
for item, w in zip(items, weights):
|
| 258 |
+
if w > 0:
|
| 259 |
+
items2.append(item)
|
| 260 |
+
weights2.append(w)
|
| 261 |
+
|
| 262 |
+
items = items2
|
| 263 |
+
weights = weights2
|
| 264 |
+
|
| 265 |
+
# Make sure that we have some items left.
|
| 266 |
+
if not items:
|
| 267 |
+
raise ValueError("Did't got any items with a positive weight.")
|
| 268 |
+
|
| 269 |
+
#
|
| 270 |
+
already_taken = [0 for i in items]
|
| 271 |
+
item_count = len(items)
|
| 272 |
+
max_weight = max(weights)
|
| 273 |
+
|
| 274 |
+
i = 0
|
| 275 |
+
while True:
|
| 276 |
+
# Each iteration of this loop, we fill up until by (total_weight/max_weight).
|
| 277 |
+
adding = True
|
| 278 |
+
while adding:
|
| 279 |
+
adding = False
|
| 280 |
+
|
| 281 |
+
for item_i, item, weight in zip(range(item_count), items, weights):
|
| 282 |
+
if already_taken[item_i] < i * weight / float(max_weight):
|
| 283 |
+
yield item
|
| 284 |
+
already_taken[item_i] += 1
|
| 285 |
+
adding = True
|
| 286 |
+
|
| 287 |
+
i += 1
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def to_str(value: Callable[[], str] | str) -> str:
|
| 291 |
+
"Turn callable or string into string."
|
| 292 |
+
if callable(value):
|
| 293 |
+
return to_str(value())
|
| 294 |
+
else:
|
| 295 |
+
return str(value)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def to_int(value: Callable[[], int] | int) -> int:
|
| 299 |
+
"Turn callable or int into int."
|
| 300 |
+
if callable(value):
|
| 301 |
+
return to_int(value())
|
| 302 |
+
else:
|
| 303 |
+
return int(value)
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
AnyFloat = Union[Callable[[], float], float]
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def to_float(value: AnyFloat) -> float:
|
| 310 |
+
"Turn callable or float into float."
|
| 311 |
+
if callable(value):
|
| 312 |
+
return to_float(value())
|
| 313 |
+
else:
|
| 314 |
+
return float(value)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def is_dumb_terminal(term: str | None = None) -> bool:
|
| 318 |
+
"""
|
| 319 |
+
True if this terminal type is considered "dumb".
|
| 320 |
+
|
| 321 |
+
If so, we should fall back to the simplest possible form of line editing,
|
| 322 |
+
without cursor positioning and color support.
|
| 323 |
+
"""
|
| 324 |
+
if term is None:
|
| 325 |
+
return is_dumb_terminal(os.environ.get("TERM", ""))
|
| 326 |
+
|
| 327 |
+
return term.lower() in ["dumb", "unknown"]
|
.venv/Lib/site-packages/prompt_toolkit/validation.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Input validation for a `Buffer`.
|
| 3 |
+
(Validators will be called before accepting input.)
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from abc import ABCMeta, abstractmethod
|
| 9 |
+
from typing import Callable
|
| 10 |
+
|
| 11 |
+
from prompt_toolkit.eventloop import run_in_executor_with_context
|
| 12 |
+
|
| 13 |
+
from .document import Document
|
| 14 |
+
from .filters import FilterOrBool, to_filter
|
| 15 |
+
|
| 16 |
+
__all__ = [
|
| 17 |
+
"ConditionalValidator",
|
| 18 |
+
"ValidationError",
|
| 19 |
+
"Validator",
|
| 20 |
+
"ThreadedValidator",
|
| 21 |
+
"DummyValidator",
|
| 22 |
+
"DynamicValidator",
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class ValidationError(Exception):
|
| 27 |
+
"""
|
| 28 |
+
Error raised by :meth:`.Validator.validate`.
|
| 29 |
+
|
| 30 |
+
:param cursor_position: The cursor position where the error occurred.
|
| 31 |
+
:param message: Text.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self, cursor_position: int = 0, message: str = "") -> None:
|
| 35 |
+
super().__init__(message)
|
| 36 |
+
self.cursor_position = cursor_position
|
| 37 |
+
self.message = message
|
| 38 |
+
|
| 39 |
+
def __repr__(self) -> str:
|
| 40 |
+
return f"{self.__class__.__name__}(cursor_position={self.cursor_position!r}, message={self.message!r})"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class Validator(metaclass=ABCMeta):
|
| 44 |
+
"""
|
| 45 |
+
Abstract base class for an input validator.
|
| 46 |
+
|
| 47 |
+
A validator is typically created in one of the following two ways:
|
| 48 |
+
|
| 49 |
+
- Either by overriding this class and implementing the `validate` method.
|
| 50 |
+
- Or by passing a callable to `Validator.from_callable`.
|
| 51 |
+
|
| 52 |
+
If the validation takes some time and needs to happen in a background
|
| 53 |
+
thread, this can be wrapped in a :class:`.ThreadedValidator`.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
@abstractmethod
|
| 57 |
+
def validate(self, document: Document) -> None:
|
| 58 |
+
"""
|
| 59 |
+
Validate the input.
|
| 60 |
+
If invalid, this should raise a :class:`.ValidationError`.
|
| 61 |
+
|
| 62 |
+
:param document: :class:`~prompt_toolkit.document.Document` instance.
|
| 63 |
+
"""
|
| 64 |
+
pass
|
| 65 |
+
|
| 66 |
+
async def validate_async(self, document: Document) -> None:
|
| 67 |
+
"""
|
| 68 |
+
Return a `Future` which is set when the validation is ready.
|
| 69 |
+
This function can be overloaded in order to provide an asynchronous
|
| 70 |
+
implementation.
|
| 71 |
+
"""
|
| 72 |
+
try:
|
| 73 |
+
self.validate(document)
|
| 74 |
+
except ValidationError:
|
| 75 |
+
raise
|
| 76 |
+
|
| 77 |
+
@classmethod
|
| 78 |
+
def from_callable(
|
| 79 |
+
cls,
|
| 80 |
+
validate_func: Callable[[str], bool],
|
| 81 |
+
error_message: str = "Invalid input",
|
| 82 |
+
move_cursor_to_end: bool = False,
|
| 83 |
+
) -> Validator:
|
| 84 |
+
"""
|
| 85 |
+
Create a validator from a simple validate callable. E.g.:
|
| 86 |
+
|
| 87 |
+
.. code:: python
|
| 88 |
+
|
| 89 |
+
def is_valid(text):
|
| 90 |
+
return text in ['hello', 'world']
|
| 91 |
+
Validator.from_callable(is_valid, error_message='Invalid input')
|
| 92 |
+
|
| 93 |
+
:param validate_func: Callable that takes the input string, and returns
|
| 94 |
+
`True` if the input is valid input.
|
| 95 |
+
:param error_message: Message to be displayed if the input is invalid.
|
| 96 |
+
:param move_cursor_to_end: Move the cursor to the end of the input, if
|
| 97 |
+
the input is invalid.
|
| 98 |
+
"""
|
| 99 |
+
return _ValidatorFromCallable(validate_func, error_message, move_cursor_to_end)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class _ValidatorFromCallable(Validator):
|
| 103 |
+
"""
|
| 104 |
+
Validate input from a simple callable.
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
def __init__(
|
| 108 |
+
self, func: Callable[[str], bool], error_message: str, move_cursor_to_end: bool
|
| 109 |
+
) -> None:
|
| 110 |
+
self.func = func
|
| 111 |
+
self.error_message = error_message
|
| 112 |
+
self.move_cursor_to_end = move_cursor_to_end
|
| 113 |
+
|
| 114 |
+
def __repr__(self) -> str:
|
| 115 |
+
return f"Validator.from_callable({self.func!r})"
|
| 116 |
+
|
| 117 |
+
def validate(self, document: Document) -> None:
|
| 118 |
+
if not self.func(document.text):
|
| 119 |
+
if self.move_cursor_to_end:
|
| 120 |
+
index = len(document.text)
|
| 121 |
+
else:
|
| 122 |
+
index = 0
|
| 123 |
+
|
| 124 |
+
raise ValidationError(cursor_position=index, message=self.error_message)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class ThreadedValidator(Validator):
|
| 128 |
+
"""
|
| 129 |
+
Wrapper that runs input validation in a thread.
|
| 130 |
+
(Use this to prevent the user interface from becoming unresponsive if the
|
| 131 |
+
input validation takes too much time.)
|
| 132 |
+
"""
|
| 133 |
+
|
| 134 |
+
def __init__(self, validator: Validator) -> None:
|
| 135 |
+
self.validator = validator
|
| 136 |
+
|
| 137 |
+
def validate(self, document: Document) -> None:
|
| 138 |
+
self.validator.validate(document)
|
| 139 |
+
|
| 140 |
+
async def validate_async(self, document: Document) -> None:
|
| 141 |
+
"""
|
| 142 |
+
Run the `validate` function in a thread.
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
def run_validation_thread() -> None:
|
| 146 |
+
return self.validate(document)
|
| 147 |
+
|
| 148 |
+
await run_in_executor_with_context(run_validation_thread)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class DummyValidator(Validator):
|
| 152 |
+
"""
|
| 153 |
+
Validator class that accepts any input.
|
| 154 |
+
"""
|
| 155 |
+
|
| 156 |
+
def validate(self, document: Document) -> None:
|
| 157 |
+
pass # Don't raise any exception.
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class ConditionalValidator(Validator):
|
| 161 |
+
"""
|
| 162 |
+
Validator that can be switched on/off according to
|
| 163 |
+
a filter. (This wraps around another validator.)
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(self, validator: Validator, filter: FilterOrBool) -> None:
|
| 167 |
+
self.validator = validator
|
| 168 |
+
self.filter = to_filter(filter)
|
| 169 |
+
|
| 170 |
+
def validate(self, document: Document) -> None:
|
| 171 |
+
# Call the validator only if the filter is active.
|
| 172 |
+
if self.filter():
|
| 173 |
+
self.validator.validate(document)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
class DynamicValidator(Validator):
|
| 177 |
+
"""
|
| 178 |
+
Validator class that can dynamically returns any Validator.
|
| 179 |
+
|
| 180 |
+
:param get_validator: Callable that returns a :class:`.Validator` instance.
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
def __init__(self, get_validator: Callable[[], Validator | None]) -> None:
|
| 184 |
+
self.get_validator = get_validator
|
| 185 |
+
|
| 186 |
+
def validate(self, document: Document) -> None:
|
| 187 |
+
validator = self.get_validator() or DummyValidator()
|
| 188 |
+
validator.validate(document)
|
| 189 |
+
|
| 190 |
+
async def validate_async(self, document: Document) -> None:
|
| 191 |
+
validator = self.get_validator() or DummyValidator()
|
| 192 |
+
await validator.validate_async(document)
|
.venv/Lib/site-packages/prompt_toolkit/win32_types.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from ctypes import Structure, Union, c_char, c_long, c_short, c_ulong
|
| 4 |
+
from ctypes.wintypes import BOOL, DWORD, LPVOID, WCHAR, WORD
|
| 5 |
+
from typing import TYPE_CHECKING
|
| 6 |
+
|
| 7 |
+
# Input/Output standard device numbers. Note that these are not handle objects.
|
| 8 |
+
# It's the `windll.kernel32.GetStdHandle` system call that turns them into a
|
| 9 |
+
# real handle object.
|
| 10 |
+
STD_INPUT_HANDLE = c_ulong(-10)
|
| 11 |
+
STD_OUTPUT_HANDLE = c_ulong(-11)
|
| 12 |
+
STD_ERROR_HANDLE = c_ulong(-12)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class COORD(Structure):
|
| 16 |
+
"""
|
| 17 |
+
Struct in wincon.h
|
| 18 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
if TYPE_CHECKING:
|
| 22 |
+
X: int
|
| 23 |
+
Y: int
|
| 24 |
+
|
| 25 |
+
_fields_ = [
|
| 26 |
+
("X", c_short), # Short
|
| 27 |
+
("Y", c_short), # Short
|
| 28 |
+
]
|
| 29 |
+
|
| 30 |
+
def __repr__(self) -> str:
|
| 31 |
+
return "{}(X={!r}, Y={!r}, type_x={!r}, type_y={!r})".format(
|
| 32 |
+
self.__class__.__name__,
|
| 33 |
+
self.X,
|
| 34 |
+
self.Y,
|
| 35 |
+
type(self.X),
|
| 36 |
+
type(self.Y),
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class UNICODE_OR_ASCII(Union):
|
| 41 |
+
if TYPE_CHECKING:
|
| 42 |
+
AsciiChar: bytes
|
| 43 |
+
UnicodeChar: str
|
| 44 |
+
|
| 45 |
+
_fields_ = [
|
| 46 |
+
("AsciiChar", c_char),
|
| 47 |
+
("UnicodeChar", WCHAR),
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class KEY_EVENT_RECORD(Structure):
|
| 52 |
+
"""
|
| 53 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
if TYPE_CHECKING:
|
| 57 |
+
KeyDown: int
|
| 58 |
+
RepeatCount: int
|
| 59 |
+
VirtualKeyCode: int
|
| 60 |
+
VirtualScanCode: int
|
| 61 |
+
uChar: UNICODE_OR_ASCII
|
| 62 |
+
ControlKeyState: int
|
| 63 |
+
|
| 64 |
+
_fields_ = [
|
| 65 |
+
("KeyDown", c_long), # bool
|
| 66 |
+
("RepeatCount", c_short), # word
|
| 67 |
+
("VirtualKeyCode", c_short), # word
|
| 68 |
+
("VirtualScanCode", c_short), # word
|
| 69 |
+
("uChar", UNICODE_OR_ASCII), # Unicode or ASCII.
|
| 70 |
+
("ControlKeyState", c_long), # double word
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class MOUSE_EVENT_RECORD(Structure):
|
| 75 |
+
"""
|
| 76 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684239(v=vs.85).aspx
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
if TYPE_CHECKING:
|
| 80 |
+
MousePosition: COORD
|
| 81 |
+
ButtonState: int
|
| 82 |
+
ControlKeyState: int
|
| 83 |
+
EventFlags: int
|
| 84 |
+
|
| 85 |
+
_fields_ = [
|
| 86 |
+
("MousePosition", COORD),
|
| 87 |
+
("ButtonState", c_long), # dword
|
| 88 |
+
("ControlKeyState", c_long), # dword
|
| 89 |
+
("EventFlags", c_long), # dword
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class WINDOW_BUFFER_SIZE_RECORD(Structure):
|
| 94 |
+
"""
|
| 95 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms687093(v=vs.85).aspx
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
if TYPE_CHECKING:
|
| 99 |
+
Size: COORD
|
| 100 |
+
|
| 101 |
+
_fields_ = [("Size", COORD)]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class MENU_EVENT_RECORD(Structure):
|
| 105 |
+
"""
|
| 106 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684213(v=vs.85).aspx
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
if TYPE_CHECKING:
|
| 110 |
+
CommandId: int
|
| 111 |
+
|
| 112 |
+
_fields_ = [("CommandId", c_long)] # uint
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class FOCUS_EVENT_RECORD(Structure):
|
| 116 |
+
"""
|
| 117 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms683149(v=vs.85).aspx
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
if TYPE_CHECKING:
|
| 121 |
+
SetFocus: int
|
| 122 |
+
|
| 123 |
+
_fields_ = [("SetFocus", c_long)] # bool
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class EVENT_RECORD(Union):
|
| 127 |
+
if TYPE_CHECKING:
|
| 128 |
+
KeyEvent: KEY_EVENT_RECORD
|
| 129 |
+
MouseEvent: MOUSE_EVENT_RECORD
|
| 130 |
+
WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD
|
| 131 |
+
MenuEvent: MENU_EVENT_RECORD
|
| 132 |
+
FocusEvent: FOCUS_EVENT_RECORD
|
| 133 |
+
|
| 134 |
+
_fields_ = [
|
| 135 |
+
("KeyEvent", KEY_EVENT_RECORD),
|
| 136 |
+
("MouseEvent", MOUSE_EVENT_RECORD),
|
| 137 |
+
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
|
| 138 |
+
("MenuEvent", MENU_EVENT_RECORD),
|
| 139 |
+
("FocusEvent", FOCUS_EVENT_RECORD),
|
| 140 |
+
]
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class INPUT_RECORD(Structure):
|
| 144 |
+
"""
|
| 145 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx
|
| 146 |
+
"""
|
| 147 |
+
|
| 148 |
+
if TYPE_CHECKING:
|
| 149 |
+
EventType: int
|
| 150 |
+
Event: EVENT_RECORD
|
| 151 |
+
|
| 152 |
+
_fields_ = [("EventType", c_short), ("Event", EVENT_RECORD)] # word # Union.
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
EventTypes = {
|
| 156 |
+
1: "KeyEvent",
|
| 157 |
+
2: "MouseEvent",
|
| 158 |
+
4: "WindowBufferSizeEvent",
|
| 159 |
+
8: "MenuEvent",
|
| 160 |
+
16: "FocusEvent",
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class SMALL_RECT(Structure):
|
| 165 |
+
"""struct in wincon.h."""
|
| 166 |
+
|
| 167 |
+
if TYPE_CHECKING:
|
| 168 |
+
Left: int
|
| 169 |
+
Top: int
|
| 170 |
+
Right: int
|
| 171 |
+
Bottom: int
|
| 172 |
+
|
| 173 |
+
_fields_ = [
|
| 174 |
+
("Left", c_short),
|
| 175 |
+
("Top", c_short),
|
| 176 |
+
("Right", c_short),
|
| 177 |
+
("Bottom", c_short),
|
| 178 |
+
]
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
| 182 |
+
"""struct in wincon.h."""
|
| 183 |
+
|
| 184 |
+
if TYPE_CHECKING:
|
| 185 |
+
dwSize: COORD
|
| 186 |
+
dwCursorPosition: COORD
|
| 187 |
+
wAttributes: int
|
| 188 |
+
srWindow: SMALL_RECT
|
| 189 |
+
dwMaximumWindowSize: COORD
|
| 190 |
+
|
| 191 |
+
_fields_ = [
|
| 192 |
+
("dwSize", COORD),
|
| 193 |
+
("dwCursorPosition", COORD),
|
| 194 |
+
("wAttributes", WORD),
|
| 195 |
+
("srWindow", SMALL_RECT),
|
| 196 |
+
("dwMaximumWindowSize", COORD),
|
| 197 |
+
]
|
| 198 |
+
|
| 199 |
+
def __repr__(self) -> str:
|
| 200 |
+
return "CONSOLE_SCREEN_BUFFER_INFO({!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r},{!r})".format(
|
| 201 |
+
self.dwSize.Y,
|
| 202 |
+
self.dwSize.X,
|
| 203 |
+
self.dwCursorPosition.Y,
|
| 204 |
+
self.dwCursorPosition.X,
|
| 205 |
+
self.wAttributes,
|
| 206 |
+
self.srWindow.Top,
|
| 207 |
+
self.srWindow.Left,
|
| 208 |
+
self.srWindow.Bottom,
|
| 209 |
+
self.srWindow.Right,
|
| 210 |
+
self.dwMaximumWindowSize.Y,
|
| 211 |
+
self.dwMaximumWindowSize.X,
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class SECURITY_ATTRIBUTES(Structure):
|
| 216 |
+
"""
|
| 217 |
+
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx
|
| 218 |
+
"""
|
| 219 |
+
|
| 220 |
+
if TYPE_CHECKING:
|
| 221 |
+
nLength: int
|
| 222 |
+
lpSecurityDescriptor: int
|
| 223 |
+
bInheritHandle: int # BOOL comes back as 'int'.
|
| 224 |
+
|
| 225 |
+
_fields_ = [
|
| 226 |
+
("nLength", DWORD),
|
| 227 |
+
("lpSecurityDescriptor", LPVOID),
|
| 228 |
+
("bInheritHandle", BOOL),
|
| 229 |
+
]
|
.venv/Lib/site-packages/requests-2.32.5.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/Lib/site-packages/requests-2.32.5.dist-info/METADATA
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: requests
|
| 3 |
+
Version: 2.32.5
|
| 4 |
+
Summary: Python HTTP for Humans.
|
| 5 |
+
Home-page: https://requests.readthedocs.io
|
| 6 |
+
Author: Kenneth Reitz
|
| 7 |
+
Author-email: me@kennethreitz.org
|
| 8 |
+
License: Apache-2.0
|
| 9 |
+
Project-URL: Documentation, https://requests.readthedocs.io
|
| 10 |
+
Project-URL: Source, https://github.com/psf/requests
|
| 11 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 12 |
+
Classifier: Environment :: Web Environment
|
| 13 |
+
Classifier: Intended Audience :: Developers
|
| 14 |
+
Classifier: License :: OSI Approved :: Apache Software License
|
| 15 |
+
Classifier: Natural Language :: English
|
| 16 |
+
Classifier: Operating System :: OS Independent
|
| 17 |
+
Classifier: Programming Language :: Python
|
| 18 |
+
Classifier: Programming Language :: Python :: 3
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 25 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 26 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 27 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 28 |
+
Classifier: Topic :: Internet :: WWW/HTTP
|
| 29 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 30 |
+
Requires-Python: >=3.9
|
| 31 |
+
Description-Content-Type: text/markdown
|
| 32 |
+
License-File: LICENSE
|
| 33 |
+
Requires-Dist: charset_normalizer<4,>=2
|
| 34 |
+
Requires-Dist: idna<4,>=2.5
|
| 35 |
+
Requires-Dist: urllib3<3,>=1.21.1
|
| 36 |
+
Requires-Dist: certifi>=2017.4.17
|
| 37 |
+
Provides-Extra: security
|
| 38 |
+
Provides-Extra: socks
|
| 39 |
+
Requires-Dist: PySocks!=1.5.7,>=1.5.6; extra == "socks"
|
| 40 |
+
Provides-Extra: use-chardet-on-py3
|
| 41 |
+
Requires-Dist: chardet<6,>=3.0.2; extra == "use-chardet-on-py3"
|
| 42 |
+
Dynamic: author
|
| 43 |
+
Dynamic: author-email
|
| 44 |
+
Dynamic: classifier
|
| 45 |
+
Dynamic: description
|
| 46 |
+
Dynamic: description-content-type
|
| 47 |
+
Dynamic: home-page
|
| 48 |
+
Dynamic: license
|
| 49 |
+
Dynamic: license-file
|
| 50 |
+
Dynamic: project-url
|
| 51 |
+
Dynamic: provides-extra
|
| 52 |
+
Dynamic: requires-dist
|
| 53 |
+
Dynamic: requires-python
|
| 54 |
+
Dynamic: summary
|
| 55 |
+
|
| 56 |
+
# Requests
|
| 57 |
+
|
| 58 |
+
**Requests** is a simple, yet elegant, HTTP library.
|
| 59 |
+
|
| 60 |
+
```python
|
| 61 |
+
>>> import requests
|
| 62 |
+
>>> r = requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass'))
|
| 63 |
+
>>> r.status_code
|
| 64 |
+
200
|
| 65 |
+
>>> r.headers['content-type']
|
| 66 |
+
'application/json; charset=utf8'
|
| 67 |
+
>>> r.encoding
|
| 68 |
+
'utf-8'
|
| 69 |
+
>>> r.text
|
| 70 |
+
'{"authenticated": true, ...'
|
| 71 |
+
>>> r.json()
|
| 72 |
+
{'authenticated': True, ...}
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
Requests allows you to send HTTP/1.1 requests extremely easily. There’s no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method!
|
| 76 |
+
|
| 77 |
+
Requests is one of the most downloaded Python packages today, pulling in around `30M downloads / week`— according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `1,000,000+` repositories. You may certainly put your trust in this code.
|
| 78 |
+
|
| 79 |
+
[](https://pepy.tech/project/requests)
|
| 80 |
+
[](https://pypi.org/project/requests)
|
| 81 |
+
[](https://github.com/psf/requests/graphs/contributors)
|
| 82 |
+
|
| 83 |
+
## Installing Requests and Supported Versions
|
| 84 |
+
|
| 85 |
+
Requests is available on PyPI:
|
| 86 |
+
|
| 87 |
+
```console
|
| 88 |
+
$ python -m pip install requests
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
Requests officially supports Python 3.9+.
|
| 92 |
+
|
| 93 |
+
## Supported Features & Best–Practices
|
| 94 |
+
|
| 95 |
+
Requests is ready for the demands of building robust and reliable HTTP–speaking applications, for the needs of today.
|
| 96 |
+
|
| 97 |
+
- Keep-Alive & Connection Pooling
|
| 98 |
+
- International Domains and URLs
|
| 99 |
+
- Sessions with Cookie Persistence
|
| 100 |
+
- Browser-style TLS/SSL Verification
|
| 101 |
+
- Basic & Digest Authentication
|
| 102 |
+
- Familiar `dict`–like Cookies
|
| 103 |
+
- Automatic Content Decompression and Decoding
|
| 104 |
+
- Multi-part File Uploads
|
| 105 |
+
- SOCKS Proxy Support
|
| 106 |
+
- Connection Timeouts
|
| 107 |
+
- Streaming Downloads
|
| 108 |
+
- Automatic honoring of `.netrc`
|
| 109 |
+
- Chunked HTTP Requests
|
| 110 |
+
|
| 111 |
+
## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io)
|
| 112 |
+
|
| 113 |
+
[](https://requests.readthedocs.io)
|
| 114 |
+
|
| 115 |
+
## Cloning the repository
|
| 116 |
+
|
| 117 |
+
When cloning the Requests repository, you may need to add the `-c
|
| 118 |
+
fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit timestamp (see
|
| 119 |
+
[this issue](https://github.com/psf/requests/issues/2690) for more background):
|
| 120 |
+
|
| 121 |
+
```shell
|
| 122 |
+
git clone -c fetch.fsck.badTimezone=ignore https://github.com/psf/requests.git
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
You can also apply this setting to your global Git config:
|
| 126 |
+
|
| 127 |
+
```shell
|
| 128 |
+
git config --global fetch.fsck.badTimezone ignore
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
[](https://kennethreitz.org) [](https://www.python.org/psf)
|
.venv/Lib/site-packages/requests-2.32.5.dist-info/RECORD
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
requests-2.32.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
requests-2.32.5.dist-info/METADATA,sha256=ZbWgjagfSRVRPnYJZf8Ut1GPZbe7Pv4NqzZLvMTUDLA,4945
|
| 3 |
+
requests-2.32.5.dist-info/RECORD,,
|
| 4 |
+
requests-2.32.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
| 5 |
+
requests-2.32.5.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
|
| 6 |
+
requests-2.32.5.dist-info/top_level.txt,sha256=fMSVmHfb5rbGOo6xv-O_tUX6j-WyixssE-SnwcDRxNQ,9
|
| 7 |
+
requests/__init__.py,sha256=4xaAERmPDIBPsa2PsjpU9r06yooK-2mZKHTZAhWRWts,5072
|
| 8 |
+
requests/__pycache__/__version__.cpython-312.pyc,,
|
| 9 |
+
requests/__pycache__/_internal_utils.cpython-312.pyc,,
|
| 10 |
+
requests/__pycache__/adapters.cpython-312.pyc,,
|
| 11 |
+
requests/__pycache__/api.cpython-312.pyc,,
|
| 12 |
+
requests/__pycache__/certs.cpython-312.pyc,,
|
| 13 |
+
requests/__pycache__/cookies.cpython-312.pyc,,
|
| 14 |
+
requests/__pycache__/exceptions.cpython-312.pyc,,
|
| 15 |
+
requests/__pycache__/help.cpython-312.pyc,,
|
| 16 |
+
requests/__pycache__/hooks.cpython-312.pyc,,
|
| 17 |
+
requests/__pycache__/packages.cpython-312.pyc,,
|
| 18 |
+
requests/__pycache__/sessions.cpython-312.pyc,,
|
| 19 |
+
requests/__pycache__/structures.cpython-312.pyc,,
|
| 20 |
+
requests/__pycache__/utils.cpython-312.pyc,,
|
| 21 |
+
requests/__version__.py,sha256=QKDceK8K_ujqwDDc3oYrR0odOBYgKVOQQ5vFap_G_cg,435
|
| 22 |
+
requests/_internal_utils.py,sha256=nMQymr4hs32TqVo5AbCrmcJEhvPUh7xXlluyqwslLiQ,1495
|
| 23 |
+
requests/adapters.py,sha256=8nX113gbb123aUtx2ETkAN_6IsYX-M2fRoLGluTEcRk,26285
|
| 24 |
+
requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449
|
| 25 |
+
requests/auth.py,sha256=kF75tqnLctZ9Mf_hm9TZIj4cQWnN5uxRz8oWsx5wmR0,10186
|
| 26 |
+
requests/certs.py,sha256=Z9Sb410Anv6jUFTyss0jFFhU6xst8ctELqfy8Ev23gw,429
|
| 27 |
+
requests/compat.py,sha256=J7sIjR6XoDGp5JTVzOxkK5fSoUVUa_Pjc7iRZhAWGmI,2142
|
| 28 |
+
requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590
|
| 29 |
+
requests/exceptions.py,sha256=jJPS1UWATs86ShVUaLorTiJb1SaGuoNEWgICJep-VkY,4260
|
| 30 |
+
requests/help.py,sha256=gPX5d_H7Xd88aDABejhqGgl9B1VFRTt5BmiYvL3PzIQ,3875
|
| 31 |
+
requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733
|
| 32 |
+
requests/models.py,sha256=MjZdZ4k7tnw-1nz5PKShjmPmqyk0L6DciwnFngb_Vk4,35510
|
| 33 |
+
requests/packages.py,sha256=_g0gZ681UyAlKHRjH6kanbaoxx2eAb6qzcXiODyTIoc,904
|
| 34 |
+
requests/sessions.py,sha256=Cl1dpEnOfwrzzPbku-emepNeN4Rt_0_58Iy2x-JGTm8,30503
|
| 35 |
+
requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322
|
| 36 |
+
requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912
|
| 37 |
+
requests/utils.py,sha256=WqU86rZ3wvhC-tQjWcjtH_HEKZwWB3iWCZV6SW5DEdQ,33213
|
.venv/Lib/site-packages/requests-2.32.5.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (80.9.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
.venv/Lib/site-packages/requests-2.32.5.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
requests
|
.venv/Lib/site-packages/requests/__init__.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# __
|
| 2 |
+
# /__) _ _ _ _ _/ _
|
| 3 |
+
# / ( (- (/ (/ (- _) / _)
|
| 4 |
+
# /
|
| 5 |
+
|
| 6 |
+
"""
|
| 7 |
+
Requests HTTP Library
|
| 8 |
+
~~~~~~~~~~~~~~~~~~~~~
|
| 9 |
+
|
| 10 |
+
Requests is an HTTP library, written in Python, for human beings.
|
| 11 |
+
Basic GET usage:
|
| 12 |
+
|
| 13 |
+
>>> import requests
|
| 14 |
+
>>> r = requests.get('https://www.python.org')
|
| 15 |
+
>>> r.status_code
|
| 16 |
+
200
|
| 17 |
+
>>> b'Python is a programming language' in r.content
|
| 18 |
+
True
|
| 19 |
+
|
| 20 |
+
... or POST:
|
| 21 |
+
|
| 22 |
+
>>> payload = dict(key1='value1', key2='value2')
|
| 23 |
+
>>> r = requests.post('https://httpbin.org/post', data=payload)
|
| 24 |
+
>>> print(r.text)
|
| 25 |
+
{
|
| 26 |
+
...
|
| 27 |
+
"form": {
|
| 28 |
+
"key1": "value1",
|
| 29 |
+
"key2": "value2"
|
| 30 |
+
},
|
| 31 |
+
...
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
The other HTTP methods are supported - see `requests.api`. Full documentation
|
| 35 |
+
is at <https://requests.readthedocs.io>.
|
| 36 |
+
|
| 37 |
+
:copyright: (c) 2017 by Kenneth Reitz.
|
| 38 |
+
:license: Apache 2.0, see LICENSE for more details.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
import warnings
|
| 42 |
+
|
| 43 |
+
import urllib3
|
| 44 |
+
|
| 45 |
+
from .exceptions import RequestsDependencyWarning
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
from charset_normalizer import __version__ as charset_normalizer_version
|
| 49 |
+
except ImportError:
|
| 50 |
+
charset_normalizer_version = None
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
from chardet import __version__ as chardet_version
|
| 54 |
+
except ImportError:
|
| 55 |
+
chardet_version = None
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
|
| 59 |
+
urllib3_version = urllib3_version.split(".")
|
| 60 |
+
assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git.
|
| 61 |
+
|
| 62 |
+
# Sometimes, urllib3 only reports its version as 16.1.
|
| 63 |
+
if len(urllib3_version) == 2:
|
| 64 |
+
urllib3_version.append("0")
|
| 65 |
+
|
| 66 |
+
# Check urllib3 for compatibility.
|
| 67 |
+
major, minor, patch = urllib3_version # noqa: F811
|
| 68 |
+
major, minor, patch = int(major), int(minor), int(patch)
|
| 69 |
+
# urllib3 >= 1.21.1
|
| 70 |
+
assert major >= 1
|
| 71 |
+
if major == 1:
|
| 72 |
+
assert minor >= 21
|
| 73 |
+
|
| 74 |
+
# Check charset_normalizer for compatibility.
|
| 75 |
+
if chardet_version:
|
| 76 |
+
major, minor, patch = chardet_version.split(".")[:3]
|
| 77 |
+
major, minor, patch = int(major), int(minor), int(patch)
|
| 78 |
+
# chardet_version >= 3.0.2, < 6.0.0
|
| 79 |
+
assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0)
|
| 80 |
+
elif charset_normalizer_version:
|
| 81 |
+
major, minor, patch = charset_normalizer_version.split(".")[:3]
|
| 82 |
+
major, minor, patch = int(major), int(minor), int(patch)
|
| 83 |
+
# charset_normalizer >= 2.0.0 < 4.0.0
|
| 84 |
+
assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
|
| 85 |
+
else:
|
| 86 |
+
warnings.warn(
|
| 87 |
+
"Unable to find acceptable character detection dependency "
|
| 88 |
+
"(chardet or charset_normalizer).",
|
| 89 |
+
RequestsDependencyWarning,
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _check_cryptography(cryptography_version):
|
| 94 |
+
# cryptography < 1.3.4
|
| 95 |
+
try:
|
| 96 |
+
cryptography_version = list(map(int, cryptography_version.split(".")))
|
| 97 |
+
except ValueError:
|
| 98 |
+
return
|
| 99 |
+
|
| 100 |
+
if cryptography_version < [1, 3, 4]:
|
| 101 |
+
warning = "Old version of cryptography ({}) may cause slowdown.".format(
|
| 102 |
+
cryptography_version
|
| 103 |
+
)
|
| 104 |
+
warnings.warn(warning, RequestsDependencyWarning)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# Check imported dependencies for compatibility.
|
| 108 |
+
try:
|
| 109 |
+
check_compatibility(
|
| 110 |
+
urllib3.__version__, chardet_version, charset_normalizer_version
|
| 111 |
+
)
|
| 112 |
+
except (AssertionError, ValueError):
|
| 113 |
+
warnings.warn(
|
| 114 |
+
"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
|
| 115 |
+
"version!".format(
|
| 116 |
+
urllib3.__version__, chardet_version, charset_normalizer_version
|
| 117 |
+
),
|
| 118 |
+
RequestsDependencyWarning,
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# Attempt to enable urllib3's fallback for SNI support
|
| 122 |
+
# if the standard library doesn't support SNI or the
|
| 123 |
+
# 'ssl' library isn't available.
|
| 124 |
+
try:
|
| 125 |
+
try:
|
| 126 |
+
import ssl
|
| 127 |
+
except ImportError:
|
| 128 |
+
ssl = None
|
| 129 |
+
|
| 130 |
+
if not getattr(ssl, "HAS_SNI", False):
|
| 131 |
+
from urllib3.contrib import pyopenssl
|
| 132 |
+
|
| 133 |
+
pyopenssl.inject_into_urllib3()
|
| 134 |
+
|
| 135 |
+
# Check cryptography version
|
| 136 |
+
from cryptography import __version__ as cryptography_version
|
| 137 |
+
|
| 138 |
+
_check_cryptography(cryptography_version)
|
| 139 |
+
except ImportError:
|
| 140 |
+
pass
|
| 141 |
+
|
| 142 |
+
# urllib3's DependencyWarnings should be silenced.
|
| 143 |
+
from urllib3.exceptions import DependencyWarning
|
| 144 |
+
|
| 145 |
+
warnings.simplefilter("ignore", DependencyWarning)
|
| 146 |
+
|
| 147 |
+
# Set default logging handler to avoid "No handler found" warnings.
|
| 148 |
+
import logging
|
| 149 |
+
from logging import NullHandler
|
| 150 |
+
|
| 151 |
+
from . import packages, utils
|
| 152 |
+
from .__version__ import (
|
| 153 |
+
__author__,
|
| 154 |
+
__author_email__,
|
| 155 |
+
__build__,
|
| 156 |
+
__cake__,
|
| 157 |
+
__copyright__,
|
| 158 |
+
__description__,
|
| 159 |
+
__license__,
|
| 160 |
+
__title__,
|
| 161 |
+
__url__,
|
| 162 |
+
__version__,
|
| 163 |
+
)
|
| 164 |
+
from .api import delete, get, head, options, patch, post, put, request
|
| 165 |
+
from .exceptions import (
|
| 166 |
+
ConnectionError,
|
| 167 |
+
ConnectTimeout,
|
| 168 |
+
FileModeWarning,
|
| 169 |
+
HTTPError,
|
| 170 |
+
JSONDecodeError,
|
| 171 |
+
ReadTimeout,
|
| 172 |
+
RequestException,
|
| 173 |
+
Timeout,
|
| 174 |
+
TooManyRedirects,
|
| 175 |
+
URLRequired,
|
| 176 |
+
)
|
| 177 |
+
from .models import PreparedRequest, Request, Response
|
| 178 |
+
from .sessions import Session, session
|
| 179 |
+
from .status_codes import codes
|
| 180 |
+
|
| 181 |
+
logging.getLogger(__name__).addHandler(NullHandler())
|
| 182 |
+
|
| 183 |
+
# FileModeWarnings go off per the default.
|
| 184 |
+
warnings.simplefilter("default", FileModeWarning, append=True)
|
.venv/Lib/site-packages/requests/__version__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# .-. .-. .-. . . .-. .-. .-. .-.
|
| 2 |
+
# |( |- |.| | | |- `-. | `-.
|
| 3 |
+
# ' ' `-' `-`.`-' `-' `-' ' `-'
|
| 4 |
+
|
| 5 |
+
__title__ = "requests"
|
| 6 |
+
__description__ = "Python HTTP for Humans."
|
| 7 |
+
__url__ = "https://requests.readthedocs.io"
|
| 8 |
+
__version__ = "2.32.5"
|
| 9 |
+
__build__ = 0x023205
|
| 10 |
+
__author__ = "Kenneth Reitz"
|
| 11 |
+
__author_email__ = "me@kennethreitz.org"
|
| 12 |
+
__license__ = "Apache-2.0"
|
| 13 |
+
__copyright__ = "Copyright Kenneth Reitz"
|
| 14 |
+
__cake__ = "\u2728 \U0001f370 \u2728"
|
.venv/Lib/site-packages/requests/_internal_utils.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests._internal_utils
|
| 3 |
+
~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Provides utility functions that are consumed internally by Requests
|
| 6 |
+
which depend on extremely few external helpers (such as compat)
|
| 7 |
+
"""
|
| 8 |
+
import re
|
| 9 |
+
|
| 10 |
+
from .compat import builtin_str
|
| 11 |
+
|
| 12 |
+
_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$")
|
| 13 |
+
_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$")
|
| 14 |
+
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
|
| 15 |
+
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")
|
| 16 |
+
|
| 17 |
+
_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
|
| 18 |
+
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
|
| 19 |
+
HEADER_VALIDATORS = {
|
| 20 |
+
bytes: _HEADER_VALIDATORS_BYTE,
|
| 21 |
+
str: _HEADER_VALIDATORS_STR,
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def to_native_string(string, encoding="ascii"):
|
| 26 |
+
"""Given a string object, regardless of type, returns a representation of
|
| 27 |
+
that string in the native string type, encoding and decoding where
|
| 28 |
+
necessary. This assumes ASCII unless told otherwise.
|
| 29 |
+
"""
|
| 30 |
+
if isinstance(string, builtin_str):
|
| 31 |
+
out = string
|
| 32 |
+
else:
|
| 33 |
+
out = string.decode(encoding)
|
| 34 |
+
|
| 35 |
+
return out
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def unicode_is_ascii(u_string):
|
| 39 |
+
"""Determine if unicode string only contains ASCII characters.
|
| 40 |
+
|
| 41 |
+
:param str u_string: unicode string to check. Must be unicode
|
| 42 |
+
and not Python 2 `str`.
|
| 43 |
+
:rtype: bool
|
| 44 |
+
"""
|
| 45 |
+
assert isinstance(u_string, str)
|
| 46 |
+
try:
|
| 47 |
+
u_string.encode("ascii")
|
| 48 |
+
return True
|
| 49 |
+
except UnicodeEncodeError:
|
| 50 |
+
return False
|
.venv/Lib/site-packages/requests/adapters.py
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests.adapters
|
| 3 |
+
~~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module contains the transport adapters that Requests uses to define
|
| 6 |
+
and maintain connections.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import os.path
|
| 10 |
+
import socket # noqa: F401
|
| 11 |
+
import typing
|
| 12 |
+
import warnings
|
| 13 |
+
|
| 14 |
+
from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
|
| 15 |
+
from urllib3.exceptions import HTTPError as _HTTPError
|
| 16 |
+
from urllib3.exceptions import InvalidHeader as _InvalidHeader
|
| 17 |
+
from urllib3.exceptions import (
|
| 18 |
+
LocationValueError,
|
| 19 |
+
MaxRetryError,
|
| 20 |
+
NewConnectionError,
|
| 21 |
+
ProtocolError,
|
| 22 |
+
)
|
| 23 |
+
from urllib3.exceptions import ProxyError as _ProxyError
|
| 24 |
+
from urllib3.exceptions import ReadTimeoutError, ResponseError
|
| 25 |
+
from urllib3.exceptions import SSLError as _SSLError
|
| 26 |
+
from urllib3.poolmanager import PoolManager, proxy_from_url
|
| 27 |
+
from urllib3.util import Timeout as TimeoutSauce
|
| 28 |
+
from urllib3.util import parse_url
|
| 29 |
+
from urllib3.util.retry import Retry
|
| 30 |
+
|
| 31 |
+
from .auth import _basic_auth_str
|
| 32 |
+
from .compat import basestring, urlparse
|
| 33 |
+
from .cookies import extract_cookies_to_jar
|
| 34 |
+
from .exceptions import (
|
| 35 |
+
ConnectionError,
|
| 36 |
+
ConnectTimeout,
|
| 37 |
+
InvalidHeader,
|
| 38 |
+
InvalidProxyURL,
|
| 39 |
+
InvalidSchema,
|
| 40 |
+
InvalidURL,
|
| 41 |
+
ProxyError,
|
| 42 |
+
ReadTimeout,
|
| 43 |
+
RetryError,
|
| 44 |
+
SSLError,
|
| 45 |
+
)
|
| 46 |
+
from .models import Response
|
| 47 |
+
from .structures import CaseInsensitiveDict
|
| 48 |
+
from .utils import (
|
| 49 |
+
DEFAULT_CA_BUNDLE_PATH,
|
| 50 |
+
extract_zipped_paths,
|
| 51 |
+
get_auth_from_url,
|
| 52 |
+
get_encoding_from_headers,
|
| 53 |
+
prepend_scheme_if_needed,
|
| 54 |
+
select_proxy,
|
| 55 |
+
urldefragauth,
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
from urllib3.contrib.socks import SOCKSProxyManager
|
| 60 |
+
except ImportError:
|
| 61 |
+
|
| 62 |
+
def SOCKSProxyManager(*args, **kwargs):
|
| 63 |
+
raise InvalidSchema("Missing dependencies for SOCKS support.")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
if typing.TYPE_CHECKING:
|
| 67 |
+
from .models import PreparedRequest
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
DEFAULT_POOLBLOCK = False
|
| 71 |
+
DEFAULT_POOLSIZE = 10
|
| 72 |
+
DEFAULT_RETRIES = 0
|
| 73 |
+
DEFAULT_POOL_TIMEOUT = None
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _urllib3_request_context(
|
| 77 |
+
request: "PreparedRequest",
|
| 78 |
+
verify: "bool | str | None",
|
| 79 |
+
client_cert: "typing.Tuple[str, str] | str | None",
|
| 80 |
+
poolmanager: "PoolManager",
|
| 81 |
+
) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])":
|
| 82 |
+
host_params = {}
|
| 83 |
+
pool_kwargs = {}
|
| 84 |
+
parsed_request_url = urlparse(request.url)
|
| 85 |
+
scheme = parsed_request_url.scheme.lower()
|
| 86 |
+
port = parsed_request_url.port
|
| 87 |
+
|
| 88 |
+
cert_reqs = "CERT_REQUIRED"
|
| 89 |
+
if verify is False:
|
| 90 |
+
cert_reqs = "CERT_NONE"
|
| 91 |
+
elif isinstance(verify, str):
|
| 92 |
+
if not os.path.isdir(verify):
|
| 93 |
+
pool_kwargs["ca_certs"] = verify
|
| 94 |
+
else:
|
| 95 |
+
pool_kwargs["ca_cert_dir"] = verify
|
| 96 |
+
pool_kwargs["cert_reqs"] = cert_reqs
|
| 97 |
+
if client_cert is not None:
|
| 98 |
+
if isinstance(client_cert, tuple) and len(client_cert) == 2:
|
| 99 |
+
pool_kwargs["cert_file"] = client_cert[0]
|
| 100 |
+
pool_kwargs["key_file"] = client_cert[1]
|
| 101 |
+
else:
|
| 102 |
+
# According to our docs, we allow users to specify just the client
|
| 103 |
+
# cert path
|
| 104 |
+
pool_kwargs["cert_file"] = client_cert
|
| 105 |
+
host_params = {
|
| 106 |
+
"scheme": scheme,
|
| 107 |
+
"host": parsed_request_url.hostname,
|
| 108 |
+
"port": port,
|
| 109 |
+
}
|
| 110 |
+
return host_params, pool_kwargs
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class BaseAdapter:
|
| 114 |
+
"""The Base Transport Adapter"""
|
| 115 |
+
|
| 116 |
+
def __init__(self):
|
| 117 |
+
super().__init__()
|
| 118 |
+
|
| 119 |
+
def send(
|
| 120 |
+
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
|
| 121 |
+
):
|
| 122 |
+
"""Sends PreparedRequest object. Returns Response object.
|
| 123 |
+
|
| 124 |
+
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
| 125 |
+
:param stream: (optional) Whether to stream the request content.
|
| 126 |
+
:param timeout: (optional) How long to wait for the server to send
|
| 127 |
+
data before giving up, as a float, or a :ref:`(connect timeout,
|
| 128 |
+
read timeout) <timeouts>` tuple.
|
| 129 |
+
:type timeout: float or tuple
|
| 130 |
+
:param verify: (optional) Either a boolean, in which case it controls whether we verify
|
| 131 |
+
the server's TLS certificate, or a string, in which case it must be a path
|
| 132 |
+
to a CA bundle to use
|
| 133 |
+
:param cert: (optional) Any user-provided SSL certificate to be trusted.
|
| 134 |
+
:param proxies: (optional) The proxies dictionary to apply to the request.
|
| 135 |
+
"""
|
| 136 |
+
raise NotImplementedError
|
| 137 |
+
|
| 138 |
+
def close(self):
|
| 139 |
+
"""Cleans up adapter specific items."""
|
| 140 |
+
raise NotImplementedError
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class HTTPAdapter(BaseAdapter):
|
| 144 |
+
"""The built-in HTTP Adapter for urllib3.
|
| 145 |
+
|
| 146 |
+
Provides a general-case interface for Requests sessions to contact HTTP and
|
| 147 |
+
HTTPS urls by implementing the Transport Adapter interface. This class will
|
| 148 |
+
usually be created by the :class:`Session <Session>` class under the
|
| 149 |
+
covers.
|
| 150 |
+
|
| 151 |
+
:param pool_connections: The number of urllib3 connection pools to cache.
|
| 152 |
+
:param pool_maxsize: The maximum number of connections to save in the pool.
|
| 153 |
+
:param max_retries: The maximum number of retries each connection
|
| 154 |
+
should attempt. Note, this applies only to failed DNS lookups, socket
|
| 155 |
+
connections and connection timeouts, never to requests where data has
|
| 156 |
+
made it to the server. By default, Requests does not retry failed
|
| 157 |
+
connections. If you need granular control over the conditions under
|
| 158 |
+
which we retry a request, import urllib3's ``Retry`` class and pass
|
| 159 |
+
that instead.
|
| 160 |
+
:param pool_block: Whether the connection pool should block for connections.
|
| 161 |
+
|
| 162 |
+
Usage::
|
| 163 |
+
|
| 164 |
+
>>> import requests
|
| 165 |
+
>>> s = requests.Session()
|
| 166 |
+
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
|
| 167 |
+
>>> s.mount('http://', a)
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
__attrs__ = [
|
| 171 |
+
"max_retries",
|
| 172 |
+
"config",
|
| 173 |
+
"_pool_connections",
|
| 174 |
+
"_pool_maxsize",
|
| 175 |
+
"_pool_block",
|
| 176 |
+
]
|
| 177 |
+
|
| 178 |
+
def __init__(
|
| 179 |
+
self,
|
| 180 |
+
pool_connections=DEFAULT_POOLSIZE,
|
| 181 |
+
pool_maxsize=DEFAULT_POOLSIZE,
|
| 182 |
+
max_retries=DEFAULT_RETRIES,
|
| 183 |
+
pool_block=DEFAULT_POOLBLOCK,
|
| 184 |
+
):
|
| 185 |
+
if max_retries == DEFAULT_RETRIES:
|
| 186 |
+
self.max_retries = Retry(0, read=False)
|
| 187 |
+
else:
|
| 188 |
+
self.max_retries = Retry.from_int(max_retries)
|
| 189 |
+
self.config = {}
|
| 190 |
+
self.proxy_manager = {}
|
| 191 |
+
|
| 192 |
+
super().__init__()
|
| 193 |
+
|
| 194 |
+
self._pool_connections = pool_connections
|
| 195 |
+
self._pool_maxsize = pool_maxsize
|
| 196 |
+
self._pool_block = pool_block
|
| 197 |
+
|
| 198 |
+
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
|
| 199 |
+
|
| 200 |
+
def __getstate__(self):
|
| 201 |
+
return {attr: getattr(self, attr, None) for attr in self.__attrs__}
|
| 202 |
+
|
| 203 |
+
def __setstate__(self, state):
|
| 204 |
+
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
|
| 205 |
+
# self.poolmanager uses a lambda function, which isn't pickleable.
|
| 206 |
+
self.proxy_manager = {}
|
| 207 |
+
self.config = {}
|
| 208 |
+
|
| 209 |
+
for attr, value in state.items():
|
| 210 |
+
setattr(self, attr, value)
|
| 211 |
+
|
| 212 |
+
self.init_poolmanager(
|
| 213 |
+
self._pool_connections, self._pool_maxsize, block=self._pool_block
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
def init_poolmanager(
|
| 217 |
+
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
|
| 218 |
+
):
|
| 219 |
+
"""Initializes a urllib3 PoolManager.
|
| 220 |
+
|
| 221 |
+
This method should not be called from user code, and is only
|
| 222 |
+
exposed for use when subclassing the
|
| 223 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 224 |
+
|
| 225 |
+
:param connections: The number of urllib3 connection pools to cache.
|
| 226 |
+
:param maxsize: The maximum number of connections to save in the pool.
|
| 227 |
+
:param block: Block when no free connections are available.
|
| 228 |
+
:param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
|
| 229 |
+
"""
|
| 230 |
+
# save these values for pickling
|
| 231 |
+
self._pool_connections = connections
|
| 232 |
+
self._pool_maxsize = maxsize
|
| 233 |
+
self._pool_block = block
|
| 234 |
+
|
| 235 |
+
self.poolmanager = PoolManager(
|
| 236 |
+
num_pools=connections,
|
| 237 |
+
maxsize=maxsize,
|
| 238 |
+
block=block,
|
| 239 |
+
**pool_kwargs,
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
| 243 |
+
"""Return urllib3 ProxyManager for the given proxy.
|
| 244 |
+
|
| 245 |
+
This method should not be called from user code, and is only
|
| 246 |
+
exposed for use when subclassing the
|
| 247 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 248 |
+
|
| 249 |
+
:param proxy: The proxy to return a urllib3 ProxyManager for.
|
| 250 |
+
:param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
|
| 251 |
+
:returns: ProxyManager
|
| 252 |
+
:rtype: urllib3.ProxyManager
|
| 253 |
+
"""
|
| 254 |
+
if proxy in self.proxy_manager:
|
| 255 |
+
manager = self.proxy_manager[proxy]
|
| 256 |
+
elif proxy.lower().startswith("socks"):
|
| 257 |
+
username, password = get_auth_from_url(proxy)
|
| 258 |
+
manager = self.proxy_manager[proxy] = SOCKSProxyManager(
|
| 259 |
+
proxy,
|
| 260 |
+
username=username,
|
| 261 |
+
password=password,
|
| 262 |
+
num_pools=self._pool_connections,
|
| 263 |
+
maxsize=self._pool_maxsize,
|
| 264 |
+
block=self._pool_block,
|
| 265 |
+
**proxy_kwargs,
|
| 266 |
+
)
|
| 267 |
+
else:
|
| 268 |
+
proxy_headers = self.proxy_headers(proxy)
|
| 269 |
+
manager = self.proxy_manager[proxy] = proxy_from_url(
|
| 270 |
+
proxy,
|
| 271 |
+
proxy_headers=proxy_headers,
|
| 272 |
+
num_pools=self._pool_connections,
|
| 273 |
+
maxsize=self._pool_maxsize,
|
| 274 |
+
block=self._pool_block,
|
| 275 |
+
**proxy_kwargs,
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
return manager
|
| 279 |
+
|
| 280 |
+
def cert_verify(self, conn, url, verify, cert):
|
| 281 |
+
"""Verify a SSL certificate. This method should not be called from user
|
| 282 |
+
code, and is only exposed for use when subclassing the
|
| 283 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 284 |
+
|
| 285 |
+
:param conn: The urllib3 connection object associated with the cert.
|
| 286 |
+
:param url: The requested URL.
|
| 287 |
+
:param verify: Either a boolean, in which case it controls whether we verify
|
| 288 |
+
the server's TLS certificate, or a string, in which case it must be a path
|
| 289 |
+
to a CA bundle to use
|
| 290 |
+
:param cert: The SSL certificate to verify.
|
| 291 |
+
"""
|
| 292 |
+
if url.lower().startswith("https") and verify:
|
| 293 |
+
cert_loc = None
|
| 294 |
+
|
| 295 |
+
# Allow self-specified cert location.
|
| 296 |
+
if verify is not True:
|
| 297 |
+
cert_loc = verify
|
| 298 |
+
|
| 299 |
+
if not cert_loc:
|
| 300 |
+
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
|
| 301 |
+
|
| 302 |
+
if not cert_loc or not os.path.exists(cert_loc):
|
| 303 |
+
raise OSError(
|
| 304 |
+
f"Could not find a suitable TLS CA certificate bundle, "
|
| 305 |
+
f"invalid path: {cert_loc}"
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
conn.cert_reqs = "CERT_REQUIRED"
|
| 309 |
+
|
| 310 |
+
if not os.path.isdir(cert_loc):
|
| 311 |
+
conn.ca_certs = cert_loc
|
| 312 |
+
else:
|
| 313 |
+
conn.ca_cert_dir = cert_loc
|
| 314 |
+
else:
|
| 315 |
+
conn.cert_reqs = "CERT_NONE"
|
| 316 |
+
conn.ca_certs = None
|
| 317 |
+
conn.ca_cert_dir = None
|
| 318 |
+
|
| 319 |
+
if cert:
|
| 320 |
+
if not isinstance(cert, basestring):
|
| 321 |
+
conn.cert_file = cert[0]
|
| 322 |
+
conn.key_file = cert[1]
|
| 323 |
+
else:
|
| 324 |
+
conn.cert_file = cert
|
| 325 |
+
conn.key_file = None
|
| 326 |
+
if conn.cert_file and not os.path.exists(conn.cert_file):
|
| 327 |
+
raise OSError(
|
| 328 |
+
f"Could not find the TLS certificate file, "
|
| 329 |
+
f"invalid path: {conn.cert_file}"
|
| 330 |
+
)
|
| 331 |
+
if conn.key_file and not os.path.exists(conn.key_file):
|
| 332 |
+
raise OSError(
|
| 333 |
+
f"Could not find the TLS key file, invalid path: {conn.key_file}"
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
def build_response(self, req, resp):
|
| 337 |
+
"""Builds a :class:`Response <requests.Response>` object from a urllib3
|
| 338 |
+
response. This should not be called from user code, and is only exposed
|
| 339 |
+
for use when subclassing the
|
| 340 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`
|
| 341 |
+
|
| 342 |
+
:param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
|
| 343 |
+
:param resp: The urllib3 response object.
|
| 344 |
+
:rtype: requests.Response
|
| 345 |
+
"""
|
| 346 |
+
response = Response()
|
| 347 |
+
|
| 348 |
+
# Fallback to None if there's no status_code, for whatever reason.
|
| 349 |
+
response.status_code = getattr(resp, "status", None)
|
| 350 |
+
|
| 351 |
+
# Make headers case-insensitive.
|
| 352 |
+
response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))
|
| 353 |
+
|
| 354 |
+
# Set encoding.
|
| 355 |
+
response.encoding = get_encoding_from_headers(response.headers)
|
| 356 |
+
response.raw = resp
|
| 357 |
+
response.reason = response.raw.reason
|
| 358 |
+
|
| 359 |
+
if isinstance(req.url, bytes):
|
| 360 |
+
response.url = req.url.decode("utf-8")
|
| 361 |
+
else:
|
| 362 |
+
response.url = req.url
|
| 363 |
+
|
| 364 |
+
# Add new cookies from the server.
|
| 365 |
+
extract_cookies_to_jar(response.cookies, req, resp)
|
| 366 |
+
|
| 367 |
+
# Give the Response some context.
|
| 368 |
+
response.request = req
|
| 369 |
+
response.connection = self
|
| 370 |
+
|
| 371 |
+
return response
|
| 372 |
+
|
| 373 |
+
def build_connection_pool_key_attributes(self, request, verify, cert=None):
|
| 374 |
+
"""Build the PoolKey attributes used by urllib3 to return a connection.
|
| 375 |
+
|
| 376 |
+
This looks at the PreparedRequest, the user-specified verify value,
|
| 377 |
+
and the value of the cert parameter to determine what PoolKey values
|
| 378 |
+
to use to select a connection from a given urllib3 Connection Pool.
|
| 379 |
+
|
| 380 |
+
The SSL related pool key arguments are not consistently set. As of
|
| 381 |
+
this writing, use the following to determine what keys may be in that
|
| 382 |
+
dictionary:
|
| 383 |
+
|
| 384 |
+
* If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the
|
| 385 |
+
default Requests SSL Context
|
| 386 |
+
* If ``verify`` is ``False``, ``"ssl_context"`` will not be set but
|
| 387 |
+
``"cert_reqs"`` will be set
|
| 388 |
+
* If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
|
| 389 |
+
``"ca_certs"`` will be set if the string is not a directory recognized
|
| 390 |
+
by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be
|
| 391 |
+
set.
|
| 392 |
+
* If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
|
| 393 |
+
``"cert"`` is a tuple with a second item, ``"key_file"`` will also
|
| 394 |
+
be present
|
| 395 |
+
|
| 396 |
+
To override these settings, one may subclass this class, call this
|
| 397 |
+
method and use the above logic to change parameters as desired. For
|
| 398 |
+
example, if one wishes to use a custom :py:class:`ssl.SSLContext` one
|
| 399 |
+
must both set ``"ssl_context"`` and based on what else they require,
|
| 400 |
+
alter the other keys to ensure the desired behaviour.
|
| 401 |
+
|
| 402 |
+
:param request:
|
| 403 |
+
The PreparedReqest being sent over the connection.
|
| 404 |
+
:type request:
|
| 405 |
+
:class:`~requests.models.PreparedRequest`
|
| 406 |
+
:param verify:
|
| 407 |
+
Either a boolean, in which case it controls whether
|
| 408 |
+
we verify the server's TLS certificate, or a string, in which case it
|
| 409 |
+
must be a path to a CA bundle to use.
|
| 410 |
+
:param cert:
|
| 411 |
+
(optional) Any user-provided SSL certificate for client
|
| 412 |
+
authentication (a.k.a., mTLS). This may be a string (i.e., just
|
| 413 |
+
the path to a file which holds both certificate and key) or a
|
| 414 |
+
tuple of length 2 with the certificate file path and key file
|
| 415 |
+
path.
|
| 416 |
+
:returns:
|
| 417 |
+
A tuple of two dictionaries. The first is the "host parameters"
|
| 418 |
+
portion of the Pool Key including scheme, hostname, and port. The
|
| 419 |
+
second is a dictionary of SSLContext related parameters.
|
| 420 |
+
"""
|
| 421 |
+
return _urllib3_request_context(request, verify, cert, self.poolmanager)
|
| 422 |
+
|
| 423 |
+
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
|
| 424 |
+
"""Returns a urllib3 connection for the given request and TLS settings.
|
| 425 |
+
This should not be called from user code, and is only exposed for use
|
| 426 |
+
when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 427 |
+
|
| 428 |
+
:param request:
|
| 429 |
+
The :class:`PreparedRequest <PreparedRequest>` object to be sent
|
| 430 |
+
over the connection.
|
| 431 |
+
:param verify:
|
| 432 |
+
Either a boolean, in which case it controls whether we verify the
|
| 433 |
+
server's TLS certificate, or a string, in which case it must be a
|
| 434 |
+
path to a CA bundle to use.
|
| 435 |
+
:param proxies:
|
| 436 |
+
(optional) The proxies dictionary to apply to the request.
|
| 437 |
+
:param cert:
|
| 438 |
+
(optional) Any user-provided SSL certificate to be used for client
|
| 439 |
+
authentication (a.k.a., mTLS).
|
| 440 |
+
:rtype:
|
| 441 |
+
urllib3.ConnectionPool
|
| 442 |
+
"""
|
| 443 |
+
proxy = select_proxy(request.url, proxies)
|
| 444 |
+
try:
|
| 445 |
+
host_params, pool_kwargs = self.build_connection_pool_key_attributes(
|
| 446 |
+
request,
|
| 447 |
+
verify,
|
| 448 |
+
cert,
|
| 449 |
+
)
|
| 450 |
+
except ValueError as e:
|
| 451 |
+
raise InvalidURL(e, request=request)
|
| 452 |
+
if proxy:
|
| 453 |
+
proxy = prepend_scheme_if_needed(proxy, "http")
|
| 454 |
+
proxy_url = parse_url(proxy)
|
| 455 |
+
if not proxy_url.host:
|
| 456 |
+
raise InvalidProxyURL(
|
| 457 |
+
"Please check proxy URL. It is malformed "
|
| 458 |
+
"and could be missing the host."
|
| 459 |
+
)
|
| 460 |
+
proxy_manager = self.proxy_manager_for(proxy)
|
| 461 |
+
conn = proxy_manager.connection_from_host(
|
| 462 |
+
**host_params, pool_kwargs=pool_kwargs
|
| 463 |
+
)
|
| 464 |
+
else:
|
| 465 |
+
# Only scheme should be lower case
|
| 466 |
+
conn = self.poolmanager.connection_from_host(
|
| 467 |
+
**host_params, pool_kwargs=pool_kwargs
|
| 468 |
+
)
|
| 469 |
+
|
| 470 |
+
return conn
|
| 471 |
+
|
| 472 |
+
def get_connection(self, url, proxies=None):
|
| 473 |
+
"""DEPRECATED: Users should move to `get_connection_with_tls_context`
|
| 474 |
+
for all subclasses of HTTPAdapter using Requests>=2.32.2.
|
| 475 |
+
|
| 476 |
+
Returns a urllib3 connection for the given URL. This should not be
|
| 477 |
+
called from user code, and is only exposed for use when subclassing the
|
| 478 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 479 |
+
|
| 480 |
+
:param url: The URL to connect to.
|
| 481 |
+
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
| 482 |
+
:rtype: urllib3.ConnectionPool
|
| 483 |
+
"""
|
| 484 |
+
warnings.warn(
|
| 485 |
+
(
|
| 486 |
+
"`get_connection` has been deprecated in favor of "
|
| 487 |
+
"`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "
|
| 488 |
+
"will need to migrate for Requests>=2.32.2. Please see "
|
| 489 |
+
"https://github.com/psf/requests/pull/6710 for more details."
|
| 490 |
+
),
|
| 491 |
+
DeprecationWarning,
|
| 492 |
+
)
|
| 493 |
+
proxy = select_proxy(url, proxies)
|
| 494 |
+
|
| 495 |
+
if proxy:
|
| 496 |
+
proxy = prepend_scheme_if_needed(proxy, "http")
|
| 497 |
+
proxy_url = parse_url(proxy)
|
| 498 |
+
if not proxy_url.host:
|
| 499 |
+
raise InvalidProxyURL(
|
| 500 |
+
"Please check proxy URL. It is malformed "
|
| 501 |
+
"and could be missing the host."
|
| 502 |
+
)
|
| 503 |
+
proxy_manager = self.proxy_manager_for(proxy)
|
| 504 |
+
conn = proxy_manager.connection_from_url(url)
|
| 505 |
+
else:
|
| 506 |
+
# Only scheme should be lower case
|
| 507 |
+
parsed = urlparse(url)
|
| 508 |
+
url = parsed.geturl()
|
| 509 |
+
conn = self.poolmanager.connection_from_url(url)
|
| 510 |
+
|
| 511 |
+
return conn
|
| 512 |
+
|
| 513 |
+
def close(self):
|
| 514 |
+
"""Disposes of any internal state.
|
| 515 |
+
|
| 516 |
+
Currently, this closes the PoolManager and any active ProxyManager,
|
| 517 |
+
which closes any pooled connections.
|
| 518 |
+
"""
|
| 519 |
+
self.poolmanager.clear()
|
| 520 |
+
for proxy in self.proxy_manager.values():
|
| 521 |
+
proxy.clear()
|
| 522 |
+
|
| 523 |
+
def request_url(self, request, proxies):
|
| 524 |
+
"""Obtain the url to use when making the final request.
|
| 525 |
+
|
| 526 |
+
If the message is being sent through a HTTP proxy, the full URL has to
|
| 527 |
+
be used. Otherwise, we should only use the path portion of the URL.
|
| 528 |
+
|
| 529 |
+
This should not be called from user code, and is only exposed for use
|
| 530 |
+
when subclassing the
|
| 531 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 532 |
+
|
| 533 |
+
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
| 534 |
+
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
|
| 535 |
+
:rtype: str
|
| 536 |
+
"""
|
| 537 |
+
proxy = select_proxy(request.url, proxies)
|
| 538 |
+
scheme = urlparse(request.url).scheme
|
| 539 |
+
|
| 540 |
+
is_proxied_http_request = proxy and scheme != "https"
|
| 541 |
+
using_socks_proxy = False
|
| 542 |
+
if proxy:
|
| 543 |
+
proxy_scheme = urlparse(proxy).scheme.lower()
|
| 544 |
+
using_socks_proxy = proxy_scheme.startswith("socks")
|
| 545 |
+
|
| 546 |
+
url = request.path_url
|
| 547 |
+
if url.startswith("//"): # Don't confuse urllib3
|
| 548 |
+
url = f"/{url.lstrip('/')}"
|
| 549 |
+
|
| 550 |
+
if is_proxied_http_request and not using_socks_proxy:
|
| 551 |
+
url = urldefragauth(request.url)
|
| 552 |
+
|
| 553 |
+
return url
|
| 554 |
+
|
| 555 |
+
def add_headers(self, request, **kwargs):
|
| 556 |
+
"""Add any headers needed by the connection. As of v2.0 this does
|
| 557 |
+
nothing by default, but is left for overriding by users that subclass
|
| 558 |
+
the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 559 |
+
|
| 560 |
+
This should not be called from user code, and is only exposed for use
|
| 561 |
+
when subclassing the
|
| 562 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 563 |
+
|
| 564 |
+
:param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.
|
| 565 |
+
:param kwargs: The keyword arguments from the call to send().
|
| 566 |
+
"""
|
| 567 |
+
pass
|
| 568 |
+
|
| 569 |
+
def proxy_headers(self, proxy):
|
| 570 |
+
"""Returns a dictionary of the headers to add to any request sent
|
| 571 |
+
through a proxy. This works with urllib3 magic to ensure that they are
|
| 572 |
+
correctly sent to the proxy, rather than in a tunnelled request if
|
| 573 |
+
CONNECT is being used.
|
| 574 |
+
|
| 575 |
+
This should not be called from user code, and is only exposed for use
|
| 576 |
+
when subclassing the
|
| 577 |
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
| 578 |
+
|
| 579 |
+
:param proxy: The url of the proxy being used for this request.
|
| 580 |
+
:rtype: dict
|
| 581 |
+
"""
|
| 582 |
+
headers = {}
|
| 583 |
+
username, password = get_auth_from_url(proxy)
|
| 584 |
+
|
| 585 |
+
if username:
|
| 586 |
+
headers["Proxy-Authorization"] = _basic_auth_str(username, password)
|
| 587 |
+
|
| 588 |
+
return headers
|
| 589 |
+
|
| 590 |
+
def send(
|
| 591 |
+
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
|
| 592 |
+
):
|
| 593 |
+
"""Sends PreparedRequest object. Returns Response object.
|
| 594 |
+
|
| 595 |
+
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
| 596 |
+
:param stream: (optional) Whether to stream the request content.
|
| 597 |
+
:param timeout: (optional) How long to wait for the server to send
|
| 598 |
+
data before giving up, as a float, or a :ref:`(connect timeout,
|
| 599 |
+
read timeout) <timeouts>` tuple.
|
| 600 |
+
:type timeout: float or tuple or urllib3 Timeout object
|
| 601 |
+
:param verify: (optional) Either a boolean, in which case it controls whether
|
| 602 |
+
we verify the server's TLS certificate, or a string, in which case it
|
| 603 |
+
must be a path to a CA bundle to use
|
| 604 |
+
:param cert: (optional) Any user-provided SSL certificate to be trusted.
|
| 605 |
+
:param proxies: (optional) The proxies dictionary to apply to the request.
|
| 606 |
+
:rtype: requests.Response
|
| 607 |
+
"""
|
| 608 |
+
|
| 609 |
+
try:
|
| 610 |
+
conn = self.get_connection_with_tls_context(
|
| 611 |
+
request, verify, proxies=proxies, cert=cert
|
| 612 |
+
)
|
| 613 |
+
except LocationValueError as e:
|
| 614 |
+
raise InvalidURL(e, request=request)
|
| 615 |
+
|
| 616 |
+
self.cert_verify(conn, request.url, verify, cert)
|
| 617 |
+
url = self.request_url(request, proxies)
|
| 618 |
+
self.add_headers(
|
| 619 |
+
request,
|
| 620 |
+
stream=stream,
|
| 621 |
+
timeout=timeout,
|
| 622 |
+
verify=verify,
|
| 623 |
+
cert=cert,
|
| 624 |
+
proxies=proxies,
|
| 625 |
+
)
|
| 626 |
+
|
| 627 |
+
chunked = not (request.body is None or "Content-Length" in request.headers)
|
| 628 |
+
|
| 629 |
+
if isinstance(timeout, tuple):
|
| 630 |
+
try:
|
| 631 |
+
connect, read = timeout
|
| 632 |
+
timeout = TimeoutSauce(connect=connect, read=read)
|
| 633 |
+
except ValueError:
|
| 634 |
+
raise ValueError(
|
| 635 |
+
f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
|
| 636 |
+
f"or a single float to set both timeouts to the same value."
|
| 637 |
+
)
|
| 638 |
+
elif isinstance(timeout, TimeoutSauce):
|
| 639 |
+
pass
|
| 640 |
+
else:
|
| 641 |
+
timeout = TimeoutSauce(connect=timeout, read=timeout)
|
| 642 |
+
|
| 643 |
+
try:
|
| 644 |
+
resp = conn.urlopen(
|
| 645 |
+
method=request.method,
|
| 646 |
+
url=url,
|
| 647 |
+
body=request.body,
|
| 648 |
+
headers=request.headers,
|
| 649 |
+
redirect=False,
|
| 650 |
+
assert_same_host=False,
|
| 651 |
+
preload_content=False,
|
| 652 |
+
decode_content=False,
|
| 653 |
+
retries=self.max_retries,
|
| 654 |
+
timeout=timeout,
|
| 655 |
+
chunked=chunked,
|
| 656 |
+
)
|
| 657 |
+
|
| 658 |
+
except (ProtocolError, OSError) as err:
|
| 659 |
+
raise ConnectionError(err, request=request)
|
| 660 |
+
|
| 661 |
+
except MaxRetryError as e:
|
| 662 |
+
if isinstance(e.reason, ConnectTimeoutError):
|
| 663 |
+
# TODO: Remove this in 3.0.0: see #2811
|
| 664 |
+
if not isinstance(e.reason, NewConnectionError):
|
| 665 |
+
raise ConnectTimeout(e, request=request)
|
| 666 |
+
|
| 667 |
+
if isinstance(e.reason, ResponseError):
|
| 668 |
+
raise RetryError(e, request=request)
|
| 669 |
+
|
| 670 |
+
if isinstance(e.reason, _ProxyError):
|
| 671 |
+
raise ProxyError(e, request=request)
|
| 672 |
+
|
| 673 |
+
if isinstance(e.reason, _SSLError):
|
| 674 |
+
# This branch is for urllib3 v1.22 and later.
|
| 675 |
+
raise SSLError(e, request=request)
|
| 676 |
+
|
| 677 |
+
raise ConnectionError(e, request=request)
|
| 678 |
+
|
| 679 |
+
except ClosedPoolError as e:
|
| 680 |
+
raise ConnectionError(e, request=request)
|
| 681 |
+
|
| 682 |
+
except _ProxyError as e:
|
| 683 |
+
raise ProxyError(e)
|
| 684 |
+
|
| 685 |
+
except (_SSLError, _HTTPError) as e:
|
| 686 |
+
if isinstance(e, _SSLError):
|
| 687 |
+
# This branch is for urllib3 versions earlier than v1.22
|
| 688 |
+
raise SSLError(e, request=request)
|
| 689 |
+
elif isinstance(e, ReadTimeoutError):
|
| 690 |
+
raise ReadTimeout(e, request=request)
|
| 691 |
+
elif isinstance(e, _InvalidHeader):
|
| 692 |
+
raise InvalidHeader(e, request=request)
|
| 693 |
+
else:
|
| 694 |
+
raise
|
| 695 |
+
|
| 696 |
+
return self.build_response(request, resp)
|
.venv/Lib/site-packages/requests/api.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests.api
|
| 3 |
+
~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module implements the Requests API.
|
| 6 |
+
|
| 7 |
+
:copyright: (c) 2012 by Kenneth Reitz.
|
| 8 |
+
:license: Apache2, see LICENSE for more details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from . import sessions
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def request(method, url, **kwargs):
|
| 15 |
+
"""Constructs and sends a :class:`Request <Request>`.
|
| 16 |
+
|
| 17 |
+
:param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
|
| 18 |
+
:param url: URL for the new :class:`Request` object.
|
| 19 |
+
:param params: (optional) Dictionary, list of tuples or bytes to send
|
| 20 |
+
in the query string for the :class:`Request`.
|
| 21 |
+
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
|
| 22 |
+
object to send in the body of the :class:`Request`.
|
| 23 |
+
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
| 24 |
+
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
| 25 |
+
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
| 26 |
+
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
|
| 27 |
+
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
|
| 28 |
+
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string
|
| 29 |
+
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
|
| 30 |
+
to add for the file.
|
| 31 |
+
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
| 32 |
+
:param timeout: (optional) How many seconds to wait for the server to send data
|
| 33 |
+
before giving up, as a float, or a :ref:`(connect timeout, read
|
| 34 |
+
timeout) <timeouts>` tuple.
|
| 35 |
+
:type timeout: float or tuple
|
| 36 |
+
:param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
|
| 37 |
+
:type allow_redirects: bool
|
| 38 |
+
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
| 39 |
+
:param verify: (optional) Either a boolean, in which case it controls whether we verify
|
| 40 |
+
the server's TLS certificate, or a string, in which case it must be a path
|
| 41 |
+
to a CA bundle to use. Defaults to ``True``.
|
| 42 |
+
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
|
| 43 |
+
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
| 44 |
+
:return: :class:`Response <Response>` object
|
| 45 |
+
:rtype: requests.Response
|
| 46 |
+
|
| 47 |
+
Usage::
|
| 48 |
+
|
| 49 |
+
>>> import requests
|
| 50 |
+
>>> req = requests.request('GET', 'https://httpbin.org/get')
|
| 51 |
+
>>> req
|
| 52 |
+
<Response [200]>
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
# By using the 'with' statement we are sure the session is closed, thus we
|
| 56 |
+
# avoid leaving sockets open which can trigger a ResourceWarning in some
|
| 57 |
+
# cases, and look like a memory leak in others.
|
| 58 |
+
with sessions.Session() as session:
|
| 59 |
+
return session.request(method=method, url=url, **kwargs)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def get(url, params=None, **kwargs):
|
| 63 |
+
r"""Sends a GET request.
|
| 64 |
+
|
| 65 |
+
:param url: URL for the new :class:`Request` object.
|
| 66 |
+
:param params: (optional) Dictionary, list of tuples or bytes to send
|
| 67 |
+
in the query string for the :class:`Request`.
|
| 68 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
| 69 |
+
:return: :class:`Response <Response>` object
|
| 70 |
+
:rtype: requests.Response
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
return request("get", url, params=params, **kwargs)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def options(url, **kwargs):
|
| 77 |
+
r"""Sends an OPTIONS request.
|
| 78 |
+
|
| 79 |
+
:param url: URL for the new :class:`Request` object.
|
| 80 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
| 81 |
+
:return: :class:`Response <Response>` object
|
| 82 |
+
:rtype: requests.Response
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
return request("options", url, **kwargs)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def head(url, **kwargs):
|
| 89 |
+
r"""Sends a HEAD request.
|
| 90 |
+
|
| 91 |
+
:param url: URL for the new :class:`Request` object.
|
| 92 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes. If
|
| 93 |
+
`allow_redirects` is not provided, it will be set to `False` (as
|
| 94 |
+
opposed to the default :meth:`request` behavior).
|
| 95 |
+
:return: :class:`Response <Response>` object
|
| 96 |
+
:rtype: requests.Response
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
kwargs.setdefault("allow_redirects", False)
|
| 100 |
+
return request("head", url, **kwargs)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def post(url, data=None, json=None, **kwargs):
|
| 104 |
+
r"""Sends a POST request.
|
| 105 |
+
|
| 106 |
+
:param url: URL for the new :class:`Request` object.
|
| 107 |
+
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
|
| 108 |
+
object to send in the body of the :class:`Request`.
|
| 109 |
+
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
| 110 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
| 111 |
+
:return: :class:`Response <Response>` object
|
| 112 |
+
:rtype: requests.Response
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
return request("post", url, data=data, json=json, **kwargs)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def put(url, data=None, **kwargs):
|
| 119 |
+
r"""Sends a PUT request.
|
| 120 |
+
|
| 121 |
+
:param url: URL for the new :class:`Request` object.
|
| 122 |
+
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
|
| 123 |
+
object to send in the body of the :class:`Request`.
|
| 124 |
+
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
| 125 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
| 126 |
+
:return: :class:`Response <Response>` object
|
| 127 |
+
:rtype: requests.Response
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
return request("put", url, data=data, **kwargs)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def patch(url, data=None, **kwargs):
|
| 134 |
+
r"""Sends a PATCH request.
|
| 135 |
+
|
| 136 |
+
:param url: URL for the new :class:`Request` object.
|
| 137 |
+
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
|
| 138 |
+
object to send in the body of the :class:`Request`.
|
| 139 |
+
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
| 140 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
| 141 |
+
:return: :class:`Response <Response>` object
|
| 142 |
+
:rtype: requests.Response
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
return request("patch", url, data=data, **kwargs)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def delete(url, **kwargs):
|
| 149 |
+
r"""Sends a DELETE request.
|
| 150 |
+
|
| 151 |
+
:param url: URL for the new :class:`Request` object.
|
| 152 |
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
| 153 |
+
:return: :class:`Response <Response>` object
|
| 154 |
+
:rtype: requests.Response
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
return request("delete", url, **kwargs)
|
.venv/Lib/site-packages/requests/auth.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests.auth
|
| 3 |
+
~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module contains the authentication handlers for Requests.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import hashlib
|
| 9 |
+
import os
|
| 10 |
+
import re
|
| 11 |
+
import threading
|
| 12 |
+
import time
|
| 13 |
+
import warnings
|
| 14 |
+
from base64 import b64encode
|
| 15 |
+
|
| 16 |
+
from ._internal_utils import to_native_string
|
| 17 |
+
from .compat import basestring, str, urlparse
|
| 18 |
+
from .cookies import extract_cookies_to_jar
|
| 19 |
+
from .utils import parse_dict_header
|
| 20 |
+
|
| 21 |
+
CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
|
| 22 |
+
CONTENT_TYPE_MULTI_PART = "multipart/form-data"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _basic_auth_str(username, password):
|
| 26 |
+
"""Returns a Basic Auth string."""
|
| 27 |
+
|
| 28 |
+
# "I want us to put a big-ol' comment on top of it that
|
| 29 |
+
# says that this behaviour is dumb but we need to preserve
|
| 30 |
+
# it because people are relying on it."
|
| 31 |
+
# - Lukasa
|
| 32 |
+
#
|
| 33 |
+
# These are here solely to maintain backwards compatibility
|
| 34 |
+
# for things like ints. This will be removed in 3.0.0.
|
| 35 |
+
if not isinstance(username, basestring):
|
| 36 |
+
warnings.warn(
|
| 37 |
+
"Non-string usernames will no longer be supported in Requests "
|
| 38 |
+
"3.0.0. Please convert the object you've passed in ({!r}) to "
|
| 39 |
+
"a string or bytes object in the near future to avoid "
|
| 40 |
+
"problems.".format(username),
|
| 41 |
+
category=DeprecationWarning,
|
| 42 |
+
)
|
| 43 |
+
username = str(username)
|
| 44 |
+
|
| 45 |
+
if not isinstance(password, basestring):
|
| 46 |
+
warnings.warn(
|
| 47 |
+
"Non-string passwords will no longer be supported in Requests "
|
| 48 |
+
"3.0.0. Please convert the object you've passed in ({!r}) to "
|
| 49 |
+
"a string or bytes object in the near future to avoid "
|
| 50 |
+
"problems.".format(type(password)),
|
| 51 |
+
category=DeprecationWarning,
|
| 52 |
+
)
|
| 53 |
+
password = str(password)
|
| 54 |
+
# -- End Removal --
|
| 55 |
+
|
| 56 |
+
if isinstance(username, str):
|
| 57 |
+
username = username.encode("latin1")
|
| 58 |
+
|
| 59 |
+
if isinstance(password, str):
|
| 60 |
+
password = password.encode("latin1")
|
| 61 |
+
|
| 62 |
+
authstr = "Basic " + to_native_string(
|
| 63 |
+
b64encode(b":".join((username, password))).strip()
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
return authstr
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class AuthBase:
|
| 70 |
+
"""Base class that all auth implementations derive from"""
|
| 71 |
+
|
| 72 |
+
def __call__(self, r):
|
| 73 |
+
raise NotImplementedError("Auth hooks must be callable.")
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class HTTPBasicAuth(AuthBase):
|
| 77 |
+
"""Attaches HTTP Basic Authentication to the given Request object."""
|
| 78 |
+
|
| 79 |
+
def __init__(self, username, password):
|
| 80 |
+
self.username = username
|
| 81 |
+
self.password = password
|
| 82 |
+
|
| 83 |
+
def __eq__(self, other):
|
| 84 |
+
return all(
|
| 85 |
+
[
|
| 86 |
+
self.username == getattr(other, "username", None),
|
| 87 |
+
self.password == getattr(other, "password", None),
|
| 88 |
+
]
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
def __ne__(self, other):
|
| 92 |
+
return not self == other
|
| 93 |
+
|
| 94 |
+
def __call__(self, r):
|
| 95 |
+
r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
|
| 96 |
+
return r
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class HTTPProxyAuth(HTTPBasicAuth):
|
| 100 |
+
"""Attaches HTTP Proxy Authentication to a given Request object."""
|
| 101 |
+
|
| 102 |
+
def __call__(self, r):
|
| 103 |
+
r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
|
| 104 |
+
return r
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class HTTPDigestAuth(AuthBase):
|
| 108 |
+
"""Attaches HTTP Digest Authentication to the given Request object."""
|
| 109 |
+
|
| 110 |
+
def __init__(self, username, password):
|
| 111 |
+
self.username = username
|
| 112 |
+
self.password = password
|
| 113 |
+
# Keep state in per-thread local storage
|
| 114 |
+
self._thread_local = threading.local()
|
| 115 |
+
|
| 116 |
+
def init_per_thread_state(self):
|
| 117 |
+
# Ensure state is initialized just once per-thread
|
| 118 |
+
if not hasattr(self._thread_local, "init"):
|
| 119 |
+
self._thread_local.init = True
|
| 120 |
+
self._thread_local.last_nonce = ""
|
| 121 |
+
self._thread_local.nonce_count = 0
|
| 122 |
+
self._thread_local.chal = {}
|
| 123 |
+
self._thread_local.pos = None
|
| 124 |
+
self._thread_local.num_401_calls = None
|
| 125 |
+
|
| 126 |
+
def build_digest_header(self, method, url):
|
| 127 |
+
"""
|
| 128 |
+
:rtype: str
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
realm = self._thread_local.chal["realm"]
|
| 132 |
+
nonce = self._thread_local.chal["nonce"]
|
| 133 |
+
qop = self._thread_local.chal.get("qop")
|
| 134 |
+
algorithm = self._thread_local.chal.get("algorithm")
|
| 135 |
+
opaque = self._thread_local.chal.get("opaque")
|
| 136 |
+
hash_utf8 = None
|
| 137 |
+
|
| 138 |
+
if algorithm is None:
|
| 139 |
+
_algorithm = "MD5"
|
| 140 |
+
else:
|
| 141 |
+
_algorithm = algorithm.upper()
|
| 142 |
+
# lambdas assume digest modules are imported at the top level
|
| 143 |
+
if _algorithm == "MD5" or _algorithm == "MD5-SESS":
|
| 144 |
+
|
| 145 |
+
def md5_utf8(x):
|
| 146 |
+
if isinstance(x, str):
|
| 147 |
+
x = x.encode("utf-8")
|
| 148 |
+
return hashlib.md5(x).hexdigest()
|
| 149 |
+
|
| 150 |
+
hash_utf8 = md5_utf8
|
| 151 |
+
elif _algorithm == "SHA":
|
| 152 |
+
|
| 153 |
+
def sha_utf8(x):
|
| 154 |
+
if isinstance(x, str):
|
| 155 |
+
x = x.encode("utf-8")
|
| 156 |
+
return hashlib.sha1(x).hexdigest()
|
| 157 |
+
|
| 158 |
+
hash_utf8 = sha_utf8
|
| 159 |
+
elif _algorithm == "SHA-256":
|
| 160 |
+
|
| 161 |
+
def sha256_utf8(x):
|
| 162 |
+
if isinstance(x, str):
|
| 163 |
+
x = x.encode("utf-8")
|
| 164 |
+
return hashlib.sha256(x).hexdigest()
|
| 165 |
+
|
| 166 |
+
hash_utf8 = sha256_utf8
|
| 167 |
+
elif _algorithm == "SHA-512":
|
| 168 |
+
|
| 169 |
+
def sha512_utf8(x):
|
| 170 |
+
if isinstance(x, str):
|
| 171 |
+
x = x.encode("utf-8")
|
| 172 |
+
return hashlib.sha512(x).hexdigest()
|
| 173 |
+
|
| 174 |
+
hash_utf8 = sha512_utf8
|
| 175 |
+
|
| 176 |
+
KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731
|
| 177 |
+
|
| 178 |
+
if hash_utf8 is None:
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
# XXX not implemented yet
|
| 182 |
+
entdig = None
|
| 183 |
+
p_parsed = urlparse(url)
|
| 184 |
+
#: path is request-uri defined in RFC 2616 which should not be empty
|
| 185 |
+
path = p_parsed.path or "/"
|
| 186 |
+
if p_parsed.query:
|
| 187 |
+
path += f"?{p_parsed.query}"
|
| 188 |
+
|
| 189 |
+
A1 = f"{self.username}:{realm}:{self.password}"
|
| 190 |
+
A2 = f"{method}:{path}"
|
| 191 |
+
|
| 192 |
+
HA1 = hash_utf8(A1)
|
| 193 |
+
HA2 = hash_utf8(A2)
|
| 194 |
+
|
| 195 |
+
if nonce == self._thread_local.last_nonce:
|
| 196 |
+
self._thread_local.nonce_count += 1
|
| 197 |
+
else:
|
| 198 |
+
self._thread_local.nonce_count = 1
|
| 199 |
+
ncvalue = f"{self._thread_local.nonce_count:08x}"
|
| 200 |
+
s = str(self._thread_local.nonce_count).encode("utf-8")
|
| 201 |
+
s += nonce.encode("utf-8")
|
| 202 |
+
s += time.ctime().encode("utf-8")
|
| 203 |
+
s += os.urandom(8)
|
| 204 |
+
|
| 205 |
+
cnonce = hashlib.sha1(s).hexdigest()[:16]
|
| 206 |
+
if _algorithm == "MD5-SESS":
|
| 207 |
+
HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")
|
| 208 |
+
|
| 209 |
+
if not qop:
|
| 210 |
+
respdig = KD(HA1, f"{nonce}:{HA2}")
|
| 211 |
+
elif qop == "auth" or "auth" in qop.split(","):
|
| 212 |
+
noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
|
| 213 |
+
respdig = KD(HA1, noncebit)
|
| 214 |
+
else:
|
| 215 |
+
# XXX handle auth-int.
|
| 216 |
+
return None
|
| 217 |
+
|
| 218 |
+
self._thread_local.last_nonce = nonce
|
| 219 |
+
|
| 220 |
+
# XXX should the partial digests be encoded too?
|
| 221 |
+
base = (
|
| 222 |
+
f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
|
| 223 |
+
f'uri="{path}", response="{respdig}"'
|
| 224 |
+
)
|
| 225 |
+
if opaque:
|
| 226 |
+
base += f', opaque="{opaque}"'
|
| 227 |
+
if algorithm:
|
| 228 |
+
base += f', algorithm="{algorithm}"'
|
| 229 |
+
if entdig:
|
| 230 |
+
base += f', digest="{entdig}"'
|
| 231 |
+
if qop:
|
| 232 |
+
base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
|
| 233 |
+
|
| 234 |
+
return f"Digest {base}"
|
| 235 |
+
|
| 236 |
+
def handle_redirect(self, r, **kwargs):
|
| 237 |
+
"""Reset num_401_calls counter on redirects."""
|
| 238 |
+
if r.is_redirect:
|
| 239 |
+
self._thread_local.num_401_calls = 1
|
| 240 |
+
|
| 241 |
+
def handle_401(self, r, **kwargs):
|
| 242 |
+
"""
|
| 243 |
+
Takes the given response and tries digest-auth, if needed.
|
| 244 |
+
|
| 245 |
+
:rtype: requests.Response
|
| 246 |
+
"""
|
| 247 |
+
|
| 248 |
+
# If response is not 4xx, do not auth
|
| 249 |
+
# See https://github.com/psf/requests/issues/3772
|
| 250 |
+
if not 400 <= r.status_code < 500:
|
| 251 |
+
self._thread_local.num_401_calls = 1
|
| 252 |
+
return r
|
| 253 |
+
|
| 254 |
+
if self._thread_local.pos is not None:
|
| 255 |
+
# Rewind the file position indicator of the body to where
|
| 256 |
+
# it was to resend the request.
|
| 257 |
+
r.request.body.seek(self._thread_local.pos)
|
| 258 |
+
s_auth = r.headers.get("www-authenticate", "")
|
| 259 |
+
|
| 260 |
+
if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
|
| 261 |
+
self._thread_local.num_401_calls += 1
|
| 262 |
+
pat = re.compile(r"digest ", flags=re.IGNORECASE)
|
| 263 |
+
self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))
|
| 264 |
+
|
| 265 |
+
# Consume content and release the original connection
|
| 266 |
+
# to allow our new request to reuse the same one.
|
| 267 |
+
r.content
|
| 268 |
+
r.close()
|
| 269 |
+
prep = r.request.copy()
|
| 270 |
+
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
|
| 271 |
+
prep.prepare_cookies(prep._cookies)
|
| 272 |
+
|
| 273 |
+
prep.headers["Authorization"] = self.build_digest_header(
|
| 274 |
+
prep.method, prep.url
|
| 275 |
+
)
|
| 276 |
+
_r = r.connection.send(prep, **kwargs)
|
| 277 |
+
_r.history.append(r)
|
| 278 |
+
_r.request = prep
|
| 279 |
+
|
| 280 |
+
return _r
|
| 281 |
+
|
| 282 |
+
self._thread_local.num_401_calls = 1
|
| 283 |
+
return r
|
| 284 |
+
|
| 285 |
+
def __call__(self, r):
|
| 286 |
+
# Initialize per-thread state, if needed
|
| 287 |
+
self.init_per_thread_state()
|
| 288 |
+
# If we have a saved nonce, skip the 401
|
| 289 |
+
if self._thread_local.last_nonce:
|
| 290 |
+
r.headers["Authorization"] = self.build_digest_header(r.method, r.url)
|
| 291 |
+
try:
|
| 292 |
+
self._thread_local.pos = r.body.tell()
|
| 293 |
+
except AttributeError:
|
| 294 |
+
# In the case of HTTPDigestAuth being reused and the body of
|
| 295 |
+
# the previous request was a file-like object, pos has the
|
| 296 |
+
# file position of the previous body. Ensure it's set to
|
| 297 |
+
# None.
|
| 298 |
+
self._thread_local.pos = None
|
| 299 |
+
r.register_hook("response", self.handle_401)
|
| 300 |
+
r.register_hook("response", self.handle_redirect)
|
| 301 |
+
self._thread_local.num_401_calls = 1
|
| 302 |
+
|
| 303 |
+
return r
|
| 304 |
+
|
| 305 |
+
def __eq__(self, other):
|
| 306 |
+
return all(
|
| 307 |
+
[
|
| 308 |
+
self.username == getattr(other, "username", None),
|
| 309 |
+
self.password == getattr(other, "password", None),
|
| 310 |
+
]
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
def __ne__(self, other):
|
| 314 |
+
return not self == other
|
.venv/Lib/site-packages/requests/certs.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
requests.certs
|
| 5 |
+
~~~~~~~~~~~~~~
|
| 6 |
+
|
| 7 |
+
This module returns the preferred default CA certificate bundle. There is
|
| 8 |
+
only one — the one from the certifi package.
|
| 9 |
+
|
| 10 |
+
If you are packaging Requests, e.g., for a Linux distribution or a managed
|
| 11 |
+
environment, you can change the definition of where() to return a separately
|
| 12 |
+
packaged CA bundle.
|
| 13 |
+
"""
|
| 14 |
+
from certifi import where
|
| 15 |
+
|
| 16 |
+
if __name__ == "__main__":
|
| 17 |
+
print(where())
|
.venv/Lib/site-packages/requests/compat.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests.compat
|
| 3 |
+
~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module previously handled import compatibility issues
|
| 6 |
+
between Python 2 and Python 3. It remains for backwards
|
| 7 |
+
compatibility until the next major version.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import importlib
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
# -------
|
| 14 |
+
# urllib3
|
| 15 |
+
# -------
|
| 16 |
+
from urllib3 import __version__ as urllib3_version
|
| 17 |
+
|
| 18 |
+
# Detect which major version of urllib3 is being used.
|
| 19 |
+
try:
|
| 20 |
+
is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1
|
| 21 |
+
except (TypeError, AttributeError):
|
| 22 |
+
# If we can't discern a version, prefer old functionality.
|
| 23 |
+
is_urllib3_1 = True
|
| 24 |
+
|
| 25 |
+
# -------------------
|
| 26 |
+
# Character Detection
|
| 27 |
+
# -------------------
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _resolve_char_detection():
|
| 31 |
+
"""Find supported character detection libraries."""
|
| 32 |
+
chardet = None
|
| 33 |
+
for lib in ("chardet", "charset_normalizer"):
|
| 34 |
+
if chardet is None:
|
| 35 |
+
try:
|
| 36 |
+
chardet = importlib.import_module(lib)
|
| 37 |
+
except ImportError:
|
| 38 |
+
pass
|
| 39 |
+
return chardet
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
chardet = _resolve_char_detection()
|
| 43 |
+
|
| 44 |
+
# -------
|
| 45 |
+
# Pythons
|
| 46 |
+
# -------
|
| 47 |
+
|
| 48 |
+
# Syntax sugar.
|
| 49 |
+
_ver = sys.version_info
|
| 50 |
+
|
| 51 |
+
#: Python 2.x?
|
| 52 |
+
is_py2 = _ver[0] == 2
|
| 53 |
+
|
| 54 |
+
#: Python 3.x?
|
| 55 |
+
is_py3 = _ver[0] == 3
|
| 56 |
+
|
| 57 |
+
# json/simplejson module import resolution
|
| 58 |
+
has_simplejson = False
|
| 59 |
+
try:
|
| 60 |
+
import simplejson as json
|
| 61 |
+
|
| 62 |
+
has_simplejson = True
|
| 63 |
+
except ImportError:
|
| 64 |
+
import json
|
| 65 |
+
|
| 66 |
+
if has_simplejson:
|
| 67 |
+
from simplejson import JSONDecodeError
|
| 68 |
+
else:
|
| 69 |
+
from json import JSONDecodeError
|
| 70 |
+
|
| 71 |
+
# Keep OrderedDict for backwards compatibility.
|
| 72 |
+
from collections import OrderedDict
|
| 73 |
+
from collections.abc import Callable, Mapping, MutableMapping
|
| 74 |
+
from http import cookiejar as cookielib
|
| 75 |
+
from http.cookies import Morsel
|
| 76 |
+
from io import StringIO
|
| 77 |
+
|
| 78 |
+
# --------------
|
| 79 |
+
# Legacy Imports
|
| 80 |
+
# --------------
|
| 81 |
+
from urllib.parse import (
|
| 82 |
+
quote,
|
| 83 |
+
quote_plus,
|
| 84 |
+
unquote,
|
| 85 |
+
unquote_plus,
|
| 86 |
+
urldefrag,
|
| 87 |
+
urlencode,
|
| 88 |
+
urljoin,
|
| 89 |
+
urlparse,
|
| 90 |
+
urlsplit,
|
| 91 |
+
urlunparse,
|
| 92 |
+
)
|
| 93 |
+
from urllib.request import (
|
| 94 |
+
getproxies,
|
| 95 |
+
getproxies_environment,
|
| 96 |
+
parse_http_list,
|
| 97 |
+
proxy_bypass,
|
| 98 |
+
proxy_bypass_environment,
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
builtin_str = str
|
| 102 |
+
str = str
|
| 103 |
+
bytes = bytes
|
| 104 |
+
basestring = (str, bytes)
|
| 105 |
+
numeric_types = (int, float)
|
| 106 |
+
integer_types = (int,)
|
.venv/Lib/site-packages/requests/cookies.py
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests.cookies
|
| 3 |
+
~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Compatibility code to be able to use `http.cookiejar.CookieJar` with requests.
|
| 6 |
+
|
| 7 |
+
requests.utils imports from here, so be careful with imports.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import calendar
|
| 11 |
+
import copy
|
| 12 |
+
import time
|
| 13 |
+
|
| 14 |
+
from ._internal_utils import to_native_string
|
| 15 |
+
from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
import threading
|
| 19 |
+
except ImportError:
|
| 20 |
+
import dummy_threading as threading
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class MockRequest:
|
| 24 |
+
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
|
| 25 |
+
|
| 26 |
+
The code in `http.cookiejar.CookieJar` expects this interface in order to correctly
|
| 27 |
+
manage cookie policies, i.e., determine whether a cookie can be set, given the
|
| 28 |
+
domains of the request and the cookie.
|
| 29 |
+
|
| 30 |
+
The original request object is read-only. The client is responsible for collecting
|
| 31 |
+
the new headers via `get_new_headers()` and interpreting them appropriately. You
|
| 32 |
+
probably want `get_cookie_header`, defined below.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(self, request):
|
| 36 |
+
self._r = request
|
| 37 |
+
self._new_headers = {}
|
| 38 |
+
self.type = urlparse(self._r.url).scheme
|
| 39 |
+
|
| 40 |
+
def get_type(self):
|
| 41 |
+
return self.type
|
| 42 |
+
|
| 43 |
+
def get_host(self):
|
| 44 |
+
return urlparse(self._r.url).netloc
|
| 45 |
+
|
| 46 |
+
def get_origin_req_host(self):
|
| 47 |
+
return self.get_host()
|
| 48 |
+
|
| 49 |
+
def get_full_url(self):
|
| 50 |
+
# Only return the response's URL if the user hadn't set the Host
|
| 51 |
+
# header
|
| 52 |
+
if not self._r.headers.get("Host"):
|
| 53 |
+
return self._r.url
|
| 54 |
+
# If they did set it, retrieve it and reconstruct the expected domain
|
| 55 |
+
host = to_native_string(self._r.headers["Host"], encoding="utf-8")
|
| 56 |
+
parsed = urlparse(self._r.url)
|
| 57 |
+
# Reconstruct the URL as we expect it
|
| 58 |
+
return urlunparse(
|
| 59 |
+
[
|
| 60 |
+
parsed.scheme,
|
| 61 |
+
host,
|
| 62 |
+
parsed.path,
|
| 63 |
+
parsed.params,
|
| 64 |
+
parsed.query,
|
| 65 |
+
parsed.fragment,
|
| 66 |
+
]
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def is_unverifiable(self):
|
| 70 |
+
return True
|
| 71 |
+
|
| 72 |
+
def has_header(self, name):
|
| 73 |
+
return name in self._r.headers or name in self._new_headers
|
| 74 |
+
|
| 75 |
+
def get_header(self, name, default=None):
|
| 76 |
+
return self._r.headers.get(name, self._new_headers.get(name, default))
|
| 77 |
+
|
| 78 |
+
def add_header(self, key, val):
|
| 79 |
+
"""cookiejar has no legitimate use for this method; add it back if you find one."""
|
| 80 |
+
raise NotImplementedError(
|
| 81 |
+
"Cookie headers should be added with add_unredirected_header()"
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
def add_unredirected_header(self, name, value):
|
| 85 |
+
self._new_headers[name] = value
|
| 86 |
+
|
| 87 |
+
def get_new_headers(self):
|
| 88 |
+
return self._new_headers
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def unverifiable(self):
|
| 92 |
+
return self.is_unverifiable()
|
| 93 |
+
|
| 94 |
+
@property
|
| 95 |
+
def origin_req_host(self):
|
| 96 |
+
return self.get_origin_req_host()
|
| 97 |
+
|
| 98 |
+
@property
|
| 99 |
+
def host(self):
|
| 100 |
+
return self.get_host()
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class MockResponse:
|
| 104 |
+
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
|
| 105 |
+
|
| 106 |
+
...what? Basically, expose the parsed HTTP headers from the server response
|
| 107 |
+
the way `http.cookiejar` expects to see them.
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
def __init__(self, headers):
|
| 111 |
+
"""Make a MockResponse for `cookiejar` to read.
|
| 112 |
+
|
| 113 |
+
:param headers: a httplib.HTTPMessage or analogous carrying the headers
|
| 114 |
+
"""
|
| 115 |
+
self._headers = headers
|
| 116 |
+
|
| 117 |
+
def info(self):
|
| 118 |
+
return self._headers
|
| 119 |
+
|
| 120 |
+
def getheaders(self, name):
|
| 121 |
+
self._headers.getheaders(name)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def extract_cookies_to_jar(jar, request, response):
|
| 125 |
+
"""Extract the cookies from the response into a CookieJar.
|
| 126 |
+
|
| 127 |
+
:param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar)
|
| 128 |
+
:param request: our own requests.Request object
|
| 129 |
+
:param response: urllib3.HTTPResponse object
|
| 130 |
+
"""
|
| 131 |
+
if not (hasattr(response, "_original_response") and response._original_response):
|
| 132 |
+
return
|
| 133 |
+
# the _original_response field is the wrapped httplib.HTTPResponse object,
|
| 134 |
+
req = MockRequest(request)
|
| 135 |
+
# pull out the HTTPMessage with the headers and put it in the mock:
|
| 136 |
+
res = MockResponse(response._original_response.msg)
|
| 137 |
+
jar.extract_cookies(res, req)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def get_cookie_header(jar, request):
|
| 141 |
+
"""
|
| 142 |
+
Produce an appropriate Cookie header string to be sent with `request`, or None.
|
| 143 |
+
|
| 144 |
+
:rtype: str
|
| 145 |
+
"""
|
| 146 |
+
r = MockRequest(request)
|
| 147 |
+
jar.add_cookie_header(r)
|
| 148 |
+
return r.get_new_headers().get("Cookie")
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
| 152 |
+
"""Unsets a cookie by name, by default over all domains and paths.
|
| 153 |
+
|
| 154 |
+
Wraps CookieJar.clear(), is O(n).
|
| 155 |
+
"""
|
| 156 |
+
clearables = []
|
| 157 |
+
for cookie in cookiejar:
|
| 158 |
+
if cookie.name != name:
|
| 159 |
+
continue
|
| 160 |
+
if domain is not None and domain != cookie.domain:
|
| 161 |
+
continue
|
| 162 |
+
if path is not None and path != cookie.path:
|
| 163 |
+
continue
|
| 164 |
+
clearables.append((cookie.domain, cookie.path, cookie.name))
|
| 165 |
+
|
| 166 |
+
for domain, path, name in clearables:
|
| 167 |
+
cookiejar.clear(domain, path, name)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class CookieConflictError(RuntimeError):
|
| 171 |
+
"""There are two cookies that meet the criteria specified in the cookie jar.
|
| 172 |
+
Use .get and .set and include domain and path args in order to be more specific.
|
| 173 |
+
"""
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
|
| 177 |
+
"""Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict
|
| 178 |
+
interface.
|
| 179 |
+
|
| 180 |
+
This is the CookieJar we create by default for requests and sessions that
|
| 181 |
+
don't specify one, since some clients may expect response.cookies and
|
| 182 |
+
session.cookies to support dict operations.
|
| 183 |
+
|
| 184 |
+
Requests does not use the dict interface internally; it's just for
|
| 185 |
+
compatibility with external client code. All requests code should work
|
| 186 |
+
out of the box with externally provided instances of ``CookieJar``, e.g.
|
| 187 |
+
``LWPCookieJar`` and ``FileCookieJar``.
|
| 188 |
+
|
| 189 |
+
Unlike a regular CookieJar, this class is pickleable.
|
| 190 |
+
|
| 191 |
+
.. warning:: dictionary operations that are normally O(1) may be O(n).
|
| 192 |
+
"""
|
| 193 |
+
|
| 194 |
+
def get(self, name, default=None, domain=None, path=None):
|
| 195 |
+
"""Dict-like get() that also supports optional domain and path args in
|
| 196 |
+
order to resolve naming collisions from using one cookie jar over
|
| 197 |
+
multiple domains.
|
| 198 |
+
|
| 199 |
+
.. warning:: operation is O(n), not O(1).
|
| 200 |
+
"""
|
| 201 |
+
try:
|
| 202 |
+
return self._find_no_duplicates(name, domain, path)
|
| 203 |
+
except KeyError:
|
| 204 |
+
return default
|
| 205 |
+
|
| 206 |
+
def set(self, name, value, **kwargs):
|
| 207 |
+
"""Dict-like set() that also supports optional domain and path args in
|
| 208 |
+
order to resolve naming collisions from using one cookie jar over
|
| 209 |
+
multiple domains.
|
| 210 |
+
"""
|
| 211 |
+
# support client code that unsets cookies by assignment of a None value:
|
| 212 |
+
if value is None:
|
| 213 |
+
remove_cookie_by_name(
|
| 214 |
+
self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
|
| 215 |
+
)
|
| 216 |
+
return
|
| 217 |
+
|
| 218 |
+
if isinstance(value, Morsel):
|
| 219 |
+
c = morsel_to_cookie(value)
|
| 220 |
+
else:
|
| 221 |
+
c = create_cookie(name, value, **kwargs)
|
| 222 |
+
self.set_cookie(c)
|
| 223 |
+
return c
|
| 224 |
+
|
| 225 |
+
def iterkeys(self):
|
| 226 |
+
"""Dict-like iterkeys() that returns an iterator of names of cookies
|
| 227 |
+
from the jar.
|
| 228 |
+
|
| 229 |
+
.. seealso:: itervalues() and iteritems().
|
| 230 |
+
"""
|
| 231 |
+
for cookie in iter(self):
|
| 232 |
+
yield cookie.name
|
| 233 |
+
|
| 234 |
+
def keys(self):
|
| 235 |
+
"""Dict-like keys() that returns a list of names of cookies from the
|
| 236 |
+
jar.
|
| 237 |
+
|
| 238 |
+
.. seealso:: values() and items().
|
| 239 |
+
"""
|
| 240 |
+
return list(self.iterkeys())
|
| 241 |
+
|
| 242 |
+
def itervalues(self):
|
| 243 |
+
"""Dict-like itervalues() that returns an iterator of values of cookies
|
| 244 |
+
from the jar.
|
| 245 |
+
|
| 246 |
+
.. seealso:: iterkeys() and iteritems().
|
| 247 |
+
"""
|
| 248 |
+
for cookie in iter(self):
|
| 249 |
+
yield cookie.value
|
| 250 |
+
|
| 251 |
+
def values(self):
|
| 252 |
+
"""Dict-like values() that returns a list of values of cookies from the
|
| 253 |
+
jar.
|
| 254 |
+
|
| 255 |
+
.. seealso:: keys() and items().
|
| 256 |
+
"""
|
| 257 |
+
return list(self.itervalues())
|
| 258 |
+
|
| 259 |
+
def iteritems(self):
|
| 260 |
+
"""Dict-like iteritems() that returns an iterator of name-value tuples
|
| 261 |
+
from the jar.
|
| 262 |
+
|
| 263 |
+
.. seealso:: iterkeys() and itervalues().
|
| 264 |
+
"""
|
| 265 |
+
for cookie in iter(self):
|
| 266 |
+
yield cookie.name, cookie.value
|
| 267 |
+
|
| 268 |
+
def items(self):
|
| 269 |
+
"""Dict-like items() that returns a list of name-value tuples from the
|
| 270 |
+
jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
|
| 271 |
+
vanilla python dict of key value pairs.
|
| 272 |
+
|
| 273 |
+
.. seealso:: keys() and values().
|
| 274 |
+
"""
|
| 275 |
+
return list(self.iteritems())
|
| 276 |
+
|
| 277 |
+
def list_domains(self):
|
| 278 |
+
"""Utility method to list all the domains in the jar."""
|
| 279 |
+
domains = []
|
| 280 |
+
for cookie in iter(self):
|
| 281 |
+
if cookie.domain not in domains:
|
| 282 |
+
domains.append(cookie.domain)
|
| 283 |
+
return domains
|
| 284 |
+
|
| 285 |
+
def list_paths(self):
|
| 286 |
+
"""Utility method to list all the paths in the jar."""
|
| 287 |
+
paths = []
|
| 288 |
+
for cookie in iter(self):
|
| 289 |
+
if cookie.path not in paths:
|
| 290 |
+
paths.append(cookie.path)
|
| 291 |
+
return paths
|
| 292 |
+
|
| 293 |
+
def multiple_domains(self):
|
| 294 |
+
"""Returns True if there are multiple domains in the jar.
|
| 295 |
+
Returns False otherwise.
|
| 296 |
+
|
| 297 |
+
:rtype: bool
|
| 298 |
+
"""
|
| 299 |
+
domains = []
|
| 300 |
+
for cookie in iter(self):
|
| 301 |
+
if cookie.domain is not None and cookie.domain in domains:
|
| 302 |
+
return True
|
| 303 |
+
domains.append(cookie.domain)
|
| 304 |
+
return False # there is only one domain in jar
|
| 305 |
+
|
| 306 |
+
def get_dict(self, domain=None, path=None):
|
| 307 |
+
"""Takes as an argument an optional domain and path and returns a plain
|
| 308 |
+
old Python dict of name-value pairs of cookies that meet the
|
| 309 |
+
requirements.
|
| 310 |
+
|
| 311 |
+
:rtype: dict
|
| 312 |
+
"""
|
| 313 |
+
dictionary = {}
|
| 314 |
+
for cookie in iter(self):
|
| 315 |
+
if (domain is None or cookie.domain == domain) and (
|
| 316 |
+
path is None or cookie.path == path
|
| 317 |
+
):
|
| 318 |
+
dictionary[cookie.name] = cookie.value
|
| 319 |
+
return dictionary
|
| 320 |
+
|
| 321 |
+
def __contains__(self, name):
|
| 322 |
+
try:
|
| 323 |
+
return super().__contains__(name)
|
| 324 |
+
except CookieConflictError:
|
| 325 |
+
return True
|
| 326 |
+
|
| 327 |
+
def __getitem__(self, name):
|
| 328 |
+
"""Dict-like __getitem__() for compatibility with client code. Throws
|
| 329 |
+
exception if there are more than one cookie with name. In that case,
|
| 330 |
+
use the more explicit get() method instead.
|
| 331 |
+
|
| 332 |
+
.. warning:: operation is O(n), not O(1).
|
| 333 |
+
"""
|
| 334 |
+
return self._find_no_duplicates(name)
|
| 335 |
+
|
| 336 |
+
def __setitem__(self, name, value):
|
| 337 |
+
"""Dict-like __setitem__ for compatibility with client code. Throws
|
| 338 |
+
exception if there is already a cookie of that name in the jar. In that
|
| 339 |
+
case, use the more explicit set() method instead.
|
| 340 |
+
"""
|
| 341 |
+
self.set(name, value)
|
| 342 |
+
|
| 343 |
+
def __delitem__(self, name):
|
| 344 |
+
"""Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s
|
| 345 |
+
``remove_cookie_by_name()``.
|
| 346 |
+
"""
|
| 347 |
+
remove_cookie_by_name(self, name)
|
| 348 |
+
|
| 349 |
+
def set_cookie(self, cookie, *args, **kwargs):
|
| 350 |
+
if (
|
| 351 |
+
hasattr(cookie.value, "startswith")
|
| 352 |
+
and cookie.value.startswith('"')
|
| 353 |
+
and cookie.value.endswith('"')
|
| 354 |
+
):
|
| 355 |
+
cookie.value = cookie.value.replace('\\"', "")
|
| 356 |
+
return super().set_cookie(cookie, *args, **kwargs)
|
| 357 |
+
|
| 358 |
+
def update(self, other):
|
| 359 |
+
"""Updates this jar with cookies from another CookieJar or dict-like"""
|
| 360 |
+
if isinstance(other, cookielib.CookieJar):
|
| 361 |
+
for cookie in other:
|
| 362 |
+
self.set_cookie(copy.copy(cookie))
|
| 363 |
+
else:
|
| 364 |
+
super().update(other)
|
| 365 |
+
|
| 366 |
+
def _find(self, name, domain=None, path=None):
|
| 367 |
+
"""Requests uses this method internally to get cookie values.
|
| 368 |
+
|
| 369 |
+
If there are conflicting cookies, _find arbitrarily chooses one.
|
| 370 |
+
See _find_no_duplicates if you want an exception thrown if there are
|
| 371 |
+
conflicting cookies.
|
| 372 |
+
|
| 373 |
+
:param name: a string containing name of cookie
|
| 374 |
+
:param domain: (optional) string containing domain of cookie
|
| 375 |
+
:param path: (optional) string containing path of cookie
|
| 376 |
+
:return: cookie.value
|
| 377 |
+
"""
|
| 378 |
+
for cookie in iter(self):
|
| 379 |
+
if cookie.name == name:
|
| 380 |
+
if domain is None or cookie.domain == domain:
|
| 381 |
+
if path is None or cookie.path == path:
|
| 382 |
+
return cookie.value
|
| 383 |
+
|
| 384 |
+
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
|
| 385 |
+
|
| 386 |
+
def _find_no_duplicates(self, name, domain=None, path=None):
|
| 387 |
+
"""Both ``__get_item__`` and ``get`` call this function: it's never
|
| 388 |
+
used elsewhere in Requests.
|
| 389 |
+
|
| 390 |
+
:param name: a string containing name of cookie
|
| 391 |
+
:param domain: (optional) string containing domain of cookie
|
| 392 |
+
:param path: (optional) string containing path of cookie
|
| 393 |
+
:raises KeyError: if cookie is not found
|
| 394 |
+
:raises CookieConflictError: if there are multiple cookies
|
| 395 |
+
that match name and optionally domain and path
|
| 396 |
+
:return: cookie.value
|
| 397 |
+
"""
|
| 398 |
+
toReturn = None
|
| 399 |
+
for cookie in iter(self):
|
| 400 |
+
if cookie.name == name:
|
| 401 |
+
if domain is None or cookie.domain == domain:
|
| 402 |
+
if path is None or cookie.path == path:
|
| 403 |
+
if toReturn is not None:
|
| 404 |
+
# if there are multiple cookies that meet passed in criteria
|
| 405 |
+
raise CookieConflictError(
|
| 406 |
+
f"There are multiple cookies with name, {name!r}"
|
| 407 |
+
)
|
| 408 |
+
# we will eventually return this as long as no cookie conflict
|
| 409 |
+
toReturn = cookie.value
|
| 410 |
+
|
| 411 |
+
if toReturn:
|
| 412 |
+
return toReturn
|
| 413 |
+
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
|
| 414 |
+
|
| 415 |
+
def __getstate__(self):
|
| 416 |
+
"""Unlike a normal CookieJar, this class is pickleable."""
|
| 417 |
+
state = self.__dict__.copy()
|
| 418 |
+
# remove the unpickleable RLock object
|
| 419 |
+
state.pop("_cookies_lock")
|
| 420 |
+
return state
|
| 421 |
+
|
| 422 |
+
def __setstate__(self, state):
|
| 423 |
+
"""Unlike a normal CookieJar, this class is pickleable."""
|
| 424 |
+
self.__dict__.update(state)
|
| 425 |
+
if "_cookies_lock" not in self.__dict__:
|
| 426 |
+
self._cookies_lock = threading.RLock()
|
| 427 |
+
|
| 428 |
+
def copy(self):
|
| 429 |
+
"""Return a copy of this RequestsCookieJar."""
|
| 430 |
+
new_cj = RequestsCookieJar()
|
| 431 |
+
new_cj.set_policy(self.get_policy())
|
| 432 |
+
new_cj.update(self)
|
| 433 |
+
return new_cj
|
| 434 |
+
|
| 435 |
+
def get_policy(self):
|
| 436 |
+
"""Return the CookiePolicy instance used."""
|
| 437 |
+
return self._policy
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def _copy_cookie_jar(jar):
|
| 441 |
+
if jar is None:
|
| 442 |
+
return None
|
| 443 |
+
|
| 444 |
+
if hasattr(jar, "copy"):
|
| 445 |
+
# We're dealing with an instance of RequestsCookieJar
|
| 446 |
+
return jar.copy()
|
| 447 |
+
# We're dealing with a generic CookieJar instance
|
| 448 |
+
new_jar = copy.copy(jar)
|
| 449 |
+
new_jar.clear()
|
| 450 |
+
for cookie in jar:
|
| 451 |
+
new_jar.set_cookie(copy.copy(cookie))
|
| 452 |
+
return new_jar
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def create_cookie(name, value, **kwargs):
|
| 456 |
+
"""Make a cookie from underspecified parameters.
|
| 457 |
+
|
| 458 |
+
By default, the pair of `name` and `value` will be set for the domain ''
|
| 459 |
+
and sent on every request (this is sometimes called a "supercookie").
|
| 460 |
+
"""
|
| 461 |
+
result = {
|
| 462 |
+
"version": 0,
|
| 463 |
+
"name": name,
|
| 464 |
+
"value": value,
|
| 465 |
+
"port": None,
|
| 466 |
+
"domain": "",
|
| 467 |
+
"path": "/",
|
| 468 |
+
"secure": False,
|
| 469 |
+
"expires": None,
|
| 470 |
+
"discard": True,
|
| 471 |
+
"comment": None,
|
| 472 |
+
"comment_url": None,
|
| 473 |
+
"rest": {"HttpOnly": None},
|
| 474 |
+
"rfc2109": False,
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
badargs = set(kwargs) - set(result)
|
| 478 |
+
if badargs:
|
| 479 |
+
raise TypeError(
|
| 480 |
+
f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
|
| 481 |
+
)
|
| 482 |
+
|
| 483 |
+
result.update(kwargs)
|
| 484 |
+
result["port_specified"] = bool(result["port"])
|
| 485 |
+
result["domain_specified"] = bool(result["domain"])
|
| 486 |
+
result["domain_initial_dot"] = result["domain"].startswith(".")
|
| 487 |
+
result["path_specified"] = bool(result["path"])
|
| 488 |
+
|
| 489 |
+
return cookielib.Cookie(**result)
|
| 490 |
+
|
| 491 |
+
|
| 492 |
+
def morsel_to_cookie(morsel):
|
| 493 |
+
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
|
| 494 |
+
|
| 495 |
+
expires = None
|
| 496 |
+
if morsel["max-age"]:
|
| 497 |
+
try:
|
| 498 |
+
expires = int(time.time() + int(morsel["max-age"]))
|
| 499 |
+
except ValueError:
|
| 500 |
+
raise TypeError(f"max-age: {morsel['max-age']} must be integer")
|
| 501 |
+
elif morsel["expires"]:
|
| 502 |
+
time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
|
| 503 |
+
expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
|
| 504 |
+
return create_cookie(
|
| 505 |
+
comment=morsel["comment"],
|
| 506 |
+
comment_url=bool(morsel["comment"]),
|
| 507 |
+
discard=False,
|
| 508 |
+
domain=morsel["domain"],
|
| 509 |
+
expires=expires,
|
| 510 |
+
name=morsel.key,
|
| 511 |
+
path=morsel["path"],
|
| 512 |
+
port=None,
|
| 513 |
+
rest={"HttpOnly": morsel["httponly"]},
|
| 514 |
+
rfc2109=False,
|
| 515 |
+
secure=bool(morsel["secure"]),
|
| 516 |
+
value=morsel.value,
|
| 517 |
+
version=morsel["version"] or 0,
|
| 518 |
+
)
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
|
| 522 |
+
"""Returns a CookieJar from a key/value dictionary.
|
| 523 |
+
|
| 524 |
+
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
| 525 |
+
:param cookiejar: (optional) A cookiejar to add the cookies to.
|
| 526 |
+
:param overwrite: (optional) If False, will not replace cookies
|
| 527 |
+
already in the jar with new ones.
|
| 528 |
+
:rtype: CookieJar
|
| 529 |
+
"""
|
| 530 |
+
if cookiejar is None:
|
| 531 |
+
cookiejar = RequestsCookieJar()
|
| 532 |
+
|
| 533 |
+
if cookie_dict is not None:
|
| 534 |
+
names_from_jar = [cookie.name for cookie in cookiejar]
|
| 535 |
+
for name in cookie_dict:
|
| 536 |
+
if overwrite or (name not in names_from_jar):
|
| 537 |
+
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
|
| 538 |
+
|
| 539 |
+
return cookiejar
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
def merge_cookies(cookiejar, cookies):
|
| 543 |
+
"""Add cookies to cookiejar and returns a merged CookieJar.
|
| 544 |
+
|
| 545 |
+
:param cookiejar: CookieJar object to add the cookies to.
|
| 546 |
+
:param cookies: Dictionary or CookieJar object to be added.
|
| 547 |
+
:rtype: CookieJar
|
| 548 |
+
"""
|
| 549 |
+
if not isinstance(cookiejar, cookielib.CookieJar):
|
| 550 |
+
raise ValueError("You can only merge into CookieJar")
|
| 551 |
+
|
| 552 |
+
if isinstance(cookies, dict):
|
| 553 |
+
cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
|
| 554 |
+
elif isinstance(cookies, cookielib.CookieJar):
|
| 555 |
+
try:
|
| 556 |
+
cookiejar.update(cookies)
|
| 557 |
+
except AttributeError:
|
| 558 |
+
for cookie_in_jar in cookies:
|
| 559 |
+
cookiejar.set_cookie(cookie_in_jar)
|
| 560 |
+
|
| 561 |
+
return cookiejar
|
.venv/Lib/site-packages/requests/exceptions.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
requests.exceptions
|
| 3 |
+
~~~~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module contains the set of Requests' exceptions.
|
| 6 |
+
"""
|
| 7 |
+
from urllib3.exceptions import HTTPError as BaseHTTPError
|
| 8 |
+
|
| 9 |
+
from .compat import JSONDecodeError as CompatJSONDecodeError
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class RequestException(IOError):
|
| 13 |
+
"""There was an ambiguous exception that occurred while handling your
|
| 14 |
+
request.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
def __init__(self, *args, **kwargs):
|
| 18 |
+
"""Initialize RequestException with `request` and `response` objects."""
|
| 19 |
+
response = kwargs.pop("response", None)
|
| 20 |
+
self.response = response
|
| 21 |
+
self.request = kwargs.pop("request", None)
|
| 22 |
+
if response is not None and not self.request and hasattr(response, "request"):
|
| 23 |
+
self.request = self.response.request
|
| 24 |
+
super().__init__(*args, **kwargs)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class InvalidJSONError(RequestException):
|
| 28 |
+
"""A JSON error occurred."""
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
|
| 32 |
+
"""Couldn't decode the text into json"""
|
| 33 |
+
|
| 34 |
+
def __init__(self, *args, **kwargs):
|
| 35 |
+
"""
|
| 36 |
+
Construct the JSONDecodeError instance first with all
|
| 37 |
+
args. Then use it's args to construct the IOError so that
|
| 38 |
+
the json specific args aren't used as IOError specific args
|
| 39 |
+
and the error message from JSONDecodeError is preserved.
|
| 40 |
+
"""
|
| 41 |
+
CompatJSONDecodeError.__init__(self, *args)
|
| 42 |
+
InvalidJSONError.__init__(self, *self.args, **kwargs)
|
| 43 |
+
|
| 44 |
+
def __reduce__(self):
|
| 45 |
+
"""
|
| 46 |
+
The __reduce__ method called when pickling the object must
|
| 47 |
+
be the one from the JSONDecodeError (be it json/simplejson)
|
| 48 |
+
as it expects all the arguments for instantiation, not just
|
| 49 |
+
one like the IOError, and the MRO would by default call the
|
| 50 |
+
__reduce__ method from the IOError due to the inheritance order.
|
| 51 |
+
"""
|
| 52 |
+
return CompatJSONDecodeError.__reduce__(self)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class HTTPError(RequestException):
|
| 56 |
+
"""An HTTP error occurred."""
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class ConnectionError(RequestException):
|
| 60 |
+
"""A Connection error occurred."""
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class ProxyError(ConnectionError):
|
| 64 |
+
"""A proxy error occurred."""
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class SSLError(ConnectionError):
|
| 68 |
+
"""An SSL error occurred."""
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class Timeout(RequestException):
|
| 72 |
+
"""The request timed out.
|
| 73 |
+
|
| 74 |
+
Catching this error will catch both
|
| 75 |
+
:exc:`~requests.exceptions.ConnectTimeout` and
|
| 76 |
+
:exc:`~requests.exceptions.ReadTimeout` errors.
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class ConnectTimeout(ConnectionError, Timeout):
|
| 81 |
+
"""The request timed out while trying to connect to the remote server.
|
| 82 |
+
|
| 83 |
+
Requests that produced this error are safe to retry.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class ReadTimeout(Timeout):
|
| 88 |
+
"""The server did not send any data in the allotted amount of time."""
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class URLRequired(RequestException):
|
| 92 |
+
"""A valid URL is required to make a request."""
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class TooManyRedirects(RequestException):
|
| 96 |
+
"""Too many redirects."""
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class MissingSchema(RequestException, ValueError):
|
| 100 |
+
"""The URL scheme (e.g. http or https) is missing."""
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class InvalidSchema(RequestException, ValueError):
|
| 104 |
+
"""The URL scheme provided is either invalid or unsupported."""
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class InvalidURL(RequestException, ValueError):
|
| 108 |
+
"""The URL provided was somehow invalid."""
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class InvalidHeader(RequestException, ValueError):
|
| 112 |
+
"""The header value provided was somehow invalid."""
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class InvalidProxyURL(InvalidURL):
|
| 116 |
+
"""The proxy URL provided is invalid."""
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class ChunkedEncodingError(RequestException):
|
| 120 |
+
"""The server declared chunked encoding but sent an invalid chunk."""
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class ContentDecodingError(RequestException, BaseHTTPError):
|
| 124 |
+
"""Failed to decode response content."""
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class StreamConsumedError(RequestException, TypeError):
|
| 128 |
+
"""The content for this response was already consumed."""
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class RetryError(RequestException):
|
| 132 |
+
"""Custom retries logic failed"""
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
class UnrewindableBodyError(RequestException):
|
| 136 |
+
"""Requests encountered an error when trying to rewind a body."""
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# Warnings
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class RequestsWarning(Warning):
|
| 143 |
+
"""Base warning for Requests."""
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
class FileModeWarning(RequestsWarning, DeprecationWarning):
|
| 147 |
+
"""A file was opened in text mode, but Requests determined its binary length."""
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
class RequestsDependencyWarning(RequestsWarning):
|
| 151 |
+
"""An imported dependency doesn't match the expected version range."""
|
.venv/Lib/site-packages/requests/help.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Module containing bug report helper(s)."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import platform
|
| 5 |
+
import ssl
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
import idna
|
| 9 |
+
import urllib3
|
| 10 |
+
|
| 11 |
+
from . import __version__ as requests_version
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
import charset_normalizer
|
| 15 |
+
except ImportError:
|
| 16 |
+
charset_normalizer = None
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
import chardet
|
| 20 |
+
except ImportError:
|
| 21 |
+
chardet = None
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
from urllib3.contrib import pyopenssl
|
| 25 |
+
except ImportError:
|
| 26 |
+
pyopenssl = None
|
| 27 |
+
OpenSSL = None
|
| 28 |
+
cryptography = None
|
| 29 |
+
else:
|
| 30 |
+
import cryptography
|
| 31 |
+
import OpenSSL
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _implementation():
|
| 35 |
+
"""Return a dict with the Python implementation and version.
|
| 36 |
+
|
| 37 |
+
Provide both the name and the version of the Python implementation
|
| 38 |
+
currently running. For example, on CPython 3.10.3 it will return
|
| 39 |
+
{'name': 'CPython', 'version': '3.10.3'}.
|
| 40 |
+
|
| 41 |
+
This function works best on CPython and PyPy: in particular, it probably
|
| 42 |
+
doesn't work for Jython or IronPython. Future investigation should be done
|
| 43 |
+
to work out the correct shape of the code for those platforms.
|
| 44 |
+
"""
|
| 45 |
+
implementation = platform.python_implementation()
|
| 46 |
+
|
| 47 |
+
if implementation == "CPython":
|
| 48 |
+
implementation_version = platform.python_version()
|
| 49 |
+
elif implementation == "PyPy":
|
| 50 |
+
implementation_version = "{}.{}.{}".format(
|
| 51 |
+
sys.pypy_version_info.major,
|
| 52 |
+
sys.pypy_version_info.minor,
|
| 53 |
+
sys.pypy_version_info.micro,
|
| 54 |
+
)
|
| 55 |
+
if sys.pypy_version_info.releaselevel != "final":
|
| 56 |
+
implementation_version = "".join(
|
| 57 |
+
[implementation_version, sys.pypy_version_info.releaselevel]
|
| 58 |
+
)
|
| 59 |
+
elif implementation == "Jython":
|
| 60 |
+
implementation_version = platform.python_version() # Complete Guess
|
| 61 |
+
elif implementation == "IronPython":
|
| 62 |
+
implementation_version = platform.python_version() # Complete Guess
|
| 63 |
+
else:
|
| 64 |
+
implementation_version = "Unknown"
|
| 65 |
+
|
| 66 |
+
return {"name": implementation, "version": implementation_version}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def info():
|
| 70 |
+
"""Generate information for a bug report."""
|
| 71 |
+
try:
|
| 72 |
+
platform_info = {
|
| 73 |
+
"system": platform.system(),
|
| 74 |
+
"release": platform.release(),
|
| 75 |
+
}
|
| 76 |
+
except OSError:
|
| 77 |
+
platform_info = {
|
| 78 |
+
"system": "Unknown",
|
| 79 |
+
"release": "Unknown",
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
implementation_info = _implementation()
|
| 83 |
+
urllib3_info = {"version": urllib3.__version__}
|
| 84 |
+
charset_normalizer_info = {"version": None}
|
| 85 |
+
chardet_info = {"version": None}
|
| 86 |
+
if charset_normalizer:
|
| 87 |
+
charset_normalizer_info = {"version": charset_normalizer.__version__}
|
| 88 |
+
if chardet:
|
| 89 |
+
chardet_info = {"version": chardet.__version__}
|
| 90 |
+
|
| 91 |
+
pyopenssl_info = {
|
| 92 |
+
"version": None,
|
| 93 |
+
"openssl_version": "",
|
| 94 |
+
}
|
| 95 |
+
if OpenSSL:
|
| 96 |
+
pyopenssl_info = {
|
| 97 |
+
"version": OpenSSL.__version__,
|
| 98 |
+
"openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
|
| 99 |
+
}
|
| 100 |
+
cryptography_info = {
|
| 101 |
+
"version": getattr(cryptography, "__version__", ""),
|
| 102 |
+
}
|
| 103 |
+
idna_info = {
|
| 104 |
+
"version": getattr(idna, "__version__", ""),
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
system_ssl = ssl.OPENSSL_VERSION_NUMBER
|
| 108 |
+
system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}
|
| 109 |
+
|
| 110 |
+
return {
|
| 111 |
+
"platform": platform_info,
|
| 112 |
+
"implementation": implementation_info,
|
| 113 |
+
"system_ssl": system_ssl_info,
|
| 114 |
+
"using_pyopenssl": pyopenssl is not None,
|
| 115 |
+
"using_charset_normalizer": chardet is None,
|
| 116 |
+
"pyOpenSSL": pyopenssl_info,
|
| 117 |
+
"urllib3": urllib3_info,
|
| 118 |
+
"chardet": chardet_info,
|
| 119 |
+
"charset_normalizer": charset_normalizer_info,
|
| 120 |
+
"cryptography": cryptography_info,
|
| 121 |
+
"idna": idna_info,
|
| 122 |
+
"requests": {
|
| 123 |
+
"version": requests_version,
|
| 124 |
+
},
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def main():
|
| 129 |
+
"""Pretty-print the bug information as JSON."""
|
| 130 |
+
print(json.dumps(info(), sort_keys=True, indent=2))
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
main()
|