This is page 68 of 168. Use http://codebase.md/romanshablio/mcp_server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .DS_Store
├── .venv
│ ├── __pycache__
│ │ └── hello.cpython-312.pyc
│ ├── bin
│ │ ├── activate
│ │ ├── activate.csh
│ │ ├── activate.fish
│ │ ├── Activate.ps1
│ │ ├── flask
│ │ ├── normalizer
│ │ ├── pip
│ │ ├── pip3
│ │ ├── pip3.12
│ │ ├── python
│ │ ├── python3
│ │ └── python3.12
│ ├── hello.py
│ ├── lib
│ │ └── python3.12
│ │ └── site-packages
│ │ ├── beautifulsoup4-4.12.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ ├── AUTHORS
│ │ │ │ └── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── blinker
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _utilities.cpython-312.pyc
│ │ │ │ └── base.cpython-312.pyc
│ │ │ ├── _utilities.py
│ │ │ ├── base.py
│ │ │ └── py.typed
│ │ ├── blinker-1.8.2.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── bs4
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── css.cpython-312.pyc
│ │ │ │ ├── dammit.cpython-312.pyc
│ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ ├── element.cpython-312.pyc
│ │ │ │ └── formatter.cpython-312.pyc
│ │ │ ├── builder
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _html5lib.cpython-312.pyc
│ │ │ │ │ ├── _htmlparser.cpython-312.pyc
│ │ │ │ │ └── _lxml.cpython-312.pyc
│ │ │ │ ├── _html5lib.py
│ │ │ │ ├── _htmlparser.py
│ │ │ │ └── _lxml.py
│ │ │ ├── css.py
│ │ │ ├── dammit.py
│ │ │ ├── diagnose.py
│ │ │ ├── element.py
│ │ │ ├── formatter.py
│ │ │ └── tests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── test_builder_registry.cpython-312.pyc
│ │ │ │ ├── test_builder.cpython-312.pyc
│ │ │ │ ├── test_css.cpython-312.pyc
│ │ │ │ ├── test_dammit.cpython-312.pyc
│ │ │ │ ├── test_docs.cpython-312.pyc
│ │ │ │ ├── test_element.cpython-312.pyc
│ │ │ │ ├── test_formatter.cpython-312.pyc
│ │ │ │ ├── test_fuzz.cpython-312.pyc
│ │ │ │ ├── test_html5lib.cpython-312.pyc
│ │ │ │ ├── test_htmlparser.cpython-312.pyc
│ │ │ │ ├── test_lxml.cpython-312.pyc
│ │ │ │ ├── test_navigablestring.cpython-312.pyc
│ │ │ │ ├── test_pageelement.cpython-312.pyc
│ │ │ │ ├── test_soup.cpython-312.pyc
│ │ │ │ ├── test_tag.cpython-312.pyc
│ │ │ │ └── test_tree.cpython-312.pyc
│ │ │ ├── fuzz
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4670634698080256.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4818336571064320.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4999465949331456.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5000587759190016.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5167584867909632.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5270998950477824.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5375146639360000.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5492400320282624.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5703933063462912.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5843991618256896.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5984173902397440.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6124268085182464.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6241471367348224.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6306874195312640.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6450958476902400.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6600557255327744.testcase
│ │ │ │ ├── crash-0d306a50c8ed8bcd0785b67000fcd5dea1d33f08.testcase
│ │ │ │ └── crash-ffbdfa8a2b26f13537b68d3794b0478a4090ee4a.testcase
│ │ │ ├── test_builder_registry.py
│ │ │ ├── test_builder.py
│ │ │ ├── test_css.py
│ │ │ ├── test_dammit.py
│ │ │ ├── test_docs.py
│ │ │ ├── test_element.py
│ │ │ ├── test_formatter.py
│ │ │ ├── test_fuzz.py
│ │ │ ├── test_html5lib.py
│ │ │ ├── test_htmlparser.py
│ │ │ ├── test_lxml.py
│ │ │ ├── test_navigablestring.py
│ │ │ ├── test_pageelement.py
│ │ │ ├── test_soup.py
│ │ │ ├── test_tag.py
│ │ │ └── test_tree.py
│ │ ├── certifi
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── core.cpython-312.pyc
│ │ │ ├── cacert.pem
│ │ │ ├── core.py
│ │ │ └── py.typed
│ │ ├── certifi-2024.8.30.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── charset_normalizer
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── cd.cpython-312.pyc
│ │ │ │ ├── constant.cpython-312.pyc
│ │ │ │ ├── legacy.cpython-312.pyc
│ │ │ │ ├── md.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── version.cpython-312.pyc
│ │ │ ├── api.py
│ │ │ ├── cd.py
│ │ │ ├── cli
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __main__.py
│ │ │ │ └── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── __main__.cpython-312.pyc
│ │ │ ├── constant.py
│ │ │ ├── legacy.py
│ │ │ ├── md__mypyc.cpython-312-darwin.so
│ │ │ ├── md.cpython-312-darwin.so
│ │ │ ├── md.py
│ │ │ ├── models.py
│ │ │ ├── py.typed
│ │ │ ├── utils.py
│ │ │ └── version.py
│ │ ├── charset_normalizer-3.4.0.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── click
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ ├── _termui_impl.cpython-312.pyc
│ │ │ │ ├── _textwrap.cpython-312.pyc
│ │ │ │ ├── _winconsole.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── decorators.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formatting.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── shell_completion.cpython-312.pyc
│ │ │ │ ├── termui.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── types.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── _compat.py
│ │ │ ├── _termui_impl.py
│ │ │ ├── _textwrap.py
│ │ │ ├── _winconsole.py
│ │ │ ├── core.py
│ │ │ ├── decorators.py
│ │ │ ├── exceptions.py
│ │ │ ├── formatting.py
│ │ │ ├── globals.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── shell_completion.py
│ │ │ ├── termui.py
│ │ │ ├── testing.py
│ │ │ ├── types.py
│ │ │ └── utils.py
│ │ ├── click-8.1.7.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.rst
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── fake_useragent
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ ├── fake.cpython-312.pyc
│ │ │ │ ├── log.cpython-312.pyc
│ │ │ │ ├── settings.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── data
│ │ │ │ └── browsers.json
│ │ │ ├── errors.py
│ │ │ ├── fake.py
│ │ │ ├── log.py
│ │ │ ├── settings.py
│ │ │ └── utils.py
│ │ ├── fake_useragent-1.5.1.dist-info
│ │ │ ├── AUTHORS
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── flask
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ ├── cli.cpython-312.pyc
│ │ │ │ ├── config.cpython-312.pyc
│ │ │ │ ├── ctx.cpython-312.pyc
│ │ │ │ ├── debughelpers.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── helpers.cpython-312.pyc
│ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── signals.cpython-312.pyc
│ │ │ │ ├── templating.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── typing.cpython-312.pyc
│ │ │ │ ├── views.cpython-312.pyc
│ │ │ │ └── wrappers.cpython-312.pyc
│ │ │ ├── app.py
│ │ │ ├── blueprints.py
│ │ │ ├── cli.py
│ │ │ ├── config.py
│ │ │ ├── ctx.py
│ │ │ ├── debughelpers.py
│ │ │ ├── globals.py
│ │ │ ├── helpers.py
│ │ │ ├── json
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ └── tag.cpython-312.pyc
│ │ │ │ ├── provider.py
│ │ │ │ └── tag.py
│ │ │ ├── logging.py
│ │ │ ├── py.typed
│ │ │ ├── sansio
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ │ └── scaffold.cpython-312.pyc
│ │ │ │ ├── app.py
│ │ │ │ ├── blueprints.py
│ │ │ │ ├── README.md
│ │ │ │ └── scaffold.py
│ │ │ ├── sessions.py
│ │ │ ├── signals.py
│ │ │ ├── templating.py
│ │ │ ├── testing.py
│ │ │ ├── typing.py
│ │ │ ├── views.py
│ │ │ └── wrappers.py
│ │ ├── flask-3.0.3.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── idna
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ ├── codec.py
│ │ │ ├── compat.py
│ │ │ ├── core.py
│ │ │ ├── idnadata.py
│ │ │ ├── intranges.py
│ │ │ ├── package_data.py
│ │ │ ├── py.typed
│ │ │ └── uts46data.py
│ │ ├── idna-3.10.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── itsdangerous
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ ├── exc.cpython-312.pyc
│ │ │ │ ├── serializer.cpython-312.pyc
│ │ │ │ ├── signer.cpython-312.pyc
│ │ │ │ ├── timed.cpython-312.pyc
│ │ │ │ └── url_safe.cpython-312.pyc
│ │ │ ├── _json.py
│ │ │ ├── encoding.py
│ │ │ ├── exc.py
│ │ │ ├── py.typed
│ │ │ ├── serializer.py
│ │ │ ├── signer.py
│ │ │ ├── timed.py
│ │ │ └── url_safe.py
│ │ ├── itsdangerous-2.2.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── jinja2
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _identifier.cpython-312.pyc
│ │ │ │ ├── async_utils.cpython-312.pyc
│ │ │ │ ├── bccache.cpython-312.pyc
│ │ │ │ ├── compiler.cpython-312.pyc
│ │ │ │ ├── constants.cpython-312.pyc
│ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ ├── defaults.cpython-312.pyc
│ │ │ │ ├── environment.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ ├── filters.cpython-312.pyc
│ │ │ │ ├── idtracking.cpython-312.pyc
│ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ ├── loaders.cpython-312.pyc
│ │ │ │ ├── meta.cpython-312.pyc
│ │ │ │ ├── nativetypes.cpython-312.pyc
│ │ │ │ ├── nodes.cpython-312.pyc
│ │ │ │ ├── optimizer.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── runtime.cpython-312.pyc
│ │ │ │ ├── sandbox.cpython-312.pyc
│ │ │ │ ├── tests.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── visitor.cpython-312.pyc
│ │ │ ├── _identifier.py
│ │ │ ├── async_utils.py
│ │ │ ├── bccache.py
│ │ │ ├── compiler.py
│ │ │ ├── constants.py
│ │ │ ├── debug.py
│ │ │ ├── defaults.py
│ │ │ ├── environment.py
│ │ │ ├── exceptions.py
│ │ │ ├── ext.py
│ │ │ ├── filters.py
│ │ │ ├── idtracking.py
│ │ │ ├── lexer.py
│ │ │ ├── loaders.py
│ │ │ ├── meta.py
│ │ │ ├── nativetypes.py
│ │ │ ├── nodes.py
│ │ │ ├── optimizer.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── runtime.py
│ │ │ ├── sandbox.py
│ │ │ ├── tests.py
│ │ │ ├── utils.py
│ │ │ └── visitor.py
│ │ ├── jinja2-3.1.4.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── lxml
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _elementpath.cpython-312.pyc
│ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ ├── cssselect.cpython-312.pyc
│ │ │ │ ├── doctestcompare.cpython-312.pyc
│ │ │ │ ├── ElementInclude.cpython-312.pyc
│ │ │ │ ├── pyclasslookup.cpython-312.pyc
│ │ │ │ ├── sax.cpython-312.pyc
│ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ ├── _elementpath.cpython-312-darwin.so
│ │ │ ├── _elementpath.py
│ │ │ ├── apihelpers.pxi
│ │ │ ├── builder.cpython-312-darwin.so
│ │ │ ├── builder.py
│ │ │ ├── classlookup.pxi
│ │ │ ├── cleanup.pxi
│ │ │ ├── cssselect.py
│ │ │ ├── debug.pxi
│ │ │ ├── docloader.pxi
│ │ │ ├── doctestcompare.py
│ │ │ ├── dtd.pxi
│ │ │ ├── ElementInclude.py
│ │ │ ├── etree_api.h
│ │ │ ├── etree.cpython-312-darwin.so
│ │ │ ├── etree.h
│ │ │ ├── etree.pyx
│ │ │ ├── extensions.pxi
│ │ │ ├── html
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _diffcommand.cpython-312.pyc
│ │ │ │ │ ├── _html5builder.cpython-312.pyc
│ │ │ │ │ ├── _setmixin.cpython-312.pyc
│ │ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ │ ├── clean.cpython-312.pyc
│ │ │ │ │ ├── defs.cpython-312.pyc
│ │ │ │ │ ├── diff.cpython-312.pyc
│ │ │ │ │ ├── ElementSoup.cpython-312.pyc
│ │ │ │ │ ├── formfill.cpython-312.pyc
│ │ │ │ │ ├── html5parser.cpython-312.pyc
│ │ │ │ │ ├── soupparser.cpython-312.pyc
│ │ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ │ ├── _diffcommand.py
│ │ │ │ ├── _html5builder.py
│ │ │ │ ├── _setmixin.py
│ │ │ │ ├── builder.py
│ │ │ │ ├── clean.py
│ │ │ │ ├── defs.py
│ │ │ │ ├── diff.cpython-312-darwin.so
│ │ │ │ ├── diff.py
│ │ │ │ ├── ElementSoup.py
│ │ │ │ ├── formfill.py
│ │ │ │ ├── html5parser.py
│ │ │ │ ├── soupparser.py
│ │ │ │ └── usedoctest.py
│ │ │ ├── includes
│ │ │ │ ├── __init__.pxd
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── c14n.pxd
│ │ │ │ ├── config.pxd
│ │ │ │ ├── dtdvalid.pxd
│ │ │ │ ├── etree_defs.h
│ │ │ │ ├── etreepublic.pxd
│ │ │ │ ├── extlibs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── libcharset.h
│ │ │ │ │ ├── localcharset.h
│ │ │ │ │ ├── zconf.h
│ │ │ │ │ └── zlib.h
│ │ │ │ ├── htmlparser.pxd
│ │ │ │ ├── libexslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── exslt.h
│ │ │ │ │ ├── exsltconfig.h
│ │ │ │ │ └── exsltexports.h
│ │ │ │ ├── libxml
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── c14n.h
│ │ │ │ │ ├── catalog.h
│ │ │ │ │ ├── chvalid.h
│ │ │ │ │ ├── debugXML.h
│ │ │ │ │ ├── dict.h
│ │ │ │ │ ├── encoding.h
│ │ │ │ │ ├── entities.h
│ │ │ │ │ ├── globals.h
│ │ │ │ │ ├── hash.h
│ │ │ │ │ ├── HTMLparser.h
│ │ │ │ │ ├── HTMLtree.h
│ │ │ │ │ ├── list.h
│ │ │ │ │ ├── nanoftp.h
│ │ │ │ │ ├── nanohttp.h
│ │ │ │ │ ├── parser.h
│ │ │ │ │ ├── parserInternals.h
│ │ │ │ │ ├── relaxng.h
│ │ │ │ │ ├── SAX.h
│ │ │ │ │ ├── SAX2.h
│ │ │ │ │ ├── schemasInternals.h
│ │ │ │ │ ├── schematron.h
│ │ │ │ │ ├── threads.h
│ │ │ │ │ ├── tree.h
│ │ │ │ │ ├── uri.h
│ │ │ │ │ ├── valid.h
│ │ │ │ │ ├── xinclude.h
│ │ │ │ │ ├── xlink.h
│ │ │ │ │ ├── xmlautomata.h
│ │ │ │ │ ├── xmlerror.h
│ │ │ │ │ ├── xmlexports.h
│ │ │ │ │ ├── xmlIO.h
│ │ │ │ │ ├── xmlmemory.h
│ │ │ │ │ ├── xmlmodule.h
│ │ │ │ │ ├── xmlreader.h
│ │ │ │ │ ├── xmlregexp.h
│ │ │ │ │ ├── xmlsave.h
│ │ │ │ │ ├── xmlschemas.h
│ │ │ │ │ ├── xmlschemastypes.h
│ │ │ │ │ ├── xmlstring.h
│ │ │ │ │ ├── xmlunicode.h
│ │ │ │ │ ├── xmlversion.h
│ │ │ │ │ ├── xmlwriter.h
│ │ │ │ │ ├── xpath.h
│ │ │ │ │ ├── xpathInternals.h
│ │ │ │ │ └── xpointer.h
│ │ │ │ ├── libxslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── attributes.h
│ │ │ │ │ ├── documents.h
│ │ │ │ │ ├── extensions.h
│ │ │ │ │ ├── extra.h
│ │ │ │ │ ├── functions.h
│ │ │ │ │ ├── imports.h
│ │ │ │ │ ├── keys.h
│ │ │ │ │ ├── namespaces.h
│ │ │ │ │ ├── numbersInternals.h
│ │ │ │ │ ├── pattern.h
│ │ │ │ │ ├── preproc.h
│ │ │ │ │ ├── security.h
│ │ │ │ │ ├── templates.h
│ │ │ │ │ ├── transform.h
│ │ │ │ │ ├── variables.h
│ │ │ │ │ ├── xslt.h
│ │ │ │ │ ├── xsltconfig.h
│ │ │ │ │ ├── xsltexports.h
│ │ │ │ │ ├── xsltInternals.h
│ │ │ │ │ ├── xsltlocale.h
│ │ │ │ │ └── xsltutils.h
│ │ │ │ ├── lxml-version.h
│ │ │ │ ├── relaxng.pxd
│ │ │ │ ├── schematron.pxd
│ │ │ │ ├── tree.pxd
│ │ │ │ ├── uri.pxd
│ │ │ │ ├── xinclude.pxd
│ │ │ │ ├── xmlerror.pxd
│ │ │ │ ├── xmlparser.pxd
│ │ │ │ ├── xmlschema.pxd
│ │ │ │ ├── xpath.pxd
│ │ │ │ └── xslt.pxd
│ │ │ ├── isoschematron
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ └── resources
│ │ │ │ ├── rng
│ │ │ │ │ └── iso-schematron.rng
│ │ │ │ └── xsl
│ │ │ │ ├── iso-schematron-xslt1
│ │ │ │ │ ├── iso_abstract_expand.xsl
│ │ │ │ │ ├── iso_dsdl_include.xsl
│ │ │ │ │ ├── iso_schematron_message.xsl
│ │ │ │ │ ├── iso_schematron_skeleton_for_xslt1.xsl
│ │ │ │ │ ├── iso_svrl_for_xslt1.xsl
│ │ │ │ │ └── readme.txt
│ │ │ │ ├── RNG2Schtrn.xsl
│ │ │ │ └── XSD2Schtrn.xsl
│ │ │ ├── iterparse.pxi
│ │ │ ├── lxml.etree_api.h
│ │ │ ├── lxml.etree.h
│ │ │ ├── nsclasses.pxi
│ │ │ ├── objectify.cpython-312-darwin.so
│ │ │ ├── objectify.pyx
│ │ │ ├── objectpath.pxi
│ │ │ ├── parser.pxi
│ │ │ ├── parsertarget.pxi
│ │ │ ├── proxy.pxi
│ │ │ ├── public-api.pxi
│ │ │ ├── pyclasslookup.py
│ │ │ ├── readonlytree.pxi
│ │ │ ├── relaxng.pxi
│ │ │ ├── sax.cpython-312-darwin.so
│ │ │ ├── sax.py
│ │ │ ├── saxparser.pxi
│ │ │ ├── schematron.pxi
│ │ │ ├── serializer.pxi
│ │ │ ├── usedoctest.py
│ │ │ ├── xinclude.pxi
│ │ │ ├── xmlerror.pxi
│ │ │ ├── xmlid.pxi
│ │ │ ├── xmlschema.pxi
│ │ │ ├── xpath.pxi
│ │ │ ├── xslt.pxi
│ │ │ └── xsltext.pxi
│ │ ├── lxml-5.3.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── LICENSES.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── markupsafe
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── _native.cpython-312.pyc
│ │ │ ├── _native.py
│ │ │ ├── _speedups.c
│ │ │ ├── _speedups.cpython-312-darwin.so
│ │ │ ├── _speedups.pyi
│ │ │ └── py.typed
│ │ ├── MarkupSafe-3.0.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── pip
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pip-runner__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── __pip-runner__.cpython-312.pyc
│ │ │ ├── _internal
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── build_env.cpython-312.pyc
│ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ ├── pyproject.cpython-312.pyc
│ │ │ │ │ ├── self_outdated_check.cpython-312.pyc
│ │ │ │ │ └── wheel_builder.cpython-312.pyc
│ │ │ │ ├── build_env.py
│ │ │ │ ├── cache.py
│ │ │ │ ├── cli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── autocompletion.cpython-312.pyc
│ │ │ │ │ │ ├── base_command.cpython-312.pyc
│ │ │ │ │ │ ├── cmdoptions.cpython-312.pyc
│ │ │ │ │ │ ├── command_context.cpython-312.pyc
│ │ │ │ │ │ ├── index_command.cpython-312.pyc
│ │ │ │ │ │ ├── main_parser.cpython-312.pyc
│ │ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bars.cpython-312.pyc
│ │ │ │ │ │ ├── req_command.cpython-312.pyc
│ │ │ │ │ │ ├── spinners.cpython-312.pyc
│ │ │ │ │ │ └── status_codes.cpython-312.pyc
│ │ │ │ │ ├── autocompletion.py
│ │ │ │ │ ├── base_command.py
│ │ │ │ │ ├── cmdoptions.py
│ │ │ │ │ ├── command_context.py
│ │ │ │ │ ├── index_command.py
│ │ │ │ │ ├── main_parser.py
│ │ │ │ │ ├── main.py
│ │ │ │ │ ├── parser.py
│ │ │ │ │ ├── progress_bars.py
│ │ │ │ │ ├── req_command.py
│ │ │ │ │ ├── spinners.py
│ │ │ │ │ └── status_codes.py
│ │ │ │ ├── commands
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── completion.cpython-312.pyc
│ │ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ ├── hash.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── inspect.cpython-312.pyc
│ │ │ │ │ │ ├── install.cpython-312.pyc
│ │ │ │ │ │ ├── list.cpython-312.pyc
│ │ │ │ │ │ ├── search.cpython-312.pyc
│ │ │ │ │ │ ├── show.cpython-312.pyc
│ │ │ │ │ │ ├── uninstall.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── completion.py
│ │ │ │ │ ├── configuration.py
│ │ │ │ │ ├── debug.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── hash.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── inspect.py
│ │ │ │ │ ├── install.py
│ │ │ │ │ ├── list.py
│ │ │ │ │ ├── search.py
│ │ │ │ │ ├── show.py
│ │ │ │ │ ├── uninstall.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── configuration.py
│ │ │ │ ├── distributions
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── installed.cpython-312.pyc
│ │ │ │ │ │ ├── sdist.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── installed.py
│ │ │ │ │ ├── sdist.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── index
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── collector.cpython-312.pyc
│ │ │ │ │ │ ├── package_finder.cpython-312.pyc
│ │ │ │ │ │ └── sources.cpython-312.pyc
│ │ │ │ │ ├── collector.py
│ │ │ │ │ ├── package_finder.py
│ │ │ │ │ └── sources.py
│ │ │ │ ├── locations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _distutils.cpython-312.pyc
│ │ │ │ │ │ ├── _sysconfig.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── _distutils.py
│ │ │ │ │ ├── _sysconfig.py
│ │ │ │ │ └── base.py
│ │ │ │ ├── main.py
│ │ │ │ ├── metadata
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ └── pkg_resources.cpython-312.pyc
│ │ │ │ │ ├── _json.py
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── importlib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ │ ├── _dists.cpython-312.pyc
│ │ │ │ │ │ │ └── _envs.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.py
│ │ │ │ │ │ ├── _dists.py
│ │ │ │ │ │ └── _envs.py
│ │ │ │ │ └── pkg_resources.py
│ │ │ │ ├── models
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── candidate.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url.cpython-312.pyc
│ │ │ │ │ │ ├── format_control.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── installation_report.cpython-312.pyc
│ │ │ │ │ │ ├── link.cpython-312.pyc
│ │ │ │ │ │ ├── scheme.cpython-312.pyc
│ │ │ │ │ │ ├── search_scope.cpython-312.pyc
│ │ │ │ │ │ ├── selection_prefs.cpython-312.pyc
│ │ │ │ │ │ ├── target_python.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── candidate.py
│ │ │ │ │ ├── direct_url.py
│ │ │ │ │ ├── format_control.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── installation_report.py
│ │ │ │ │ ├── link.py
│ │ │ │ │ ├── scheme.py
│ │ │ │ │ ├── search_scope.py
│ │ │ │ │ ├── selection_prefs.py
│ │ │ │ │ ├── target_python.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── network
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── lazy_wheel.cpython-312.pyc
│ │ │ │ │ │ ├── session.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── xmlrpc.cpython-312.pyc
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── lazy_wheel.py
│ │ │ │ │ ├── session.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── xmlrpc.py
│ │ │ │ ├── operations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ └── prepare.cpython-312.pyc
│ │ │ │ │ ├── build
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── build_tracker.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_legacy.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── build_tracker.py
│ │ │ │ │ │ ├── metadata_editable.py
│ │ │ │ │ │ ├── metadata_legacy.py
│ │ │ │ │ │ ├── metadata.py
│ │ │ │ │ │ ├── wheel_editable.py
│ │ │ │ │ │ ├── wheel_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── install
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── editable_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── editable_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ └── prepare.py
│ │ │ │ ├── pyproject.py
│ │ │ │ ├── req
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── constructors.cpython-312.pyc
│ │ │ │ │ │ ├── req_file.cpython-312.pyc
│ │ │ │ │ │ ├── req_install.cpython-312.pyc
│ │ │ │ │ │ ├── req_set.cpython-312.pyc
│ │ │ │ │ │ └── req_uninstall.cpython-312.pyc
│ │ │ │ │ ├── constructors.py
│ │ │ │ │ ├── req_file.py
│ │ │ │ │ ├── req_install.py
│ │ │ │ │ ├── req_set.py
│ │ │ │ │ └── req_uninstall.py
│ │ │ │ ├── resolution
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── legacy
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ │ └── resolver.py
│ │ │ │ │ └── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── candidates.cpython-312.pyc
│ │ │ │ │ │ ├── factory.cpython-312.pyc
│ │ │ │ │ │ ├── found_candidates.cpython-312.pyc
│ │ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ │ ├── reporter.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── candidates.py
│ │ │ │ │ ├── factory.py
│ │ │ │ │ ├── found_candidates.py
│ │ │ │ │ ├── provider.py
│ │ │ │ │ ├── reporter.py
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ └── resolver.py
│ │ │ │ ├── self_outdated_check.py
│ │ │ │ ├── utils
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _jaraco_text.cpython-312.pyc
│ │ │ │ │ │ ├── _log.cpython-312.pyc
│ │ │ │ │ │ ├── appdirs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── compatibility_tags.cpython-312.pyc
│ │ │ │ │ │ ├── datetime.cpython-312.pyc
│ │ │ │ │ │ ├── deprecation.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url_helpers.cpython-312.pyc
│ │ │ │ │ │ ├── egg_link.cpython-312.pyc
│ │ │ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ │ │ ├── entrypoints.cpython-312.pyc
│ │ │ │ │ │ ├── filesystem.cpython-312.pyc
│ │ │ │ │ │ ├── filetypes.cpython-312.pyc
│ │ │ │ │ │ ├── glibc.cpython-312.pyc
│ │ │ │ │ │ ├── hashes.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── misc.cpython-312.pyc
│ │ │ │ │ │ ├── packaging.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── setuptools_build.cpython-312.pyc
│ │ │ │ │ │ ├── subprocess.cpython-312.pyc
│ │ │ │ │ │ ├── temp_dir.cpython-312.pyc
│ │ │ │ │ │ ├── unpacking.cpython-312.pyc
│ │ │ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ │ │ ├── virtualenv.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── _jaraco_text.py
│ │ │ │ │ ├── _log.py
│ │ │ │ │ ├── appdirs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── compatibility_tags.py
│ │ │ │ │ ├── datetime.py
│ │ │ │ │ ├── deprecation.py
│ │ │ │ │ ├── direct_url_helpers.py
│ │ │ │ │ ├── egg_link.py
│ │ │ │ │ ├── encoding.py
│ │ │ │ │ ├── entrypoints.py
│ │ │ │ │ ├── filesystem.py
│ │ │ │ │ ├── filetypes.py
│ │ │ │ │ ├── glibc.py
│ │ │ │ │ ├── hashes.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── misc.py
│ │ │ │ │ ├── packaging.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── setuptools_build.py
│ │ │ │ │ ├── subprocess.py
│ │ │ │ │ ├── temp_dir.py
│ │ │ │ │ ├── unpacking.py
│ │ │ │ │ ├── urls.py
│ │ │ │ │ ├── virtualenv.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── vcs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── bazaar.cpython-312.pyc
│ │ │ │ │ │ ├── git.cpython-312.pyc
│ │ │ │ │ │ ├── mercurial.cpython-312.pyc
│ │ │ │ │ │ ├── subversion.cpython-312.pyc
│ │ │ │ │ │ └── versioncontrol.cpython-312.pyc
│ │ │ │ │ ├── bazaar.py
│ │ │ │ │ ├── git.py
│ │ │ │ │ ├── mercurial.py
│ │ │ │ │ ├── subversion.py
│ │ │ │ │ └── versioncontrol.py
│ │ │ │ └── wheel_builder.py
│ │ │ ├── _vendor
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ └── typing_extensions.cpython-312.pyc
│ │ │ │ ├── cachecontrol
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _cmd.cpython-312.pyc
│ │ │ │ │ │ ├── adapter.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── controller.cpython-312.pyc
│ │ │ │ │ │ ├── filewrapper.cpython-312.pyc
│ │ │ │ │ │ ├── heuristics.cpython-312.pyc
│ │ │ │ │ │ ├── serialize.cpython-312.pyc
│ │ │ │ │ │ └── wrapper.cpython-312.pyc
│ │ │ │ │ ├── _cmd.py
│ │ │ │ │ ├── adapter.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── caches
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── file_cache.cpython-312.pyc
│ │ │ │ │ │ │ └── redis_cache.cpython-312.pyc
│ │ │ │ │ │ ├── file_cache.py
│ │ │ │ │ │ └── redis_cache.py
│ │ │ │ │ ├── controller.py
│ │ │ │ │ ├── filewrapper.py
│ │ │ │ │ ├── heuristics.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── serialize.py
│ │ │ │ │ └── wrapper.py
│ │ │ │ ├── certifi
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── core.cpython-312.pyc
│ │ │ │ │ ├── cacert.pem
│ │ │ │ │ ├── core.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── distlib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── database.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── locators.cpython-312.pyc
│ │ │ │ │ │ ├── manifest.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── resources.cpython-312.pyc
│ │ │ │ │ │ ├── scripts.cpython-312.pyc
│ │ │ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── database.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── locators.py
│ │ │ │ │ ├── manifest.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── resources.py
│ │ │ │ │ ├── scripts.py
│ │ │ │ │ ├── t32.exe
│ │ │ │ │ ├── t64-arm.exe
│ │ │ │ │ ├── t64.exe
│ │ │ │ │ ├── util.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ ├── w32.exe
│ │ │ │ │ ├── w64-arm.exe
│ │ │ │ │ ├── w64.exe
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── distro
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── distro.cpython-312.pyc
│ │ │ │ │ ├── distro.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── idna
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ │ │ ├── codec.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── core.py
│ │ │ │ │ ├── idnadata.py
│ │ │ │ │ ├── intranges.py
│ │ │ │ │ ├── package_data.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ └── uts46data.py
│ │ │ │ ├── msgpack
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ │ │ └── fallback.cpython-312.pyc
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── ext.py
│ │ │ │ │ └── fallback.py
│ │ │ │ ├── packaging
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _elffile.cpython-312.pyc
│ │ │ │ │ │ ├── _manylinux.cpython-312.pyc
│ │ │ │ │ │ ├── _musllinux.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _structures.cpython-312.pyc
│ │ │ │ │ │ ├── _tokenizer.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ ├── specifiers.cpython-312.pyc
│ │ │ │ │ │ ├── tags.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── version.cpython-312.pyc
│ │ │ │ │ ├── _elffile.py
│ │ │ │ │ ├── _manylinux.py
│ │ │ │ │ ├── _musllinux.py
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _structures.py
│ │ │ │ │ ├── _tokenizer.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ ├── specifiers.py
│ │ │ │ │ ├── tags.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── version.py
│ │ │ │ ├── pkg_resources
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── platformdirs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── android.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── macos.cpython-312.pyc
│ │ │ │ │ │ ├── unix.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── windows.cpython-312.pyc
│ │ │ │ │ ├── android.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── macos.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── unix.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ └── windows.py
│ │ │ │ ├── pygments
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── cmdline.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── filter.cpython-312.pyc
│ │ │ │ │ │ ├── formatter.cpython-312.pyc
│ │ │ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ │ │ ├── modeline.cpython-312.pyc
│ │ │ │ │ │ ├── plugin.cpython-312.pyc
│ │ │ │ │ │ ├── regexopt.cpython-312.pyc
│ │ │ │ │ │ ├── scanner.cpython-312.pyc
│ │ │ │ │ │ ├── sphinxext.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── token.cpython-312.pyc
│ │ │ │ │ │ ├── unistring.cpython-312.pyc
│ │ │ │ │ │ └── util.cpython-312.pyc
│ │ │ │ │ ├── cmdline.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── filter.py
│ │ │ │ │ ├── filters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ └── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── formatter.py
│ │ │ │ │ ├── formatters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ ├── bbcode.cpython-312.pyc
│ │ │ │ │ │ │ ├── groff.cpython-312.pyc
│ │ │ │ │ │ │ ├── html.cpython-312.pyc
│ │ │ │ │ │ │ ├── img.cpython-312.pyc
│ │ │ │ │ │ │ ├── irc.cpython-312.pyc
│ │ │ │ │ │ │ ├── latex.cpython-312.pyc
│ │ │ │ │ │ │ ├── other.cpython-312.pyc
│ │ │ │ │ │ │ ├── pangomarkup.cpython-312.pyc
│ │ │ │ │ │ │ ├── rtf.cpython-312.pyc
│ │ │ │ │ │ │ ├── svg.cpython-312.pyc
│ │ │ │ │ │ │ ├── terminal.cpython-312.pyc
│ │ │ │ │ │ │ └── terminal256.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ ├── bbcode.py
│ │ │ │ │ │ ├── groff.py
│ │ │ │ │ │ ├── html.py
│ │ │ │ │ │ ├── img.py
│ │ │ │ │ │ ├── irc.py
│ │ │ │ │ │ ├── latex.py
│ │ │ │ │ │ ├── other.py
│ │ │ │ │ │ ├── pangomarkup.py
│ │ │ │ │ │ ├── rtf.py
│ │ │ │ │ │ ├── svg.py
│ │ │ │ │ │ ├── terminal.py
│ │ │ │ │ │ └── terminal256.py
│ │ │ │ │ ├── lexer.py
│ │ │ │ │ ├── lexers
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ └── python.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ └── python.py
│ │ │ │ │ ├── modeline.py
│ │ │ │ │ ├── plugin.py
│ │ │ │ │ ├── regexopt.py
│ │ │ │ │ ├── scanner.py
│ │ │ │ │ ├── sphinxext.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styles
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── _mapping.cpython-312.pyc
│ │ │ │ │ │ └── _mapping.py
│ │ │ │ │ ├── token.py
│ │ │ │ │ ├── unistring.py
│ │ │ │ │ └── util.py
│ │ │ │ ├── pyproject_hooks
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ └── _impl.cpython-312.pyc
│ │ │ │ │ ├── _compat.py
│ │ │ │ │ ├── _impl.py
│ │ │ │ │ └── _in_process
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── _in_process.cpython-312.pyc
│ │ │ │ │ └── _in_process.py
│ │ │ │ ├── requests
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ │ ├── __version__.py
│ │ │ │ │ ├── _internal_utils.py
│ │ │ │ │ ├── adapters.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── certs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── cookies.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── hooks.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── packages.py
│ │ │ │ │ ├── sessions.py
│ │ │ │ │ ├── status_codes.py
│ │ │ │ │ ├── structures.py
│ │ │ │ │ └── utils.py
│ │ │ │ ├── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── providers.cpython-312.pyc
│ │ │ │ │ │ ├── reporters.cpython-312.pyc
│ │ │ │ │ │ ├── resolvers.cpython-312.pyc
│ │ │ │ │ │ └── structs.cpython-312.pyc
│ │ │ │ │ ├── compat
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── collections_abc.cpython-312.pyc
│ │ │ │ │ │ └── collections_abc.py
│ │ │ │ │ ├── providers.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── reporters.py
│ │ │ │ │ ├── resolvers.py
│ │ │ │ │ └── structs.py
│ │ │ │ ├── rich
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── _cell_widths.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_codes.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_replace.cpython-312.pyc
│ │ │ │ │ │ ├── _export_format.cpython-312.pyc
│ │ │ │ │ │ ├── _extension.cpython-312.pyc
│ │ │ │ │ │ ├── _fileno.cpython-312.pyc
│ │ │ │ │ │ ├── _inspect.cpython-312.pyc
│ │ │ │ │ │ ├── _log_render.cpython-312.pyc
│ │ │ │ │ │ ├── _loop.cpython-312.pyc
│ │ │ │ │ │ ├── _null_file.cpython-312.pyc
│ │ │ │ │ │ ├── _palettes.cpython-312.pyc
│ │ │ │ │ │ ├── _pick.cpython-312.pyc
│ │ │ │ │ │ ├── _ratio.cpython-312.pyc
│ │ │ │ │ │ ├── _spinners.cpython-312.pyc
│ │ │ │ │ │ ├── _stack.cpython-312.pyc
│ │ │ │ │ │ ├── _timer.cpython-312.pyc
│ │ │ │ │ │ ├── _win32_console.cpython-312.pyc
│ │ │ │ │ │ ├── _windows_renderer.cpython-312.pyc
│ │ │ │ │ │ ├── _windows.cpython-312.pyc
│ │ │ │ │ │ ├── _wrap.cpython-312.pyc
│ │ │ │ │ │ ├── abc.cpython-312.pyc
│ │ │ │ │ │ ├── align.cpython-312.pyc
│ │ │ │ │ │ ├── ansi.cpython-312.pyc
│ │ │ │ │ │ ├── bar.cpython-312.pyc
│ │ │ │ │ │ ├── box.cpython-312.pyc
│ │ │ │ │ │ ├── cells.cpython-312.pyc
│ │ │ │ │ │ ├── color_triplet.cpython-312.pyc
│ │ │ │ │ │ ├── color.cpython-312.pyc
│ │ │ │ │ │ ├── columns.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── constrain.cpython-312.pyc
│ │ │ │ │ │ ├── containers.cpython-312.pyc
│ │ │ │ │ │ ├── control.cpython-312.pyc
│ │ │ │ │ │ ├── default_styles.cpython-312.pyc
│ │ │ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ │ │ ├── emoji.cpython-312.pyc
│ │ │ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ │ │ ├── file_proxy.cpython-312.pyc
│ │ │ │ │ │ ├── filesize.cpython-312.pyc
│ │ │ │ │ │ ├── highlighter.cpython-312.pyc
│ │ │ │ │ │ ├── json.cpython-312.pyc
│ │ │ │ │ │ ├── jupyter.cpython-312.pyc
│ │ │ │ │ │ ├── layout.cpython-312.pyc
│ │ │ │ │ │ ├── live_render.cpython-312.pyc
│ │ │ │ │ │ ├── live.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── markup.cpython-312.pyc
│ │ │ │ │ │ ├── measure.cpython-312.pyc
│ │ │ │ │ │ ├── padding.cpython-312.pyc
│ │ │ │ │ │ ├── pager.cpython-312.pyc
│ │ │ │ │ │ ├── palette.cpython-312.pyc
│ │ │ │ │ │ ├── panel.cpython-312.pyc
│ │ │ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bar.cpython-312.pyc
│ │ │ │ │ │ ├── progress.cpython-312.pyc
│ │ │ │ │ │ ├── prompt.cpython-312.pyc
│ │ │ │ │ │ ├── protocol.cpython-312.pyc
│ │ │ │ │ │ ├── region.cpython-312.pyc
│ │ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ │ ├── rule.cpython-312.pyc
│ │ │ │ │ │ ├── scope.cpython-312.pyc
│ │ │ │ │ │ ├── screen.cpython-312.pyc
│ │ │ │ │ │ ├── segment.cpython-312.pyc
│ │ │ │ │ │ ├── spinner.cpython-312.pyc
│ │ │ │ │ │ ├── status.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── styled.cpython-312.pyc
│ │ │ │ │ │ ├── syntax.cpython-312.pyc
│ │ │ │ │ │ ├── table.cpython-312.pyc
│ │ │ │ │ │ ├── terminal_theme.cpython-312.pyc
│ │ │ │ │ │ ├── text.cpython-312.pyc
│ │ │ │ │ │ ├── theme.cpython-312.pyc
│ │ │ │ │ │ ├── themes.cpython-312.pyc
│ │ │ │ │ │ ├── traceback.cpython-312.pyc
│ │ │ │ │ │ └── tree.cpython-312.pyc
│ │ │ │ │ ├── _cell_widths.py
│ │ │ │ │ ├── _emoji_codes.py
│ │ │ │ │ ├── _emoji_replace.py
│ │ │ │ │ ├── _export_format.py
│ │ │ │ │ ├── _extension.py
│ │ │ │ │ ├── _fileno.py
│ │ │ │ │ ├── _inspect.py
│ │ │ │ │ ├── _log_render.py
│ │ │ │ │ ├── _loop.py
│ │ │ │ │ ├── _null_file.py
│ │ │ │ │ ├── _palettes.py
│ │ │ │ │ ├── _pick.py
│ │ │ │ │ ├── _ratio.py
│ │ │ │ │ ├── _spinners.py
│ │ │ │ │ ├── _stack.py
│ │ │ │ │ ├── _timer.py
│ │ │ │ │ ├── _win32_console.py
│ │ │ │ │ ├── _windows_renderer.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ ├── _wrap.py
│ │ │ │ │ ├── abc.py
│ │ │ │ │ ├── align.py
│ │ │ │ │ ├── ansi.py
│ │ │ │ │ ├── bar.py
│ │ │ │ │ ├── box.py
│ │ │ │ │ ├── cells.py
│ │ │ │ │ ├── color_triplet.py
│ │ │ │ │ ├── color.py
│ │ │ │ │ ├── columns.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── constrain.py
│ │ │ │ │ ├── containers.py
│ │ │ │ │ ├── control.py
│ │ │ │ │ ├── default_styles.py
│ │ │ │ │ ├── diagnose.py
│ │ │ │ │ ├── emoji.py
│ │ │ │ │ ├── errors.py
│ │ │ │ │ ├── file_proxy.py
│ │ │ │ │ ├── filesize.py
│ │ │ │ │ ├── highlighter.py
│ │ │ │ │ ├── json.py
│ │ │ │ │ ├── jupyter.py
│ │ │ │ │ ├── layout.py
│ │ │ │ │ ├── live_render.py
│ │ │ │ │ ├── live.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── markup.py
│ │ │ │ │ ├── measure.py
│ │ │ │ │ ├── padding.py
│ │ │ │ │ ├── pager.py
│ │ │ │ │ ├── palette.py
│ │ │ │ │ ├── panel.py
│ │ │ │ │ ├── pretty.py
│ │ │ │ │ ├── progress_bar.py
│ │ │ │ │ ├── progress.py
│ │ │ │ │ ├── prompt.py
│ │ │ │ │ ├── protocol.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── region.py
│ │ │ │ │ ├── repr.py
│ │ │ │ │ ├── rule.py
│ │ │ │ │ ├── scope.py
│ │ │ │ │ ├── screen.py
│ │ │ │ │ ├── segment.py
│ │ │ │ │ ├── spinner.py
│ │ │ │ │ ├── status.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styled.py
│ │ │ │ │ ├── syntax.py
│ │ │ │ │ ├── table.py
│ │ │ │ │ ├── terminal_theme.py
│ │ │ │ │ ├── text.py
│ │ │ │ │ ├── theme.py
│ │ │ │ │ ├── themes.py
│ │ │ │ │ ├── traceback.py
│ │ │ │ │ └── tree.py
│ │ │ │ ├── tomli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _re.cpython-312.pyc
│ │ │ │ │ │ └── _types.cpython-312.pyc
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _re.py
│ │ │ │ │ ├── _types.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── truststore
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _api.cpython-312.pyc
│ │ │ │ │ │ ├── _macos.cpython-312.pyc
│ │ │ │ │ │ ├── _openssl.cpython-312.pyc
│ │ │ │ │ │ ├── _ssl_constants.cpython-312.pyc
│ │ │ │ │ │ └── _windows.cpython-312.pyc
│ │ │ │ │ ├── _api.py
│ │ │ │ │ ├── _macos.py
│ │ │ │ │ ├── _openssl.py
│ │ │ │ │ ├── _ssl_constants.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── typing_extensions.py
│ │ │ │ ├── urllib3
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── _collections.py
│ │ │ │ │ ├── _version.py
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── connectionpool.py
│ │ │ │ │ ├── contrib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _appengine_environ.cpython-312.pyc
│ │ │ │ │ │ │ ├── appengine.cpython-312.pyc
│ │ │ │ │ │ │ ├── ntlmpool.cpython-312.pyc
│ │ │ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ │ │ ├── securetransport.cpython-312.pyc
│ │ │ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ │ │ ├── _appengine_environ.py
│ │ │ │ │ │ ├── _securetransport
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── bindings.cpython-312.pyc
│ │ │ │ │ │ │ │ └── low_level.cpython-312.pyc
│ │ │ │ │ │ │ ├── bindings.py
│ │ │ │ │ │ │ └── low_level.py
│ │ │ │ │ │ ├── appengine.py
│ │ │ │ │ │ ├── ntlmpool.py
│ │ │ │ │ │ ├── pyopenssl.py
│ │ │ │ │ │ ├── securetransport.py
│ │ │ │ │ │ └── socks.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── fields.py
│ │ │ │ │ ├── filepost.py
│ │ │ │ │ ├── packages
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── six.cpython-312.pyc
│ │ │ │ │ │ ├── backports
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── makefile.cpython-312.pyc
│ │ │ │ │ │ │ │ └── weakref_finalize.cpython-312.pyc
│ │ │ │ │ │ │ ├── makefile.py
│ │ │ │ │ │ │ └── weakref_finalize.py
│ │ │ │ │ │ └── six.py
│ │ │ │ │ ├── poolmanager.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ └── util
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ │ │ ├── queue.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ │ │ └── wait.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── proxy.py
│ │ │ │ │ ├── queue.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── ssl_.py
│ │ │ │ │ ├── ssl_match_hostname.py
│ │ │ │ │ ├── ssltransport.py
│ │ │ │ │ ├── timeout.py
│ │ │ │ │ ├── url.py
│ │ │ │ │ └── wait.py
│ │ │ │ └── vendor.txt
│ │ │ └── py.typed
│ │ ├── pip-24.2.dist-info
│ │ │ ├── AUTHORS.txt
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── requests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── __version__.py
│ │ │ ├── _internal_utils.py
│ │ │ ├── adapters.py
│ │ │ ├── api.py
│ │ │ ├── auth.py
│ │ │ ├── certs.py
│ │ │ ├── compat.py
│ │ │ ├── cookies.py
│ │ │ ├── exceptions.py
│ │ │ ├── help.py
│ │ │ ├── hooks.py
│ │ │ ├── models.py
│ │ │ ├── packages.py
│ │ │ ├── sessions.py
│ │ │ ├── status_codes.py
│ │ │ ├── structures.py
│ │ │ └── utils.py
│ │ ├── requests-2.32.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── soupsieve
│ │ │ ├── __init__.py
│ │ │ ├── __meta__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __meta__.cpython-312.pyc
│ │ │ │ ├── css_match.cpython-312.pyc
│ │ │ │ ├── css_parser.cpython-312.pyc
│ │ │ │ ├── css_types.cpython-312.pyc
│ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ └── util.cpython-312.pyc
│ │ │ ├── css_match.py
│ │ │ ├── css_parser.py
│ │ │ ├── css_types.py
│ │ │ ├── pretty.py
│ │ │ ├── py.typed
│ │ │ └── util.py
│ │ ├── soupsieve-2.6.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── urllib3
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _base_connection.cpython-312.pyc
│ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ ├── _request_methods.cpython-312.pyc
│ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ └── response.cpython-312.pyc
│ │ │ ├── _base_connection.py
│ │ │ ├── _collections.py
│ │ │ ├── _request_methods.py
│ │ │ ├── _version.py
│ │ │ ├── connection.py
│ │ │ ├── connectionpool.py
│ │ │ ├── contrib
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ ├── emscripten
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── fetch.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── emscripten_fetch_worker.js
│ │ │ │ │ ├── fetch.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ └── response.py
│ │ │ │ ├── pyopenssl.py
│ │ │ │ └── socks.py
│ │ │ ├── exceptions.py
│ │ │ ├── fields.py
│ │ │ ├── filepost.py
│ │ │ ├── http2
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ └── probe.cpython-312.pyc
│ │ │ │ ├── connection.py
│ │ │ │ └── probe.py
│ │ │ ├── poolmanager.py
│ │ │ ├── py.typed
│ │ │ ├── response.py
│ │ │ └── util
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ └── wait.cpython-312.pyc
│ │ │ ├── connection.py
│ │ │ ├── proxy.py
│ │ │ ├── request.py
│ │ │ ├── response.py
│ │ │ ├── retry.py
│ │ │ ├── ssl_.py
│ │ │ ├── ssl_match_hostname.py
│ │ │ ├── ssltransport.py
│ │ │ ├── timeout.py
│ │ │ ├── url.py
│ │ │ ├── util.py
│ │ │ └── wait.py
│ │ ├── urllib3-2.2.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── useragent
│ │ │ ├── __init__.py
│ │ │ ├── __init__.pyc
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── resources
│ │ │ │ └── user_agent_data.json
│ │ │ └── test
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── test_additional_os.json
│ │ │ ├── test_browser.json
│ │ │ ├── test_device.json
│ │ │ ├── test_firefox.json
│ │ │ ├── test_os.json
│ │ │ └── test_pgts_browser.json
│ │ ├── useragent-0.1.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── werkzeug
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _internal.cpython-312.pyc
│ │ │ │ ├── _reloader.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formparser.cpython-312.pyc
│ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ ├── local.cpython-312.pyc
│ │ │ │ ├── security.cpython-312.pyc
│ │ │ │ ├── serving.cpython-312.pyc
│ │ │ │ ├── test.cpython-312.pyc
│ │ │ │ ├── testapp.cpython-312.pyc
│ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ ├── user_agent.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── wsgi.cpython-312.pyc
│ │ │ ├── _internal.py
│ │ │ ├── _reloader.py
│ │ │ ├── datastructures
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── accept.cpython-312.pyc
│ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ ├── cache_control.cpython-312.pyc
│ │ │ │ │ ├── csp.cpython-312.pyc
│ │ │ │ │ ├── etag.cpython-312.pyc
│ │ │ │ │ ├── file_storage.cpython-312.pyc
│ │ │ │ │ ├── headers.cpython-312.pyc
│ │ │ │ │ ├── mixins.cpython-312.pyc
│ │ │ │ │ ├── range.cpython-312.pyc
│ │ │ │ │ └── structures.cpython-312.pyc
│ │ │ │ ├── accept.py
│ │ │ │ ├── accept.pyi
│ │ │ │ ├── auth.py
│ │ │ │ ├── cache_control.py
│ │ │ │ ├── cache_control.pyi
│ │ │ │ ├── csp.py
│ │ │ │ ├── csp.pyi
│ │ │ │ ├── etag.py
│ │ │ │ ├── etag.pyi
│ │ │ │ ├── file_storage.py
│ │ │ │ ├── file_storage.pyi
│ │ │ │ ├── headers.py
│ │ │ │ ├── headers.pyi
│ │ │ │ ├── mixins.py
│ │ │ │ ├── mixins.pyi
│ │ │ │ ├── range.py
│ │ │ │ ├── range.pyi
│ │ │ │ ├── structures.py
│ │ │ │ └── structures.pyi
│ │ │ ├── debug
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ └── tbtools.cpython-312.pyc
│ │ │ │ ├── console.py
│ │ │ │ ├── repr.py
│ │ │ │ ├── shared
│ │ │ │ │ ├── console.png
│ │ │ │ │ ├── debugger.js
│ │ │ │ │ ├── ICON_LICENSE.md
│ │ │ │ │ ├── less.png
│ │ │ │ │ ├── more.png
│ │ │ │ │ └── style.css
│ │ │ │ └── tbtools.py
│ │ │ ├── exceptions.py
│ │ │ ├── formparser.py
│ │ │ ├── http.py
│ │ │ ├── local.py
│ │ │ ├── middleware
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── dispatcher.cpython-312.pyc
│ │ │ │ │ ├── http_proxy.cpython-312.pyc
│ │ │ │ │ ├── lint.cpython-312.pyc
│ │ │ │ │ ├── profiler.cpython-312.pyc
│ │ │ │ │ ├── proxy_fix.cpython-312.pyc
│ │ │ │ │ └── shared_data.cpython-312.pyc
│ │ │ │ ├── dispatcher.py
│ │ │ │ ├── http_proxy.py
│ │ │ │ ├── lint.py
│ │ │ │ ├── profiler.py
│ │ │ │ ├── proxy_fix.py
│ │ │ │ └── shared_data.py
│ │ │ ├── py.typed
│ │ │ ├── routing
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── converters.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── map.cpython-312.pyc
│ │ │ │ │ ├── matcher.cpython-312.pyc
│ │ │ │ │ └── rules.cpython-312.pyc
│ │ │ │ ├── converters.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── map.py
│ │ │ │ ├── matcher.py
│ │ │ │ └── rules.py
│ │ │ ├── sansio
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ │ ├── multipart.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ ├── http.py
│ │ │ │ ├── multipart.py
│ │ │ │ ├── request.py
│ │ │ │ ├── response.py
│ │ │ │ └── utils.py
│ │ │ ├── security.py
│ │ │ ├── serving.py
│ │ │ ├── test.py
│ │ │ ├── testapp.py
│ │ │ ├── urls.py
│ │ │ ├── user_agent.py
│ │ │ ├── utils.py
│ │ │ ├── wrappers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ ├── request.py
│ │ │ │ └── response.py
│ │ │ └── wsgi.py
│ │ └── werkzeug-3.0.4.dist-info
│ │ ├── INSTALLER
│ │ ├── LICENSE.txt
│ │ ├── METADATA
│ │ ├── RECORD
│ │ └── WHEEL
│ ├── pyvenv.cfg
│ ├── static
│ │ └── styles.css
│ ├── templates
│ │ └── index.html
│ └── test.py
├── cline_config.json
├── mcp_server.py
├── README.md
├── search_results.json
├── settings.json
└── test_files
├── text1.txt
└── text2.txt
```
# Files
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/werkzeug/debug/__init__.py:
--------------------------------------------------------------------------------
```python
1 | from __future__ import annotations
2 |
3 | import getpass
4 | import hashlib
5 | import json
6 | import os
7 | import pkgutil
8 | import re
9 | import sys
10 | import time
11 | import typing as t
12 | import uuid
13 | from contextlib import ExitStack
14 | from io import BytesIO
15 | from itertools import chain
16 | from multiprocessing import Value
17 | from os.path import basename
18 | from os.path import join
19 | from zlib import adler32
20 |
21 | from .._internal import _log
22 | from ..exceptions import NotFound
23 | from ..exceptions import SecurityError
24 | from ..http import parse_cookie
25 | from ..sansio.utils import host_is_trusted
26 | from ..security import gen_salt
27 | from ..utils import send_file
28 | from ..wrappers.request import Request
29 | from ..wrappers.response import Response
30 | from .console import Console
31 | from .tbtools import DebugFrameSummary
32 | from .tbtools import DebugTraceback
33 | from .tbtools import render_console_html
34 |
35 | if t.TYPE_CHECKING:
36 | from _typeshed.wsgi import StartResponse
37 | from _typeshed.wsgi import WSGIApplication
38 | from _typeshed.wsgi import WSGIEnvironment
39 |
40 | # A week
41 | PIN_TIME = 60 * 60 * 24 * 7
42 |
43 |
44 | def hash_pin(pin: str) -> str:
45 | return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]
46 |
47 |
48 | _machine_id: str | bytes | None = None
49 |
50 |
51 | def get_machine_id() -> str | bytes | None:
52 | global _machine_id
53 |
54 | if _machine_id is not None:
55 | return _machine_id
56 |
57 | def _generate() -> str | bytes | None:
58 | linux = b""
59 |
60 | # machine-id is stable across boots, boot_id is not.
61 | for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
62 | try:
63 | with open(filename, "rb") as f:
64 | value = f.readline().strip()
65 | except OSError:
66 | continue
67 |
68 | if value:
69 | linux += value
70 | break
71 |
72 | # Containers share the same machine id, add some cgroup
73 | # information. This is used outside containers too but should be
74 | # relatively stable across boots.
75 | try:
76 | with open("/proc/self/cgroup", "rb") as f:
77 | linux += f.readline().strip().rpartition(b"/")[2]
78 | except OSError:
79 | pass
80 |
81 | if linux:
82 | return linux
83 |
84 | # On OS X, use ioreg to get the computer's serial number.
85 | try:
86 | # subprocess may not be available, e.g. Google App Engine
87 | # https://github.com/pallets/werkzeug/issues/925
88 | from subprocess import PIPE
89 | from subprocess import Popen
90 |
91 | dump = Popen(
92 | ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE
93 | ).communicate()[0]
94 | match = re.search(b'"serial-number" = <([^>]+)', dump)
95 |
96 | if match is not None:
97 | return match.group(1)
98 | except (OSError, ImportError):
99 | pass
100 |
101 | # On Windows, use winreg to get the machine guid.
102 | if sys.platform == "win32":
103 | import winreg
104 |
105 | try:
106 | with winreg.OpenKey(
107 | winreg.HKEY_LOCAL_MACHINE,
108 | "SOFTWARE\\Microsoft\\Cryptography",
109 | 0,
110 | winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
111 | ) as rk:
112 | guid: str | bytes
113 | guid_type: int
114 | guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid")
115 |
116 | if guid_type == winreg.REG_SZ:
117 | return guid.encode()
118 |
119 | return guid
120 | except OSError:
121 | pass
122 |
123 | return None
124 |
125 | _machine_id = _generate()
126 | return _machine_id
127 |
128 |
129 | class _ConsoleFrame:
130 | """Helper class so that we can reuse the frame console code for the
131 | standalone console.
132 | """
133 |
134 | def __init__(self, namespace: dict[str, t.Any]):
135 | self.console = Console(namespace)
136 | self.id = 0
137 |
138 | def eval(self, code: str) -> t.Any:
139 | return self.console.eval(code)
140 |
141 |
142 | def get_pin_and_cookie_name(
143 | app: WSGIApplication,
144 | ) -> tuple[str, str] | tuple[None, None]:
145 | """Given an application object this returns a semi-stable 9 digit pin
146 | code and a random key. The hope is that this is stable between
147 | restarts to not make debugging particularly frustrating. If the pin
148 | was forcefully disabled this returns `None`.
149 |
150 | Second item in the resulting tuple is the cookie name for remembering.
151 | """
152 | pin = os.environ.get("WERKZEUG_DEBUG_PIN")
153 | rv = None
154 | num = None
155 |
156 | # Pin was explicitly disabled
157 | if pin == "off":
158 | return None, None
159 |
160 | # Pin was provided explicitly
161 | if pin is not None and pin.replace("-", "").isdecimal():
162 | # If there are separators in the pin, return it directly
163 | if "-" in pin:
164 | rv = pin
165 | else:
166 | num = pin
167 |
168 | modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
169 | username: str | None
170 |
171 | try:
172 | # getuser imports the pwd module, which does not exist in Google
173 | # App Engine. It may also raise a KeyError if the UID does not
174 | # have a username, such as in Docker.
175 | username = getpass.getuser()
176 | except (ImportError, KeyError):
177 | username = None
178 |
179 | mod = sys.modules.get(modname)
180 |
181 | # This information only exists to make the cookie unique on the
182 | # computer, not as a security feature.
183 | probably_public_bits = [
184 | username,
185 | modname,
186 | getattr(app, "__name__", type(app).__name__),
187 | getattr(mod, "__file__", None),
188 | ]
189 |
190 | # This information is here to make it harder for an attacker to
191 | # guess the cookie name. They are unlikely to be contained anywhere
192 | # within the unauthenticated debug page.
193 | private_bits = [str(uuid.getnode()), get_machine_id()]
194 |
195 | h = hashlib.sha1()
196 | for bit in chain(probably_public_bits, private_bits):
197 | if not bit:
198 | continue
199 | if isinstance(bit, str):
200 | bit = bit.encode()
201 | h.update(bit)
202 | h.update(b"cookiesalt")
203 |
204 | cookie_name = f"__wzd{h.hexdigest()[:20]}"
205 |
206 | # If we need to generate a pin we salt it a bit more so that we don't
207 | # end up with the same value and generate out 9 digits
208 | if num is None:
209 | h.update(b"pinsalt")
210 | num = f"{int(h.hexdigest(), 16):09d}"[:9]
211 |
212 | # Format the pincode in groups of digits for easier remembering if
213 | # we don't have a result yet.
214 | if rv is None:
215 | for group_size in 5, 4, 3:
216 | if len(num) % group_size == 0:
217 | rv = "-".join(
218 | num[x : x + group_size].rjust(group_size, "0")
219 | for x in range(0, len(num), group_size)
220 | )
221 | break
222 | else:
223 | rv = num
224 |
225 | return rv, cookie_name
226 |
227 |
228 | class DebuggedApplication:
229 | """Enables debugging support for a given application::
230 |
231 | from werkzeug.debug import DebuggedApplication
232 | from myapp import app
233 | app = DebuggedApplication(app, evalex=True)
234 |
235 | The ``evalex`` argument allows evaluating expressions in any frame
236 | of a traceback. This works by preserving each frame with its local
237 | state. Some state, such as context globals, cannot be restored with
238 | the frame by default. When ``evalex`` is enabled,
239 | ``environ["werkzeug.debug.preserve_context"]`` will be a callable
240 | that takes a context manager, and can be called multiple times.
241 | Each context manager will be entered before evaluating code in the
242 | frame, then exited again, so they can perform setup and cleanup for
243 | each call.
244 |
245 | :param app: the WSGI application to run debugged.
246 | :param evalex: enable exception evaluation feature (interactive
247 | debugging). This requires a non-forking server.
248 | :param request_key: The key that points to the request object in this
249 | environment. This parameter is ignored in current
250 | versions.
251 | :param console_path: the URL for a general purpose console.
252 | :param console_init_func: the function that is executed before starting
253 | the general purpose console. The return value
254 | is used as initial namespace.
255 | :param show_hidden_frames: by default hidden traceback frames are skipped.
256 | You can show them by setting this parameter
257 | to `True`.
258 | :param pin_security: can be used to disable the pin based security system.
259 | :param pin_logging: enables the logging of the pin system.
260 |
261 | .. versionchanged:: 2.2
262 | Added the ``werkzeug.debug.preserve_context`` environ key.
263 | """
264 |
265 | _pin: str
266 | _pin_cookie: str
267 |
268 | def __init__(
269 | self,
270 | app: WSGIApplication,
271 | evalex: bool = False,
272 | request_key: str = "werkzeug.request",
273 | console_path: str = "/console",
274 | console_init_func: t.Callable[[], dict[str, t.Any]] | None = None,
275 | show_hidden_frames: bool = False,
276 | pin_security: bool = True,
277 | pin_logging: bool = True,
278 | ) -> None:
279 | if not console_init_func:
280 | console_init_func = None
281 | self.app = app
282 | self.evalex = evalex
283 | self.frames: dict[int, DebugFrameSummary | _ConsoleFrame] = {}
284 | self.frame_contexts: dict[int, list[t.ContextManager[None]]] = {}
285 | self.request_key = request_key
286 | self.console_path = console_path
287 | self.console_init_func = console_init_func
288 | self.show_hidden_frames = show_hidden_frames
289 | self.secret = gen_salt(20)
290 | self._failed_pin_auth = Value("B")
291 |
292 | self.pin_logging = pin_logging
293 | if pin_security:
294 | # Print out the pin for the debugger on standard out.
295 | if os.environ.get("WERKZEUG_RUN_MAIN") == "true" and pin_logging:
296 | _log("warning", " * Debugger is active!")
297 | if self.pin is None:
298 | _log("warning", " * Debugger PIN disabled. DEBUGGER UNSECURED!")
299 | else:
300 | _log("info", " * Debugger PIN: %s", self.pin)
301 | else:
302 | self.pin = None
303 |
304 | self.trusted_hosts: list[str] = [".localhost", "127.0.0.1"]
305 | """List of domains to allow requests to the debugger from. A leading dot
306 | allows all subdomains. This only allows ``".localhost"`` domains by
307 | default.
308 |
309 | .. versionadded:: 3.0.3
310 | """
311 |
312 | @property
313 | def pin(self) -> str | None:
314 | if not hasattr(self, "_pin"):
315 | pin_cookie = get_pin_and_cookie_name(self.app)
316 | self._pin, self._pin_cookie = pin_cookie # type: ignore
317 | return self._pin
318 |
319 | @pin.setter
320 | def pin(self, value: str) -> None:
321 | self._pin = value
322 |
323 | @property
324 | def pin_cookie_name(self) -> str:
325 | """The name of the pin cookie."""
326 | if not hasattr(self, "_pin_cookie"):
327 | pin_cookie = get_pin_and_cookie_name(self.app)
328 | self._pin, self._pin_cookie = pin_cookie # type: ignore
329 | return self._pin_cookie
330 |
331 | def debug_application(
332 | self, environ: WSGIEnvironment, start_response: StartResponse
333 | ) -> t.Iterator[bytes]:
334 | """Run the application and conserve the traceback frames."""
335 | contexts: list[t.ContextManager[t.Any]] = []
336 |
337 | if self.evalex:
338 | environ["werkzeug.debug.preserve_context"] = contexts.append
339 |
340 | app_iter = None
341 | try:
342 | app_iter = self.app(environ, start_response)
343 | yield from app_iter
344 | if hasattr(app_iter, "close"):
345 | app_iter.close()
346 | except Exception as e:
347 | if hasattr(app_iter, "close"):
348 | app_iter.close() # type: ignore
349 |
350 | tb = DebugTraceback(e, skip=1, hide=not self.show_hidden_frames)
351 |
352 | for frame in tb.all_frames:
353 | self.frames[id(frame)] = frame
354 | self.frame_contexts[id(frame)] = contexts
355 |
356 | is_trusted = bool(self.check_pin_trust(environ))
357 | html = tb.render_debugger_html(
358 | evalex=self.evalex and self.check_host_trust(environ),
359 | secret=self.secret,
360 | evalex_trusted=is_trusted,
361 | )
362 | response = Response(html, status=500, mimetype="text/html")
363 |
364 | try:
365 | yield from response(environ, start_response)
366 | except Exception:
367 | # if we end up here there has been output but an error
368 | # occurred. in that situation we can do nothing fancy any
369 | # more, better log something into the error log and fall
370 | # back gracefully.
371 | environ["wsgi.errors"].write(
372 | "Debugging middleware caught exception in streamed "
373 | "response at a point where response headers were already "
374 | "sent.\n"
375 | )
376 |
377 | environ["wsgi.errors"].write("".join(tb.render_traceback_text()))
378 |
379 | def execute_command(
380 | self,
381 | request: Request,
382 | command: str,
383 | frame: DebugFrameSummary | _ConsoleFrame,
384 | ) -> Response:
385 | """Execute a command in a console."""
386 | if not self.check_host_trust(request.environ):
387 | return SecurityError() # type: ignore[return-value]
388 |
389 | contexts = self.frame_contexts.get(id(frame), [])
390 |
391 | with ExitStack() as exit_stack:
392 | for cm in contexts:
393 | exit_stack.enter_context(cm)
394 |
395 | return Response(frame.eval(command), mimetype="text/html")
396 |
397 | def display_console(self, request: Request) -> Response:
398 | """Display a standalone shell."""
399 | if not self.check_host_trust(request.environ):
400 | return SecurityError() # type: ignore[return-value]
401 |
402 | if 0 not in self.frames:
403 | if self.console_init_func is None:
404 | ns = {}
405 | else:
406 | ns = dict(self.console_init_func())
407 | ns.setdefault("app", self.app)
408 | self.frames[0] = _ConsoleFrame(ns)
409 | is_trusted = bool(self.check_pin_trust(request.environ))
410 | return Response(
411 | render_console_html(secret=self.secret, evalex_trusted=is_trusted),
412 | mimetype="text/html",
413 | )
414 |
415 | def get_resource(self, request: Request, filename: str) -> Response:
416 | """Return a static resource from the shared folder."""
417 | path = join("shared", basename(filename))
418 |
419 | try:
420 | data = pkgutil.get_data(__package__, path)
421 | except OSError:
422 | return NotFound() # type: ignore[return-value]
423 | else:
424 | if data is None:
425 | return NotFound() # type: ignore[return-value]
426 |
427 | etag = str(adler32(data) & 0xFFFFFFFF)
428 | return send_file(
429 | BytesIO(data), request.environ, download_name=filename, etag=etag
430 | )
431 |
432 | def check_pin_trust(self, environ: WSGIEnvironment) -> bool | None:
433 | """Checks if the request passed the pin test. This returns `True` if the
434 | request is trusted on a pin/cookie basis and returns `False` if not.
435 | Additionally if the cookie's stored pin hash is wrong it will return
436 | `None` so that appropriate action can be taken.
437 | """
438 | if self.pin is None:
439 | return True
440 | val = parse_cookie(environ).get(self.pin_cookie_name)
441 | if not val or "|" not in val:
442 | return False
443 | ts_str, pin_hash = val.split("|", 1)
444 |
445 | try:
446 | ts = int(ts_str)
447 | except ValueError:
448 | return False
449 |
450 | if pin_hash != hash_pin(self.pin):
451 | return None
452 | return (time.time() - PIN_TIME) < ts
453 |
454 | def check_host_trust(self, environ: WSGIEnvironment) -> bool:
455 | return host_is_trusted(environ.get("HTTP_HOST"), self.trusted_hosts)
456 |
457 | def _fail_pin_auth(self) -> None:
458 | with self._failed_pin_auth.get_lock():
459 | count = self._failed_pin_auth.value
460 | self._failed_pin_auth.value = count + 1
461 |
462 | time.sleep(5.0 if count > 5 else 0.5)
463 |
464 | def pin_auth(self, request: Request) -> Response:
465 | """Authenticates with the pin."""
466 | if not self.check_host_trust(request.environ):
467 | return SecurityError() # type: ignore[return-value]
468 |
469 | exhausted = False
470 | auth = False
471 | trust = self.check_pin_trust(request.environ)
472 | pin = t.cast(str, self.pin)
473 |
474 | # If the trust return value is `None` it means that the cookie is
475 | # set but the stored pin hash value is bad. This means that the
476 | # pin was changed. In this case we count a bad auth and unset the
477 | # cookie. This way it becomes harder to guess the cookie name
478 | # instead of the pin as we still count up failures.
479 | bad_cookie = False
480 | if trust is None:
481 | self._fail_pin_auth()
482 | bad_cookie = True
483 |
484 | # If we're trusted, we're authenticated.
485 | elif trust:
486 | auth = True
487 |
488 | # If we failed too many times, then we're locked out.
489 | elif self._failed_pin_auth.value > 10:
490 | exhausted = True
491 |
492 | # Otherwise go through pin based authentication
493 | else:
494 | entered_pin = request.args["pin"]
495 |
496 | if entered_pin.strip().replace("-", "") == pin.replace("-", ""):
497 | self._failed_pin_auth.value = 0
498 | auth = True
499 | else:
500 | self._fail_pin_auth()
501 |
502 | rv = Response(
503 | json.dumps({"auth": auth, "exhausted": exhausted}),
504 | mimetype="application/json",
505 | )
506 | if auth:
507 | rv.set_cookie(
508 | self.pin_cookie_name,
509 | f"{int(time.time())}|{hash_pin(pin)}",
510 | httponly=True,
511 | samesite="Strict",
512 | secure=request.is_secure,
513 | )
514 | elif bad_cookie:
515 | rv.delete_cookie(self.pin_cookie_name)
516 | return rv
517 |
518 | def log_pin_request(self, request: Request) -> Response:
519 | """Log the pin if needed."""
520 | if not self.check_host_trust(request.environ):
521 | return SecurityError() # type: ignore[return-value]
522 |
523 | if self.pin_logging and self.pin is not None:
524 | _log(
525 | "info", " * To enable the debugger you need to enter the security pin:"
526 | )
527 | _log("info", " * Debugger pin code: %s", self.pin)
528 | return Response("")
529 |
530 | def __call__(
531 | self, environ: WSGIEnvironment, start_response: StartResponse
532 | ) -> t.Iterable[bytes]:
533 | """Dispatch the requests."""
534 | # important: don't ever access a function here that reads the incoming
535 | # form data! Otherwise the application won't have access to that data
536 | # any more!
537 | request = Request(environ)
538 | response = self.debug_application
539 | if request.args.get("__debugger__") == "yes":
540 | cmd = request.args.get("cmd")
541 | arg = request.args.get("f")
542 | secret = request.args.get("s")
543 | frame = self.frames.get(request.args.get("frm", type=int)) # type: ignore
544 | if cmd == "resource" and arg:
545 | response = self.get_resource(request, arg) # type: ignore
546 | elif cmd == "pinauth" and secret == self.secret:
547 | response = self.pin_auth(request) # type: ignore
548 | elif cmd == "printpin" and secret == self.secret:
549 | response = self.log_pin_request(request) # type: ignore
550 | elif (
551 | self.evalex
552 | and cmd is not None
553 | and frame is not None
554 | and self.secret == secret
555 | and self.check_pin_trust(environ)
556 | ):
557 | response = self.execute_command(request, cmd, frame) # type: ignore
558 | elif (
559 | self.evalex
560 | and self.console_path is not None
561 | and request.path == self.console_path
562 | ):
563 | response = self.display_console(request) # type: ignore
564 | return response(environ, start_response)
565 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/werkzeug/sansio/request.py:
--------------------------------------------------------------------------------
```python
1 | from __future__ import annotations
2 |
3 | import typing as t
4 | from datetime import datetime
5 | from urllib.parse import parse_qsl
6 |
7 | from ..datastructures import Accept
8 | from ..datastructures import Authorization
9 | from ..datastructures import CharsetAccept
10 | from ..datastructures import ETags
11 | from ..datastructures import Headers
12 | from ..datastructures import HeaderSet
13 | from ..datastructures import IfRange
14 | from ..datastructures import ImmutableList
15 | from ..datastructures import ImmutableMultiDict
16 | from ..datastructures import LanguageAccept
17 | from ..datastructures import MIMEAccept
18 | from ..datastructures import MultiDict
19 | from ..datastructures import Range
20 | from ..datastructures import RequestCacheControl
21 | from ..http import parse_accept_header
22 | from ..http import parse_cache_control_header
23 | from ..http import parse_date
24 | from ..http import parse_etags
25 | from ..http import parse_if_range_header
26 | from ..http import parse_list_header
27 | from ..http import parse_options_header
28 | from ..http import parse_range_header
29 | from ..http import parse_set_header
30 | from ..user_agent import UserAgent
31 | from ..utils import cached_property
32 | from ..utils import header_property
33 | from .http import parse_cookie
34 | from .utils import get_content_length
35 | from .utils import get_current_url
36 | from .utils import get_host
37 |
38 |
39 | class Request:
40 | """Represents the non-IO parts of a HTTP request, including the
41 | method, URL info, and headers.
42 |
43 | This class is not meant for general use. It should only be used when
44 | implementing WSGI, ASGI, or another HTTP application spec. Werkzeug
45 | provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`.
46 |
47 | :param method: The method the request was made with, such as
48 | ``GET``.
49 | :param scheme: The URL scheme of the protocol the request used, such
50 | as ``https`` or ``wss``.
51 | :param server: The address of the server. ``(host, port)``,
52 | ``(path, None)`` for unix sockets, or ``None`` if not known.
53 | :param root_path: The prefix that the application is mounted under.
54 | This is prepended to generated URLs, but is not part of route
55 | matching.
56 | :param path: The path part of the URL after ``root_path``.
57 | :param query_string: The part of the URL after the "?".
58 | :param headers: The headers received with the request.
59 | :param remote_addr: The address of the client sending the request.
60 |
61 | .. versionchanged:: 3.0
62 | The ``charset``, ``url_charset``, and ``encoding_errors`` attributes
63 | were removed.
64 |
65 | .. versionadded:: 2.0
66 | """
67 |
68 | #: the class to use for `args` and `form`. The default is an
69 | #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
70 | #: multiple values per key. alternatively it makes sense to use an
71 | #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
72 | #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
73 | #: which is the fastest but only remembers the last key. It is also
74 | #: possible to use mutable structures, but this is not recommended.
75 | #:
76 | #: .. versionadded:: 0.6
77 | parameter_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict
78 |
79 | #: The type to be used for dict values from the incoming WSGI
80 | #: environment. (For example for :attr:`cookies`.) By default an
81 | #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used.
82 | #:
83 | #: .. versionchanged:: 1.0.0
84 | #: Changed to ``ImmutableMultiDict`` to support multiple values.
85 | #:
86 | #: .. versionadded:: 0.6
87 | dict_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict
88 |
89 | #: the type to be used for list values from the incoming WSGI environment.
90 | #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
91 | #: (for example for :attr:`access_list`).
92 | #:
93 | #: .. versionadded:: 0.6
94 | list_storage_class: type[list[t.Any]] = ImmutableList
95 |
96 | user_agent_class: type[UserAgent] = UserAgent
97 | """The class used and returned by the :attr:`user_agent` property to
98 | parse the header. Defaults to
99 | :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An
100 | extension can provide a subclass that uses a parser to provide other
101 | data.
102 |
103 | .. versionadded:: 2.0
104 | """
105 |
106 | #: Valid host names when handling requests. By default all hosts are
107 | #: trusted, which means that whatever the client says the host is
108 | #: will be accepted.
109 | #:
110 | #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to
111 | #: any value by a malicious client, it is recommended to either set
112 | #: this property or implement similar validation in the proxy (if
113 | #: the application is being run behind one).
114 | #:
115 | #: .. versionadded:: 0.9
116 | trusted_hosts: list[str] | None = None
117 |
118 | def __init__(
119 | self,
120 | method: str,
121 | scheme: str,
122 | server: tuple[str, int | None] | None,
123 | root_path: str,
124 | path: str,
125 | query_string: bytes,
126 | headers: Headers,
127 | remote_addr: str | None,
128 | ) -> None:
129 | #: The method the request was made with, such as ``GET``.
130 | self.method = method.upper()
131 | #: The URL scheme of the protocol the request used, such as
132 | #: ``https`` or ``wss``.
133 | self.scheme = scheme
134 | #: The address of the server. ``(host, port)``, ``(path, None)``
135 | #: for unix sockets, or ``None`` if not known.
136 | self.server = server
137 | #: The prefix that the application is mounted under, without a
138 | #: trailing slash. :attr:`path` comes after this.
139 | self.root_path = root_path.rstrip("/")
140 | #: The path part of the URL after :attr:`root_path`. This is the
141 | #: path used for routing within the application.
142 | self.path = "/" + path.lstrip("/")
143 | #: The part of the URL after the "?". This is the raw value, use
144 | #: :attr:`args` for the parsed values.
145 | self.query_string = query_string
146 | #: The headers received with the request.
147 | self.headers = headers
148 | #: The address of the client sending the request.
149 | self.remote_addr = remote_addr
150 |
151 | def __repr__(self) -> str:
152 | try:
153 | url = self.url
154 | except Exception as e:
155 | url = f"(invalid URL: {e})"
156 |
157 | return f"<{type(self).__name__} {url!r} [{self.method}]>"
158 |
159 | @cached_property
160 | def args(self) -> MultiDict[str, str]:
161 | """The parsed URL parameters (the part in the URL after the question
162 | mark).
163 |
164 | By default an
165 | :class:`~werkzeug.datastructures.ImmutableMultiDict`
166 | is returned from this function. This can be changed by setting
167 | :attr:`parameter_storage_class` to a different type. This might
168 | be necessary if the order of the form data is important.
169 |
170 | .. versionchanged:: 2.3
171 | Invalid bytes remain percent encoded.
172 | """
173 | return self.parameter_storage_class(
174 | parse_qsl(
175 | self.query_string.decode(),
176 | keep_blank_values=True,
177 | errors="werkzeug.url_quote",
178 | )
179 | )
180 |
181 | @cached_property
182 | def access_route(self) -> list[str]:
183 | """If a forwarded header exists this is a list of all ip addresses
184 | from the client ip to the last proxy server.
185 | """
186 | if "X-Forwarded-For" in self.headers:
187 | return self.list_storage_class(
188 | parse_list_header(self.headers["X-Forwarded-For"])
189 | )
190 | elif self.remote_addr is not None:
191 | return self.list_storage_class([self.remote_addr])
192 | return self.list_storage_class()
193 |
194 | @cached_property
195 | def full_path(self) -> str:
196 | """Requested path, including the query string."""
197 | return f"{self.path}?{self.query_string.decode()}"
198 |
199 | @property
200 | def is_secure(self) -> bool:
201 | """``True`` if the request was made with a secure protocol
202 | (HTTPS or WSS).
203 | """
204 | return self.scheme in {"https", "wss"}
205 |
206 | @cached_property
207 | def url(self) -> str:
208 | """The full request URL with the scheme, host, root path, path,
209 | and query string."""
210 | return get_current_url(
211 | self.scheme, self.host, self.root_path, self.path, self.query_string
212 | )
213 |
214 | @cached_property
215 | def base_url(self) -> str:
216 | """Like :attr:`url` but without the query string."""
217 | return get_current_url(self.scheme, self.host, self.root_path, self.path)
218 |
219 | @cached_property
220 | def root_url(self) -> str:
221 | """The request URL scheme, host, and root path. This is the root
222 | that the application is accessed from.
223 | """
224 | return get_current_url(self.scheme, self.host, self.root_path)
225 |
226 | @cached_property
227 | def host_url(self) -> str:
228 | """The request URL scheme and host only."""
229 | return get_current_url(self.scheme, self.host)
230 |
231 | @cached_property
232 | def host(self) -> str:
233 | """The host name the request was made to, including the port if
234 | it's non-standard. Validated with :attr:`trusted_hosts`.
235 | """
236 | return get_host(
237 | self.scheme, self.headers.get("host"), self.server, self.trusted_hosts
238 | )
239 |
240 | @cached_property
241 | def cookies(self) -> ImmutableMultiDict[str, str]:
242 | """A :class:`dict` with the contents of all cookies transmitted with
243 | the request."""
244 | wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie"))
245 | return parse_cookie( # type: ignore
246 | wsgi_combined_cookie, cls=self.dict_storage_class
247 | )
248 |
249 | # Common Descriptors
250 |
251 | content_type = header_property[str](
252 | "Content-Type",
253 | doc="""The Content-Type entity-header field indicates the media
254 | type of the entity-body sent to the recipient or, in the case of
255 | the HEAD method, the media type that would have been sent had
256 | the request been a GET.""",
257 | read_only=True,
258 | )
259 |
260 | @cached_property
261 | def content_length(self) -> int | None:
262 | """The Content-Length entity-header field indicates the size of the
263 | entity-body in bytes or, in the case of the HEAD method, the size of
264 | the entity-body that would have been sent had the request been a
265 | GET.
266 | """
267 | return get_content_length(
268 | http_content_length=self.headers.get("Content-Length"),
269 | http_transfer_encoding=self.headers.get("Transfer-Encoding"),
270 | )
271 |
272 | content_encoding = header_property[str](
273 | "Content-Encoding",
274 | doc="""The Content-Encoding entity-header field is used as a
275 | modifier to the media-type. When present, its value indicates
276 | what additional content codings have been applied to the
277 | entity-body, and thus what decoding mechanisms must be applied
278 | in order to obtain the media-type referenced by the Content-Type
279 | header field.
280 |
281 | .. versionadded:: 0.9""",
282 | read_only=True,
283 | )
284 | content_md5 = header_property[str](
285 | "Content-MD5",
286 | doc="""The Content-MD5 entity-header field, as defined in
287 | RFC 1864, is an MD5 digest of the entity-body for the purpose of
288 | providing an end-to-end message integrity check (MIC) of the
289 | entity-body. (Note: a MIC is good for detecting accidental
290 | modification of the entity-body in transit, but is not proof
291 | against malicious attacks.)
292 |
293 | .. versionadded:: 0.9""",
294 | read_only=True,
295 | )
296 | referrer = header_property[str](
297 | "Referer",
298 | doc="""The Referer[sic] request-header field allows the client
299 | to specify, for the server's benefit, the address (URI) of the
300 | resource from which the Request-URI was obtained (the
301 | "referrer", although the header field is misspelled).""",
302 | read_only=True,
303 | )
304 | date = header_property(
305 | "Date",
306 | None,
307 | parse_date,
308 | doc="""The Date general-header field represents the date and
309 | time at which the message was originated, having the same
310 | semantics as orig-date in RFC 822.
311 |
312 | .. versionchanged:: 2.0
313 | The datetime object is timezone-aware.
314 | """,
315 | read_only=True,
316 | )
317 | max_forwards = header_property(
318 | "Max-Forwards",
319 | None,
320 | int,
321 | doc="""The Max-Forwards request-header field provides a
322 | mechanism with the TRACE and OPTIONS methods to limit the number
323 | of proxies or gateways that can forward the request to the next
324 | inbound server.""",
325 | read_only=True,
326 | )
327 |
328 | def _parse_content_type(self) -> None:
329 | if not hasattr(self, "_parsed_content_type"):
330 | self._parsed_content_type = parse_options_header(
331 | self.headers.get("Content-Type", "")
332 | )
333 |
334 | @property
335 | def mimetype(self) -> str:
336 | """Like :attr:`content_type`, but without parameters (eg, without
337 | charset, type etc.) and always lowercase. For example if the content
338 | type is ``text/HTML; charset=utf-8`` the mimetype would be
339 | ``'text/html'``.
340 | """
341 | self._parse_content_type()
342 | return self._parsed_content_type[0].lower()
343 |
344 | @property
345 | def mimetype_params(self) -> dict[str, str]:
346 | """The mimetype parameters as dict. For example if the content
347 | type is ``text/html; charset=utf-8`` the params would be
348 | ``{'charset': 'utf-8'}``.
349 | """
350 | self._parse_content_type()
351 | return self._parsed_content_type[1]
352 |
353 | @cached_property
354 | def pragma(self) -> HeaderSet:
355 | """The Pragma general-header field is used to include
356 | implementation-specific directives that might apply to any recipient
357 | along the request/response chain. All pragma directives specify
358 | optional behavior from the viewpoint of the protocol; however, some
359 | systems MAY require that behavior be consistent with the directives.
360 | """
361 | return parse_set_header(self.headers.get("Pragma", ""))
362 |
363 | # Accept
364 |
365 | @cached_property
366 | def accept_mimetypes(self) -> MIMEAccept:
367 | """List of mimetypes this client supports as
368 | :class:`~werkzeug.datastructures.MIMEAccept` object.
369 | """
370 | return parse_accept_header(self.headers.get("Accept"), MIMEAccept)
371 |
372 | @cached_property
373 | def accept_charsets(self) -> CharsetAccept:
374 | """List of charsets this client supports as
375 | :class:`~werkzeug.datastructures.CharsetAccept` object.
376 | """
377 | return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept)
378 |
379 | @cached_property
380 | def accept_encodings(self) -> Accept:
381 | """List of encodings this client accepts. Encodings in a HTTP term
382 | are compression encodings such as gzip. For charsets have a look at
383 | :attr:`accept_charset`.
384 | """
385 | return parse_accept_header(self.headers.get("Accept-Encoding"))
386 |
387 | @cached_property
388 | def accept_languages(self) -> LanguageAccept:
389 | """List of languages this client accepts as
390 | :class:`~werkzeug.datastructures.LanguageAccept` object.
391 |
392 | .. versionchanged 0.5
393 | In previous versions this was a regular
394 | :class:`~werkzeug.datastructures.Accept` object.
395 | """
396 | return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept)
397 |
398 | # ETag
399 |
400 | @cached_property
401 | def cache_control(self) -> RequestCacheControl:
402 | """A :class:`~werkzeug.datastructures.RequestCacheControl` object
403 | for the incoming cache control headers.
404 | """
405 | cache_control = self.headers.get("Cache-Control")
406 | return parse_cache_control_header(cache_control, None, RequestCacheControl)
407 |
408 | @cached_property
409 | def if_match(self) -> ETags:
410 | """An object containing all the etags in the `If-Match` header.
411 |
412 | :rtype: :class:`~werkzeug.datastructures.ETags`
413 | """
414 | return parse_etags(self.headers.get("If-Match"))
415 |
416 | @cached_property
417 | def if_none_match(self) -> ETags:
418 | """An object containing all the etags in the `If-None-Match` header.
419 |
420 | :rtype: :class:`~werkzeug.datastructures.ETags`
421 | """
422 | return parse_etags(self.headers.get("If-None-Match"))
423 |
424 | @cached_property
425 | def if_modified_since(self) -> datetime | None:
426 | """The parsed `If-Modified-Since` header as a datetime object.
427 |
428 | .. versionchanged:: 2.0
429 | The datetime object is timezone-aware.
430 | """
431 | return parse_date(self.headers.get("If-Modified-Since"))
432 |
433 | @cached_property
434 | def if_unmodified_since(self) -> datetime | None:
435 | """The parsed `If-Unmodified-Since` header as a datetime object.
436 |
437 | .. versionchanged:: 2.0
438 | The datetime object is timezone-aware.
439 | """
440 | return parse_date(self.headers.get("If-Unmodified-Since"))
441 |
442 | @cached_property
443 | def if_range(self) -> IfRange:
444 | """The parsed ``If-Range`` header.
445 |
446 | .. versionchanged:: 2.0
447 | ``IfRange.date`` is timezone-aware.
448 |
449 | .. versionadded:: 0.7
450 | """
451 | return parse_if_range_header(self.headers.get("If-Range"))
452 |
453 | @cached_property
454 | def range(self) -> Range | None:
455 | """The parsed `Range` header.
456 |
457 | .. versionadded:: 0.7
458 |
459 | :rtype: :class:`~werkzeug.datastructures.Range`
460 | """
461 | return parse_range_header(self.headers.get("Range"))
462 |
463 | # User Agent
464 |
465 | @cached_property
466 | def user_agent(self) -> UserAgent:
467 | """The user agent. Use ``user_agent.string`` to get the header
468 | value. Set :attr:`user_agent_class` to a subclass of
469 | :class:`~werkzeug.user_agent.UserAgent` to provide parsing for
470 | the other properties or other extended data.
471 |
472 | .. versionchanged:: 2.1
473 | The built-in parser was removed. Set ``user_agent_class`` to a ``UserAgent``
474 | subclass to parse data from the string.
475 | """
476 | return self.user_agent_class(self.headers.get("User-Agent", ""))
477 |
478 | # Authorization
479 |
480 | @cached_property
481 | def authorization(self) -> Authorization | None:
482 | """The ``Authorization`` header parsed into an :class:`.Authorization` object.
483 | ``None`` if the header is not present.
484 |
485 | .. versionchanged:: 2.3
486 | :class:`Authorization` is no longer a ``dict``. The ``token`` attribute
487 | was added for auth schemes that use a token instead of parameters.
488 | """
489 | return Authorization.from_header(self.headers.get("Authorization"))
490 |
491 | # CORS
492 |
493 | origin = header_property[str](
494 | "Origin",
495 | doc=(
496 | "The host that the request originated from. Set"
497 | " :attr:`~CORSResponseMixin.access_control_allow_origin` on"
498 | " the response to indicate which origins are allowed."
499 | ),
500 | read_only=True,
501 | )
502 |
503 | access_control_request_headers = header_property(
504 | "Access-Control-Request-Headers",
505 | load_func=parse_set_header,
506 | doc=(
507 | "Sent with a preflight request to indicate which headers"
508 | " will be sent with the cross origin request. Set"
509 | " :attr:`~CORSResponseMixin.access_control_allow_headers`"
510 | " on the response to indicate which headers are allowed."
511 | ),
512 | read_only=True,
513 | )
514 |
515 | access_control_request_method = header_property[str](
516 | "Access-Control-Request-Method",
517 | doc=(
518 | "Sent with a preflight request to indicate which method"
519 | " will be used for the cross origin request. Set"
520 | " :attr:`~CORSResponseMixin.access_control_allow_methods`"
521 | " on the response to indicate which methods are allowed."
522 | ),
523 | read_only=True,
524 | )
525 |
526 | @property
527 | def is_json(self) -> bool:
528 | """Check if the mimetype indicates JSON data, either
529 | :mimetype:`application/json` or :mimetype:`application/*+json`.
530 | """
531 | mt = self.mimetype
532 | return (
533 | mt == "application/json"
534 | or mt.startswith("application/")
535 | and mt.endswith("+json")
536 | )
537 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py:
--------------------------------------------------------------------------------
```python
1 | from __future__ import absolute_import
2 |
3 | import collections
4 | import functools
5 | import logging
6 |
7 | from ._collections import HTTPHeaderDict, RecentlyUsedContainer
8 | from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme
9 | from .exceptions import (
10 | LocationValueError,
11 | MaxRetryError,
12 | ProxySchemeUnknown,
13 | ProxySchemeUnsupported,
14 | URLSchemeUnknown,
15 | )
16 | from .packages import six
17 | from .packages.six.moves.urllib.parse import urljoin
18 | from .request import RequestMethods
19 | from .util.proxy import connection_requires_http_tunnel
20 | from .util.retry import Retry
21 | from .util.url import parse_url
22 |
23 | __all__ = ["PoolManager", "ProxyManager", "proxy_from_url"]
24 |
25 |
26 | log = logging.getLogger(__name__)
27 |
28 | SSL_KEYWORDS = (
29 | "key_file",
30 | "cert_file",
31 | "cert_reqs",
32 | "ca_certs",
33 | "ssl_version",
34 | "ca_cert_dir",
35 | "ssl_context",
36 | "key_password",
37 | "server_hostname",
38 | )
39 |
40 | # All known keyword arguments that could be provided to the pool manager, its
41 | # pools, or the underlying connections. This is used to construct a pool key.
42 | _key_fields = (
43 | "key_scheme", # str
44 | "key_host", # str
45 | "key_port", # int
46 | "key_timeout", # int or float or Timeout
47 | "key_retries", # int or Retry
48 | "key_strict", # bool
49 | "key_block", # bool
50 | "key_source_address", # str
51 | "key_key_file", # str
52 | "key_key_password", # str
53 | "key_cert_file", # str
54 | "key_cert_reqs", # str
55 | "key_ca_certs", # str
56 | "key_ssl_version", # str
57 | "key_ca_cert_dir", # str
58 | "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext
59 | "key_maxsize", # int
60 | "key_headers", # dict
61 | "key__proxy", # parsed proxy url
62 | "key__proxy_headers", # dict
63 | "key__proxy_config", # class
64 | "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples
65 | "key__socks_options", # dict
66 | "key_assert_hostname", # bool or string
67 | "key_assert_fingerprint", # str
68 | "key_server_hostname", # str
69 | )
70 |
71 | #: The namedtuple class used to construct keys for the connection pool.
72 | #: All custom key schemes should include the fields in this key at a minimum.
73 | PoolKey = collections.namedtuple("PoolKey", _key_fields)
74 |
75 | _proxy_config_fields = ("ssl_context", "use_forwarding_for_https")
76 | ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields)
77 |
78 |
79 | def _default_key_normalizer(key_class, request_context):
80 | """
81 | Create a pool key out of a request context dictionary.
82 |
83 | According to RFC 3986, both the scheme and host are case-insensitive.
84 | Therefore, this function normalizes both before constructing the pool
85 | key for an HTTPS request. If you wish to change this behaviour, provide
86 | alternate callables to ``key_fn_by_scheme``.
87 |
88 | :param key_class:
89 | The class to use when constructing the key. This should be a namedtuple
90 | with the ``scheme`` and ``host`` keys at a minimum.
91 | :type key_class: namedtuple
92 | :param request_context:
93 | A dictionary-like object that contain the context for a request.
94 | :type request_context: dict
95 |
96 | :return: A namedtuple that can be used as a connection pool key.
97 | :rtype: PoolKey
98 | """
99 | # Since we mutate the dictionary, make a copy first
100 | context = request_context.copy()
101 | context["scheme"] = context["scheme"].lower()
102 | context["host"] = context["host"].lower()
103 |
104 | # These are both dictionaries and need to be transformed into frozensets
105 | for key in ("headers", "_proxy_headers", "_socks_options"):
106 | if key in context and context[key] is not None:
107 | context[key] = frozenset(context[key].items())
108 |
109 | # The socket_options key may be a list and needs to be transformed into a
110 | # tuple.
111 | socket_opts = context.get("socket_options")
112 | if socket_opts is not None:
113 | context["socket_options"] = tuple(socket_opts)
114 |
115 | # Map the kwargs to the names in the namedtuple - this is necessary since
116 | # namedtuples can't have fields starting with '_'.
117 | for key in list(context.keys()):
118 | context["key_" + key] = context.pop(key)
119 |
120 | # Default to ``None`` for keys missing from the context
121 | for field in key_class._fields:
122 | if field not in context:
123 | context[field] = None
124 |
125 | return key_class(**context)
126 |
127 |
128 | #: A dictionary that maps a scheme to a callable that creates a pool key.
129 | #: This can be used to alter the way pool keys are constructed, if desired.
130 | #: Each PoolManager makes a copy of this dictionary so they can be configured
131 | #: globally here, or individually on the instance.
132 | key_fn_by_scheme = {
133 | "http": functools.partial(_default_key_normalizer, PoolKey),
134 | "https": functools.partial(_default_key_normalizer, PoolKey),
135 | }
136 |
137 | pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool}
138 |
139 |
140 | class PoolManager(RequestMethods):
141 | """
142 | Allows for arbitrary requests while transparently keeping track of
143 | necessary connection pools for you.
144 |
145 | :param num_pools:
146 | Number of connection pools to cache before discarding the least
147 | recently used pool.
148 |
149 | :param headers:
150 | Headers to include with all requests, unless other headers are given
151 | explicitly.
152 |
153 | :param \\**connection_pool_kw:
154 | Additional parameters are used to create fresh
155 | :class:`urllib3.connectionpool.ConnectionPool` instances.
156 |
157 | Example::
158 |
159 | >>> manager = PoolManager(num_pools=2)
160 | >>> r = manager.request('GET', 'http://google.com/')
161 | >>> r = manager.request('GET', 'http://google.com/mail')
162 | >>> r = manager.request('GET', 'http://yahoo.com/')
163 | >>> len(manager.pools)
164 | 2
165 |
166 | """
167 |
168 | proxy = None
169 | proxy_config = None
170 |
171 | def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
172 | RequestMethods.__init__(self, headers)
173 | self.connection_pool_kw = connection_pool_kw
174 | self.pools = RecentlyUsedContainer(num_pools)
175 |
176 | # Locally set the pool classes and keys so other PoolManagers can
177 | # override them.
178 | self.pool_classes_by_scheme = pool_classes_by_scheme
179 | self.key_fn_by_scheme = key_fn_by_scheme.copy()
180 |
181 | def __enter__(self):
182 | return self
183 |
184 | def __exit__(self, exc_type, exc_val, exc_tb):
185 | self.clear()
186 | # Return False to re-raise any potential exceptions
187 | return False
188 |
189 | def _new_pool(self, scheme, host, port, request_context=None):
190 | """
191 | Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and
192 | any additional pool keyword arguments.
193 |
194 | If ``request_context`` is provided, it is provided as keyword arguments
195 | to the pool class used. This method is used to actually create the
196 | connection pools handed out by :meth:`connection_from_url` and
197 | companion methods. It is intended to be overridden for customization.
198 | """
199 | pool_cls = self.pool_classes_by_scheme[scheme]
200 | if request_context is None:
201 | request_context = self.connection_pool_kw.copy()
202 |
203 | # Although the context has everything necessary to create the pool,
204 | # this function has historically only used the scheme, host, and port
205 | # in the positional args. When an API change is acceptable these can
206 | # be removed.
207 | for key in ("scheme", "host", "port"):
208 | request_context.pop(key, None)
209 |
210 | if scheme == "http":
211 | for kw in SSL_KEYWORDS:
212 | request_context.pop(kw, None)
213 |
214 | return pool_cls(host, port, **request_context)
215 |
216 | def clear(self):
217 | """
218 | Empty our store of pools and direct them all to close.
219 |
220 | This will not affect in-flight connections, but they will not be
221 | re-used after completion.
222 | """
223 | self.pools.clear()
224 |
225 | def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
226 | """
227 | Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme.
228 |
229 | If ``port`` isn't given, it will be derived from the ``scheme`` using
230 | ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is
231 | provided, it is merged with the instance's ``connection_pool_kw``
232 | variable and used to create the new connection pool, if one is
233 | needed.
234 | """
235 |
236 | if not host:
237 | raise LocationValueError("No host specified.")
238 |
239 | request_context = self._merge_pool_kwargs(pool_kwargs)
240 | request_context["scheme"] = scheme or "http"
241 | if not port:
242 | port = port_by_scheme.get(request_context["scheme"].lower(), 80)
243 | request_context["port"] = port
244 | request_context["host"] = host
245 |
246 | return self.connection_from_context(request_context)
247 |
248 | def connection_from_context(self, request_context):
249 | """
250 | Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context.
251 |
252 | ``request_context`` must at least contain the ``scheme`` key and its
253 | value must be a key in ``key_fn_by_scheme`` instance variable.
254 | """
255 | scheme = request_context["scheme"].lower()
256 | pool_key_constructor = self.key_fn_by_scheme.get(scheme)
257 | if not pool_key_constructor:
258 | raise URLSchemeUnknown(scheme)
259 | pool_key = pool_key_constructor(request_context)
260 |
261 | return self.connection_from_pool_key(pool_key, request_context=request_context)
262 |
263 | def connection_from_pool_key(self, pool_key, request_context=None):
264 | """
265 | Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key.
266 |
267 | ``pool_key`` should be a namedtuple that only contains immutable
268 | objects. At a minimum it must have the ``scheme``, ``host``, and
269 | ``port`` fields.
270 | """
271 | with self.pools.lock:
272 | # If the scheme, host, or port doesn't match existing open
273 | # connections, open a new ConnectionPool.
274 | pool = self.pools.get(pool_key)
275 | if pool:
276 | return pool
277 |
278 | # Make a fresh ConnectionPool of the desired type
279 | scheme = request_context["scheme"]
280 | host = request_context["host"]
281 | port = request_context["port"]
282 | pool = self._new_pool(scheme, host, port, request_context=request_context)
283 | self.pools[pool_key] = pool
284 |
285 | return pool
286 |
287 | def connection_from_url(self, url, pool_kwargs=None):
288 | """
289 | Similar to :func:`urllib3.connectionpool.connection_from_url`.
290 |
291 | If ``pool_kwargs`` is not provided and a new pool needs to be
292 | constructed, ``self.connection_pool_kw`` is used to initialize
293 | the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs``
294 | is provided, it is used instead. Note that if a new pool does not
295 | need to be created for the request, the provided ``pool_kwargs`` are
296 | not used.
297 | """
298 | u = parse_url(url)
299 | return self.connection_from_host(
300 | u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs
301 | )
302 |
303 | def _merge_pool_kwargs(self, override):
304 | """
305 | Merge a dictionary of override values for self.connection_pool_kw.
306 |
307 | This does not modify self.connection_pool_kw and returns a new dict.
308 | Any keys in the override dictionary with a value of ``None`` are
309 | removed from the merged dictionary.
310 | """
311 | base_pool_kwargs = self.connection_pool_kw.copy()
312 | if override:
313 | for key, value in override.items():
314 | if value is None:
315 | try:
316 | del base_pool_kwargs[key]
317 | except KeyError:
318 | pass
319 | else:
320 | base_pool_kwargs[key] = value
321 | return base_pool_kwargs
322 |
323 | def _proxy_requires_url_absolute_form(self, parsed_url):
324 | """
325 | Indicates if the proxy requires the complete destination URL in the
326 | request. Normally this is only needed when not using an HTTP CONNECT
327 | tunnel.
328 | """
329 | if self.proxy is None:
330 | return False
331 |
332 | return not connection_requires_http_tunnel(
333 | self.proxy, self.proxy_config, parsed_url.scheme
334 | )
335 |
336 | def _validate_proxy_scheme_url_selection(self, url_scheme):
337 | """
338 | Validates that were not attempting to do TLS in TLS connections on
339 | Python2 or with unsupported SSL implementations.
340 | """
341 | if self.proxy is None or url_scheme != "https":
342 | return
343 |
344 | if self.proxy.scheme != "https":
345 | return
346 |
347 | if six.PY2 and not self.proxy_config.use_forwarding_for_https:
348 | raise ProxySchemeUnsupported(
349 | "Contacting HTTPS destinations through HTTPS proxies "
350 | "'via CONNECT tunnels' is not supported in Python 2"
351 | )
352 |
353 | def urlopen(self, method, url, redirect=True, **kw):
354 | """
355 | Same as :meth:`urllib3.HTTPConnectionPool.urlopen`
356 | with custom cross-host redirect logic and only sends the request-uri
357 | portion of the ``url``.
358 |
359 | The given ``url`` parameter must be absolute, such that an appropriate
360 | :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
361 | """
362 | u = parse_url(url)
363 | self._validate_proxy_scheme_url_selection(u.scheme)
364 |
365 | conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)
366 |
367 | kw["assert_same_host"] = False
368 | kw["redirect"] = False
369 |
370 | if "headers" not in kw:
371 | kw["headers"] = self.headers.copy()
372 |
373 | if self._proxy_requires_url_absolute_form(u):
374 | response = conn.urlopen(method, url, **kw)
375 | else:
376 | response = conn.urlopen(method, u.request_uri, **kw)
377 |
378 | redirect_location = redirect and response.get_redirect_location()
379 | if not redirect_location:
380 | return response
381 |
382 | # Support relative URLs for redirecting.
383 | redirect_location = urljoin(url, redirect_location)
384 |
385 | if response.status == 303:
386 | # Change the method according to RFC 9110, Section 15.4.4.
387 | method = "GET"
388 | # And lose the body not to transfer anything sensitive.
389 | kw["body"] = None
390 | kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change()
391 |
392 | retries = kw.get("retries")
393 | if not isinstance(retries, Retry):
394 | retries = Retry.from_int(retries, redirect=redirect)
395 |
396 | # Strip headers marked as unsafe to forward to the redirected location.
397 | # Check remove_headers_on_redirect to avoid a potential network call within
398 | # conn.is_same_host() which may use socket.gethostbyname() in the future.
399 | if retries.remove_headers_on_redirect and not conn.is_same_host(
400 | redirect_location
401 | ):
402 | headers = list(six.iterkeys(kw["headers"]))
403 | for header in headers:
404 | if header.lower() in retries.remove_headers_on_redirect:
405 | kw["headers"].pop(header, None)
406 |
407 | try:
408 | retries = retries.increment(method, url, response=response, _pool=conn)
409 | except MaxRetryError:
410 | if retries.raise_on_redirect:
411 | response.drain_conn()
412 | raise
413 | return response
414 |
415 | kw["retries"] = retries
416 | kw["redirect"] = redirect
417 |
418 | log.info("Redirecting %s -> %s", url, redirect_location)
419 |
420 | response.drain_conn()
421 | return self.urlopen(method, redirect_location, **kw)
422 |
423 |
424 | class ProxyManager(PoolManager):
425 | """
426 | Behaves just like :class:`PoolManager`, but sends all requests through
427 | the defined proxy, using the CONNECT method for HTTPS URLs.
428 |
429 | :param proxy_url:
430 | The URL of the proxy to be used.
431 |
432 | :param proxy_headers:
433 | A dictionary containing headers that will be sent to the proxy. In case
434 | of HTTP they are being sent with each request, while in the
435 | HTTPS/CONNECT case they are sent only once. Could be used for proxy
436 | authentication.
437 |
438 | :param proxy_ssl_context:
439 | The proxy SSL context is used to establish the TLS connection to the
440 | proxy when using HTTPS proxies.
441 |
442 | :param use_forwarding_for_https:
443 | (Defaults to False) If set to True will forward requests to the HTTPS
444 | proxy to be made on behalf of the client instead of creating a TLS
445 | tunnel via the CONNECT method. **Enabling this flag means that request
446 | and response headers and content will be visible from the HTTPS proxy**
447 | whereas tunneling keeps request and response headers and content
448 | private. IP address, target hostname, SNI, and port are always visible
449 | to an HTTPS proxy even when this flag is disabled.
450 |
451 | Example:
452 | >>> proxy = urllib3.ProxyManager('http://localhost:3128/')
453 | >>> r1 = proxy.request('GET', 'http://google.com/')
454 | >>> r2 = proxy.request('GET', 'http://httpbin.org/')
455 | >>> len(proxy.pools)
456 | 1
457 | >>> r3 = proxy.request('GET', 'https://httpbin.org/')
458 | >>> r4 = proxy.request('GET', 'https://twitter.com/')
459 | >>> len(proxy.pools)
460 | 3
461 |
462 | """
463 |
464 | def __init__(
465 | self,
466 | proxy_url,
467 | num_pools=10,
468 | headers=None,
469 | proxy_headers=None,
470 | proxy_ssl_context=None,
471 | use_forwarding_for_https=False,
472 | **connection_pool_kw
473 | ):
474 |
475 | if isinstance(proxy_url, HTTPConnectionPool):
476 | proxy_url = "%s://%s:%i" % (
477 | proxy_url.scheme,
478 | proxy_url.host,
479 | proxy_url.port,
480 | )
481 | proxy = parse_url(proxy_url)
482 |
483 | if proxy.scheme not in ("http", "https"):
484 | raise ProxySchemeUnknown(proxy.scheme)
485 |
486 | if not proxy.port:
487 | port = port_by_scheme.get(proxy.scheme, 80)
488 | proxy = proxy._replace(port=port)
489 |
490 | self.proxy = proxy
491 | self.proxy_headers = proxy_headers or {}
492 | self.proxy_ssl_context = proxy_ssl_context
493 | self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https)
494 |
495 | connection_pool_kw["_proxy"] = self.proxy
496 | connection_pool_kw["_proxy_headers"] = self.proxy_headers
497 | connection_pool_kw["_proxy_config"] = self.proxy_config
498 |
499 | super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw)
500 |
501 | def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
502 | if scheme == "https":
503 | return super(ProxyManager, self).connection_from_host(
504 | host, port, scheme, pool_kwargs=pool_kwargs
505 | )
506 |
507 | return super(ProxyManager, self).connection_from_host(
508 | self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs
509 | )
510 |
511 | def _set_proxy_headers(self, url, headers=None):
512 | """
513 | Sets headers needed by proxies: specifically, the Accept and Host
514 | headers. Only sets headers not provided by the user.
515 | """
516 | headers_ = {"Accept": "*/*"}
517 |
518 | netloc = parse_url(url).netloc
519 | if netloc:
520 | headers_["Host"] = netloc
521 |
522 | if headers:
523 | headers_.update(headers)
524 | return headers_
525 |
526 | def urlopen(self, method, url, redirect=True, **kw):
527 | "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
528 | u = parse_url(url)
529 | if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme):
530 | # For connections using HTTP CONNECT, httplib sets the necessary
531 | # headers on the CONNECT to the proxy. If we're not using CONNECT,
532 | # we'll definitely need to set 'Host' at the very least.
533 | headers = kw.get("headers", self.headers)
534 | kw["headers"] = self._set_proxy_headers(url, headers)
535 |
536 | return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw)
537 |
538 |
539 | def proxy_from_url(url, **kw):
540 | return ProxyManager(proxy_url=url, **kw)
541 |
```