This is page 51 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/bs4/tests/test_pageelement.py:
--------------------------------------------------------------------------------
```python
1 | """Tests of the bs4.element.PageElement class"""
2 | import copy
3 | import pickle
4 | import pytest
5 | import sys
6 |
7 | from bs4 import BeautifulSoup
8 | from bs4.element import (
9 | Comment,
10 | ResultSet,
11 | SoupStrainer,
12 | )
13 | from . import (
14 | SoupTest,
15 | )
16 |
17 | class TestEncoding(SoupTest):
18 | """Test the ability to encode objects into strings."""
19 |
20 | def test_unicode_string_can_be_encoded(self):
21 | html = "<b>\N{SNOWMAN}</b>"
22 | soup = self.soup(html)
23 | assert soup.b.string.encode("utf-8") == "\N{SNOWMAN}".encode("utf-8")
24 |
25 | def test_tag_containing_unicode_string_can_be_encoded(self):
26 | html = "<b>\N{SNOWMAN}</b>"
27 | soup = self.soup(html)
28 | assert soup.b.encode("utf-8") == html.encode("utf-8")
29 |
30 | def test_encoding_substitutes_unrecognized_characters_by_default(self):
31 | html = "<b>\N{SNOWMAN}</b>"
32 | soup = self.soup(html)
33 | assert soup.b.encode("ascii") == b"<b>☃</b>"
34 |
35 | def test_encoding_can_be_made_strict(self):
36 | html = "<b>\N{SNOWMAN}</b>"
37 | soup = self.soup(html)
38 | with pytest.raises(UnicodeEncodeError):
39 | soup.encode("ascii", errors="strict")
40 |
41 | def test_decode_contents(self):
42 | html = "<b>\N{SNOWMAN}</b>"
43 | soup = self.soup(html)
44 | assert "\N{SNOWMAN}" == soup.b.decode_contents()
45 |
46 | def test_encode_contents(self):
47 | html = "<b>\N{SNOWMAN}</b>"
48 | soup = self.soup(html)
49 | assert "\N{SNOWMAN}".encode("utf8") == soup.b.encode_contents(
50 | encoding="utf8"
51 | )
52 |
53 | def test_encode_deeply_nested_document(self):
54 | # This test verifies that encoding a string doesn't involve
55 | # any recursive function calls. If it did, this test would
56 | # overflow the Python interpreter stack.
57 | limit = sys.getrecursionlimit() + 1
58 | markup = "<span>" * limit
59 | soup = self.soup(markup)
60 | encoded = soup.encode()
61 | assert limit == encoded.count(b"<span>")
62 |
63 | def test_deprecated_renderContents(self):
64 | html = "<b>\N{SNOWMAN}</b>"
65 | soup = self.soup(html)
66 | soup.renderContents()
67 | assert "\N{SNOWMAN}".encode("utf8") == soup.b.renderContents()
68 |
69 | def test_repr(self):
70 | html = "<b>\N{SNOWMAN}</b>"
71 | soup = self.soup(html)
72 | assert html == repr(soup)
73 |
74 |
75 | class TestFormatters(SoupTest):
76 | """Test the formatting feature, used by methods like decode() and
77 | prettify(), and the formatters themselves.
78 | """
79 |
80 | def test_default_formatter_is_minimal(self):
81 | markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
82 | soup = self.soup(markup)
83 | decoded = soup.decode(formatter="minimal")
84 | # The < is converted back into < but the e-with-acute is left alone.
85 | assert decoded == self.document_for(
86 | "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
87 | )
88 |
89 | def test_formatter_html(self):
90 | markup = "<br><b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
91 | soup = self.soup(markup)
92 | decoded = soup.decode(formatter="html")
93 | assert decoded == self.document_for(
94 | "<br/><b><<Sacré bleu!>></b>"
95 | )
96 |
97 | def test_formatter_html5(self):
98 | markup = "<br><b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
99 | soup = self.soup(markup)
100 | decoded = soup.decode(formatter="html5")
101 | assert decoded == self.document_for(
102 | "<br><b><<Sacré bleu!>></b>"
103 | )
104 |
105 | def test_formatter_minimal(self):
106 | markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
107 | soup = self.soup(markup)
108 | decoded = soup.decode(formatter="minimal")
109 | # The < is converted back into < but the e-with-acute is left alone.
110 | assert decoded == self.document_for(
111 | "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
112 | )
113 |
114 | def test_formatter_null(self):
115 | markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
116 | soup = self.soup(markup)
117 | decoded = soup.decode(formatter=None)
118 | # Neither the angle brackets nor the e-with-acute are converted.
119 | # This is not valid HTML, but it's what the user wanted.
120 | assert decoded == self.document_for(
121 | "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>"
122 | )
123 |
124 | def test_formatter_custom(self):
125 | markup = "<b><foo></b><b>bar</b><br/>"
126 | soup = self.soup(markup)
127 | decoded = soup.decode(formatter = lambda x: x.upper())
128 | # Instead of normal entity conversion code, the custom
129 | # callable is called on every string.
130 | assert decoded == self.document_for("<b><FOO></b><b>BAR</b><br/>")
131 |
132 | def test_formatter_is_run_on_attribute_values(self):
133 | markup = '<a href="http://a.com?a=b&c=é">e</a>'
134 | soup = self.soup(markup)
135 | a = soup.a
136 |
137 | expect_minimal = '<a href="http://a.com?a=b&c=é">e</a>'
138 |
139 | assert expect_minimal == a.decode()
140 | assert expect_minimal == a.decode(formatter="minimal")
141 |
142 | expect_html = '<a href="http://a.com?a=b&c=é">e</a>'
143 | assert expect_html == a.decode(formatter="html")
144 |
145 | assert markup == a.decode(formatter=None)
146 | expect_upper = '<a href="HTTP://A.COM?A=B&C=É">E</a>'
147 | assert expect_upper == a.decode(formatter=lambda x: x.upper())
148 |
149 | def test_formatter_skips_script_tag_for_html_documents(self):
150 | doc = """
151 | <script type="text/javascript">
152 | console.log("< < hey > > ");
153 | </script>
154 | """
155 | encoded = BeautifulSoup(doc, 'html.parser').encode()
156 | assert b"< < hey > >" in encoded
157 |
158 | def test_formatter_skips_style_tag_for_html_documents(self):
159 | doc = """
160 | <style type="text/css">
161 | console.log("< < hey > > ");
162 | </style>
163 | """
164 | encoded = BeautifulSoup(doc, 'html.parser').encode()
165 | assert b"< < hey > >" in encoded
166 |
167 | def test_prettify_leaves_preformatted_text_alone(self):
168 | soup = self.soup("<div> foo <pre> \tbar\n \n </pre> baz <textarea> eee\nfff\t</textarea></div>")
169 | # Everything outside the <pre> tag is reformatted, but everything
170 | # inside is left alone.
171 | assert '<div>\n foo\n <pre> \tbar\n \n </pre>\n baz\n <textarea> eee\nfff\t</textarea>\n</div>\n' == soup.div.prettify()
172 |
173 | def test_prettify_handles_nested_string_literal_tags(self):
174 | # Most of this markup is inside a <pre> tag, so prettify()
175 | # only does three things to it:
176 | # 1. Add a newline and a space between the <div> and the <pre>
177 | # 2. Add a newline after the </pre>
178 | # 3. Add a newline at the end.
179 | #
180 | # The contents of the <pre> tag are left completely alone. In
181 | # particular, we don't start adding whitespace again once we
182 | # encounter the first </pre> tag, because we know it's not
183 | # the one that put us into string literal mode.
184 | markup = """<div><pre><code>some
185 | <script><pre>code</pre></script> for you
186 | </code></pre></div>"""
187 |
188 | expect = """<div>
189 | <pre><code>some
190 | <script><pre>code</pre></script> for you
191 | </code></pre>
192 | </div>
193 | """
194 | soup = self.soup(markup)
195 | assert expect == soup.div.prettify()
196 |
197 | def test_prettify_accepts_formatter_function(self):
198 | soup = BeautifulSoup("<html><body>foo</body></html>", 'html.parser')
199 | pretty = soup.prettify(formatter = lambda x: x.upper())
200 | assert "FOO" in pretty
201 |
202 | def test_prettify_outputs_unicode_by_default(self):
203 | soup = self.soup("<a></a>")
204 | assert str == type(soup.prettify())
205 |
206 | def test_prettify_can_encode_data(self):
207 | soup = self.soup("<a></a>")
208 | assert bytes == type(soup.prettify("utf-8"))
209 |
210 | def test_html_entity_substitution_off_by_default(self):
211 | markup = "<b>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</b>"
212 | soup = self.soup(markup)
213 | encoded = soup.b.encode("utf-8")
214 | assert encoded == markup.encode('utf-8')
215 |
216 | def test_encoding_substitution(self):
217 | # Here's the <meta> tag saying that a document is
218 | # encoded in Shift-JIS.
219 | meta_tag = ('<meta content="text/html; charset=x-sjis" '
220 | 'http-equiv="Content-type"/>')
221 | soup = self.soup(meta_tag)
222 |
223 | # Parse the document, and the charset apprears unchanged.
224 | assert soup.meta['content'] == 'text/html; charset=x-sjis'
225 |
226 | # Encode the document into some encoding, and the encoding is
227 | # substituted into the meta tag.
228 | utf_8 = soup.encode("utf-8")
229 | assert b"charset=utf-8" in utf_8
230 |
231 | euc_jp = soup.encode("euc_jp")
232 | assert b"charset=euc_jp" in euc_jp
233 |
234 | shift_jis = soup.encode("shift-jis")
235 | assert b"charset=shift-jis" in shift_jis
236 |
237 | utf_16_u = soup.encode("utf-16").decode("utf-16")
238 | assert "charset=utf-16" in utf_16_u
239 |
240 | def test_encoding_substitution_doesnt_happen_if_tag_is_strained(self):
241 | markup = ('<head><meta content="text/html; charset=x-sjis" '
242 | 'http-equiv="Content-type"/></head><pre>foo</pre>')
243 |
244 | # Beautiful Soup used to try to rewrite the meta tag even if the
245 | # meta tag got filtered out by the strainer. This test makes
246 | # sure that doesn't happen.
247 | strainer = SoupStrainer('pre')
248 | soup = self.soup(markup, parse_only=strainer)
249 | assert soup.contents[0].name == 'pre'
250 |
251 |
252 | class TestPersistence(SoupTest):
253 | "Testing features like pickle and deepcopy."
254 |
255 | def setup_method(self):
256 | self.page = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
257 | "http://www.w3.org/TR/REC-html40/transitional.dtd">
258 | <html>
259 | <head>
260 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
261 | <title>Beautiful Soup: We called him Tortoise because he taught us.</title>
262 | <link rev="made" href="mailto:[email protected]">
263 | <meta name="Description" content="Beautiful Soup: an HTML parser optimized for screen-scraping.">
264 | <meta name="generator" content="Markov Approximation 1.4 (module: leonardr)">
265 | <meta name="author" content="Leonard Richardson">
266 | </head>
267 | <body>
268 | <a href="foo">foo</a>
269 | <a href="foo"><b>bar</b></a>
270 | </body>
271 | </html>"""
272 | self.tree = self.soup(self.page)
273 |
274 | def test_pickle_and_unpickle_identity(self):
275 | # Pickling a tree, then unpickling it, yields a tree identical
276 | # to the original.
277 | dumped = pickle.dumps(self.tree, 2)
278 | loaded = pickle.loads(dumped)
279 | assert loaded.__class__ == BeautifulSoup
280 | assert loaded.decode() == self.tree.decode()
281 |
282 | def test_deepcopy_identity(self):
283 | # Making a deepcopy of a tree yields an identical tree.
284 | copied = copy.deepcopy(self.tree)
285 | assert copied.decode() == self.tree.decode()
286 |
287 | def test_copy_deeply_nested_document(self):
288 | # This test verifies that copy and deepcopy don't involve any
289 | # recursive function calls. If they did, this test would
290 | # overflow the Python interpreter stack.
291 | limit = sys.getrecursionlimit() + 1
292 | markup = "<span>" * limit
293 |
294 | soup = self.soup(markup)
295 |
296 | copied = copy.copy(soup)
297 | copied = copy.deepcopy(soup)
298 |
299 | def test_copy_preserves_encoding(self):
300 | soup = BeautifulSoup(b'<p> </p>', 'html.parser')
301 | encoding = soup.original_encoding
302 | copy = soup.__copy__()
303 | assert "<p> </p>" == str(copy)
304 | assert encoding == copy.original_encoding
305 |
306 | def test_copy_preserves_builder_information(self):
307 |
308 | tag = self.soup('<p></p>').p
309 |
310 | # Simulate a tag obtained from a source file.
311 | tag.sourceline = 10
312 | tag.sourcepos = 33
313 |
314 | copied = tag.__copy__()
315 |
316 | # The TreeBuilder object is no longer availble, but information
317 | # obtained from it gets copied over to the new Tag object.
318 | assert tag.sourceline == copied.sourceline
319 | assert tag.sourcepos == copied.sourcepos
320 | assert tag.can_be_empty_element == copied.can_be_empty_element
321 | assert tag.cdata_list_attributes == copied.cdata_list_attributes
322 | assert tag.preserve_whitespace_tags == copied.preserve_whitespace_tags
323 | assert tag.interesting_string_types == copied.interesting_string_types
324 |
325 | def test_unicode_pickle(self):
326 | # A tree containing Unicode characters can be pickled.
327 | html = "<b>\N{SNOWMAN}</b>"
328 | soup = self.soup(html)
329 | dumped = pickle.dumps(soup, pickle.HIGHEST_PROTOCOL)
330 | loaded = pickle.loads(dumped)
331 | assert loaded.decode() == soup.decode()
332 |
333 | def test_copy_navigablestring_is_not_attached_to_tree(self):
334 | html = "<b>Foo<a></a></b><b>Bar</b>"
335 | soup = self.soup(html)
336 | s1 = soup.find(string="Foo")
337 | s2 = copy.copy(s1)
338 | assert s1 == s2
339 | assert None == s2.parent
340 | assert None == s2.next_element
341 | assert None != s1.next_sibling
342 | assert None == s2.next_sibling
343 | assert None == s2.previous_element
344 |
345 | def test_copy_navigablestring_subclass_has_same_type(self):
346 | html = "<b><!--Foo--></b>"
347 | soup = self.soup(html)
348 | s1 = soup.string
349 | s2 = copy.copy(s1)
350 | assert s1 == s2
351 | assert isinstance(s2, Comment)
352 |
353 | def test_copy_entire_soup(self):
354 | html = "<div><b>Foo<a></a></b><b>Bar</b></div>end"
355 | soup = self.soup(html)
356 | soup_copy = copy.copy(soup)
357 | assert soup == soup_copy
358 |
359 | def test_copy_tag_copies_contents(self):
360 | html = "<div><b>Foo<a></a></b><b>Bar</b></div>end"
361 | soup = self.soup(html)
362 | div = soup.div
363 | div_copy = copy.copy(div)
364 |
365 | # The two tags look the same, and evaluate to equal.
366 | assert str(div) == str(div_copy)
367 | assert div == div_copy
368 |
369 | # But they're not the same object.
370 | assert div is not div_copy
371 |
372 | # And they don't have the same relation to the parse tree. The
373 | # copy is not associated with a parse tree at all.
374 | assert None == div_copy.parent
375 | assert None == div_copy.previous_element
376 | assert None == div_copy.find(string='Bar').next_element
377 | assert None != div.find(string='Bar').next_element
378 |
379 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/pip/_vendor/rich/live.py:
--------------------------------------------------------------------------------
```python
1 | import sys
2 | from threading import Event, RLock, Thread
3 | from types import TracebackType
4 | from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast
5 |
6 | from . import get_console
7 | from .console import Console, ConsoleRenderable, RenderableType, RenderHook
8 | from .control import Control
9 | from .file_proxy import FileProxy
10 | from .jupyter import JupyterMixin
11 | from .live_render import LiveRender, VerticalOverflowMethod
12 | from .screen import Screen
13 | from .text import Text
14 |
15 |
16 | class _RefreshThread(Thread):
17 | """A thread that calls refresh() at regular intervals."""
18 |
19 | def __init__(self, live: "Live", refresh_per_second: float) -> None:
20 | self.live = live
21 | self.refresh_per_second = refresh_per_second
22 | self.done = Event()
23 | super().__init__(daemon=True)
24 |
25 | def stop(self) -> None:
26 | self.done.set()
27 |
28 | def run(self) -> None:
29 | while not self.done.wait(1 / self.refresh_per_second):
30 | with self.live._lock:
31 | if not self.done.is_set():
32 | self.live.refresh()
33 |
34 |
35 | class Live(JupyterMixin, RenderHook):
36 | """Renders an auto-updating live display of any given renderable.
37 |
38 | Args:
39 | renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing.
40 | console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
41 | screen (bool, optional): Enable alternate screen mode. Defaults to False.
42 | auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
43 | refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4.
44 | transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False.
45 | redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
46 | redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True.
47 | vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis".
48 | get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None.
49 | """
50 |
51 | def __init__(
52 | self,
53 | renderable: Optional[RenderableType] = None,
54 | *,
55 | console: Optional[Console] = None,
56 | screen: bool = False,
57 | auto_refresh: bool = True,
58 | refresh_per_second: float = 4,
59 | transient: bool = False,
60 | redirect_stdout: bool = True,
61 | redirect_stderr: bool = True,
62 | vertical_overflow: VerticalOverflowMethod = "ellipsis",
63 | get_renderable: Optional[Callable[[], RenderableType]] = None,
64 | ) -> None:
65 | assert refresh_per_second > 0, "refresh_per_second must be > 0"
66 | self._renderable = renderable
67 | self.console = console if console is not None else get_console()
68 | self._screen = screen
69 | self._alt_screen = False
70 |
71 | self._redirect_stdout = redirect_stdout
72 | self._redirect_stderr = redirect_stderr
73 | self._restore_stdout: Optional[IO[str]] = None
74 | self._restore_stderr: Optional[IO[str]] = None
75 |
76 | self._lock = RLock()
77 | self.ipy_widget: Optional[Any] = None
78 | self.auto_refresh = auto_refresh
79 | self._started: bool = False
80 | self.transient = True if screen else transient
81 |
82 | self._refresh_thread: Optional[_RefreshThread] = None
83 | self.refresh_per_second = refresh_per_second
84 |
85 | self.vertical_overflow = vertical_overflow
86 | self._get_renderable = get_renderable
87 | self._live_render = LiveRender(
88 | self.get_renderable(), vertical_overflow=vertical_overflow
89 | )
90 |
91 | @property
92 | def is_started(self) -> bool:
93 | """Check if live display has been started."""
94 | return self._started
95 |
96 | def get_renderable(self) -> RenderableType:
97 | renderable = (
98 | self._get_renderable()
99 | if self._get_renderable is not None
100 | else self._renderable
101 | )
102 | return renderable or ""
103 |
104 | def start(self, refresh: bool = False) -> None:
105 | """Start live rendering display.
106 |
107 | Args:
108 | refresh (bool, optional): Also refresh. Defaults to False.
109 | """
110 | with self._lock:
111 | if self._started:
112 | return
113 | self.console.set_live(self)
114 | self._started = True
115 | if self._screen:
116 | self._alt_screen = self.console.set_alt_screen(True)
117 | self.console.show_cursor(False)
118 | self._enable_redirect_io()
119 | self.console.push_render_hook(self)
120 | if refresh:
121 | try:
122 | self.refresh()
123 | except Exception:
124 | # If refresh fails, we want to stop the redirection of sys.stderr,
125 | # so the error stacktrace is properly displayed in the terminal.
126 | # (or, if the code that calls Rich captures the exception and wants to display something,
127 | # let this be displayed in the terminal).
128 | self.stop()
129 | raise
130 | if self.auto_refresh:
131 | self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
132 | self._refresh_thread.start()
133 |
134 | def stop(self) -> None:
135 | """Stop live rendering display."""
136 | with self._lock:
137 | if not self._started:
138 | return
139 | self.console.clear_live()
140 | self._started = False
141 |
142 | if self.auto_refresh and self._refresh_thread is not None:
143 | self._refresh_thread.stop()
144 | self._refresh_thread = None
145 | # allow it to fully render on the last even if overflow
146 | self.vertical_overflow = "visible"
147 | with self.console:
148 | try:
149 | if not self._alt_screen and not self.console.is_jupyter:
150 | self.refresh()
151 | finally:
152 | self._disable_redirect_io()
153 | self.console.pop_render_hook()
154 | if not self._alt_screen and self.console.is_terminal:
155 | self.console.line()
156 | self.console.show_cursor(True)
157 | if self._alt_screen:
158 | self.console.set_alt_screen(False)
159 |
160 | if self.transient and not self._alt_screen:
161 | self.console.control(self._live_render.restore_cursor())
162 | if self.ipy_widget is not None and self.transient:
163 | self.ipy_widget.close() # pragma: no cover
164 |
165 | def __enter__(self) -> "Live":
166 | self.start(refresh=self._renderable is not None)
167 | return self
168 |
169 | def __exit__(
170 | self,
171 | exc_type: Optional[Type[BaseException]],
172 | exc_val: Optional[BaseException],
173 | exc_tb: Optional[TracebackType],
174 | ) -> None:
175 | self.stop()
176 |
177 | def _enable_redirect_io(self) -> None:
178 | """Enable redirecting of stdout / stderr."""
179 | if self.console.is_terminal or self.console.is_jupyter:
180 | if self._redirect_stdout and not isinstance(sys.stdout, FileProxy):
181 | self._restore_stdout = sys.stdout
182 | sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout))
183 | if self._redirect_stderr and not isinstance(sys.stderr, FileProxy):
184 | self._restore_stderr = sys.stderr
185 | sys.stderr = cast("TextIO", FileProxy(self.console, sys.stderr))
186 |
187 | def _disable_redirect_io(self) -> None:
188 | """Disable redirecting of stdout / stderr."""
189 | if self._restore_stdout:
190 | sys.stdout = cast("TextIO", self._restore_stdout)
191 | self._restore_stdout = None
192 | if self._restore_stderr:
193 | sys.stderr = cast("TextIO", self._restore_stderr)
194 | self._restore_stderr = None
195 |
196 | @property
197 | def renderable(self) -> RenderableType:
198 | """Get the renderable that is being displayed
199 |
200 | Returns:
201 | RenderableType: Displayed renderable.
202 | """
203 | renderable = self.get_renderable()
204 | return Screen(renderable) if self._alt_screen else renderable
205 |
206 | def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
207 | """Update the renderable that is being displayed
208 |
209 | Args:
210 | renderable (RenderableType): New renderable to use.
211 | refresh (bool, optional): Refresh the display. Defaults to False.
212 | """
213 | if isinstance(renderable, str):
214 | renderable = self.console.render_str(renderable)
215 | with self._lock:
216 | self._renderable = renderable
217 | if refresh:
218 | self.refresh()
219 |
220 | def refresh(self) -> None:
221 | """Update the display of the Live Render."""
222 | with self._lock:
223 | self._live_render.set_renderable(self.renderable)
224 | if self.console.is_jupyter: # pragma: no cover
225 | try:
226 | from IPython.display import display
227 | from ipywidgets import Output
228 | except ImportError:
229 | import warnings
230 |
231 | warnings.warn('install "ipywidgets" for Jupyter support')
232 | else:
233 | if self.ipy_widget is None:
234 | self.ipy_widget = Output()
235 | display(self.ipy_widget)
236 |
237 | with self.ipy_widget:
238 | self.ipy_widget.clear_output(wait=True)
239 | self.console.print(self._live_render.renderable)
240 | elif self.console.is_terminal and not self.console.is_dumb_terminal:
241 | with self.console:
242 | self.console.print(Control())
243 | elif (
244 | not self._started and not self.transient
245 | ): # if it is finished allow files or dumb-terminals to see final result
246 | with self.console:
247 | self.console.print(Control())
248 |
249 | def process_renderables(
250 | self, renderables: List[ConsoleRenderable]
251 | ) -> List[ConsoleRenderable]:
252 | """Process renderables to restore cursor and display progress."""
253 | self._live_render.vertical_overflow = self.vertical_overflow
254 | if self.console.is_interactive:
255 | # lock needs acquiring as user can modify live_render renderable at any time unlike in Progress.
256 | with self._lock:
257 | reset = (
258 | Control.home()
259 | if self._alt_screen
260 | else self._live_render.position_cursor()
261 | )
262 | renderables = [reset, *renderables, self._live_render]
263 | elif (
264 | not self._started and not self.transient
265 | ): # if it is finished render the final output for files or dumb_terminals
266 | renderables = [*renderables, self._live_render]
267 |
268 | return renderables
269 |
270 |
271 | if __name__ == "__main__": # pragma: no cover
272 | import random
273 | import time
274 | from itertools import cycle
275 | from typing import Dict, List, Tuple
276 |
277 | from .align import Align
278 | from .console import Console
279 | from .live import Live as Live
280 | from .panel import Panel
281 | from .rule import Rule
282 | from .syntax import Syntax
283 | from .table import Table
284 |
285 | console = Console()
286 |
287 | syntax = Syntax(
288 | '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
289 | """Iterate and generate a tuple with a flag for last value."""
290 | iter_values = iter(values)
291 | try:
292 | previous_value = next(iter_values)
293 | except StopIteration:
294 | return
295 | for value in iter_values:
296 | yield False, previous_value
297 | previous_value = value
298 | yield True, previous_value''',
299 | "python",
300 | line_numbers=True,
301 | )
302 |
303 | table = Table("foo", "bar", "baz")
304 | table.add_row("1", "2", "3")
305 |
306 | progress_renderables = [
307 | "You can make the terminal shorter and taller to see the live table hide"
308 | "Text may be printed while the progress bars are rendering.",
309 | Panel("In fact, [i]any[/i] renderable will work"),
310 | "Such as [magenta]tables[/]...",
311 | table,
312 | "Pretty printed structures...",
313 | {"type": "example", "text": "Pretty printed"},
314 | "Syntax...",
315 | syntax,
316 | Rule("Give it a try!"),
317 | ]
318 |
319 | examples = cycle(progress_renderables)
320 |
321 | exchanges = [
322 | "SGD",
323 | "MYR",
324 | "EUR",
325 | "USD",
326 | "AUD",
327 | "JPY",
328 | "CNH",
329 | "HKD",
330 | "CAD",
331 | "INR",
332 | "DKK",
333 | "GBP",
334 | "RUB",
335 | "NZD",
336 | "MXN",
337 | "IDR",
338 | "TWD",
339 | "THB",
340 | "VND",
341 | ]
342 | with Live(console=console) as live_table:
343 | exchange_rate_dict: Dict[Tuple[str, str], float] = {}
344 |
345 | for index in range(100):
346 | select_exchange = exchanges[index % len(exchanges)]
347 |
348 | for exchange in exchanges:
349 | if exchange == select_exchange:
350 | continue
351 | time.sleep(0.4)
352 | if random.randint(0, 10) < 1:
353 | console.log(next(examples))
354 | exchange_rate_dict[(select_exchange, exchange)] = 200 / (
355 | (random.random() * 320) + 1
356 | )
357 | if len(exchange_rate_dict) > len(exchanges) - 1:
358 | exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0])
359 | table = Table(title="Exchange Rates")
360 |
361 | table.add_column("Source Currency")
362 | table.add_column("Destination Currency")
363 | table.add_column("Exchange Rate")
364 |
365 | for (source, dest), exchange_rate in exchange_rate_dict.items():
366 | table.add_row(
367 | source,
368 | dest,
369 | Text(
370 | f"{exchange_rate:.4f}",
371 | style="red" if exchange_rate < 1.0 else "green",
372 | ),
373 | )
374 |
375 | live_table.update(Align.center(table))
376 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py:
--------------------------------------------------------------------------------
```python
1 | from __future__ import absolute_import
2 |
3 | import re
4 | from collections import namedtuple
5 |
6 | from ..exceptions import LocationParseError
7 | from ..packages import six
8 |
9 | url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"]
10 |
11 | # We only want to normalize urls with an HTTP(S) scheme.
12 | # urllib3 infers URLs without a scheme (None) to be http.
13 | NORMALIZABLE_SCHEMES = ("http", "https", None)
14 |
15 | # Almost all of these patterns were derived from the
16 | # 'rfc3986' module: https://github.com/python-hyper/rfc3986
17 | PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}")
18 | SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)")
19 | URI_RE = re.compile(
20 | r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?"
21 | r"(?://([^\\/?#]*))?"
22 | r"([^?#]*)"
23 | r"(?:\?([^#]*))?"
24 | r"(?:#(.*))?$",
25 | re.UNICODE | re.DOTALL,
26 | )
27 |
28 | IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
29 | HEX_PAT = "[0-9A-Fa-f]{1,4}"
30 | LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT)
31 | _subs = {"hex": HEX_PAT, "ls32": LS32_PAT}
32 | _variations = [
33 | # 6( h16 ":" ) ls32
34 | "(?:%(hex)s:){6}%(ls32)s",
35 | # "::" 5( h16 ":" ) ls32
36 | "::(?:%(hex)s:){5}%(ls32)s",
37 | # [ h16 ] "::" 4( h16 ":" ) ls32
38 | "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s",
39 | # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
40 | "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s",
41 | # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
42 | "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s",
43 | # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
44 | "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s",
45 | # [ *4( h16 ":" ) h16 ] "::" ls32
46 | "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s",
47 | # [ *5( h16 ":" ) h16 ] "::" h16
48 | "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s",
49 | # [ *6( h16 ":" ) h16 ] "::"
50 | "(?:(?:%(hex)s:){0,6}%(hex)s)?::",
51 | ]
52 |
53 | UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~"
54 | IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")"
55 | ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+"
56 | IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]"
57 | REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*"
58 | TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$")
59 |
60 | IPV4_RE = re.compile("^" + IPV4_PAT + "$")
61 | IPV6_RE = re.compile("^" + IPV6_PAT + "$")
62 | IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$")
63 | BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$")
64 | ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$")
65 |
66 | _HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % (
67 | REG_NAME_PAT,
68 | IPV4_PAT,
69 | IPV6_ADDRZ_PAT,
70 | )
71 | _HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL)
72 |
73 | UNRESERVED_CHARS = set(
74 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~"
75 | )
76 | SUB_DELIM_CHARS = set("!$&'()*+,;=")
77 | USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"}
78 | PATH_CHARS = USERINFO_CHARS | {"@", "/"}
79 | QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"}
80 |
81 |
82 | class Url(namedtuple("Url", url_attrs)):
83 | """
84 | Data structure for representing an HTTP URL. Used as a return value for
85 | :func:`parse_url`. Both the scheme and host are normalized as they are
86 | both case-insensitive according to RFC 3986.
87 | """
88 |
89 | __slots__ = ()
90 |
91 | def __new__(
92 | cls,
93 | scheme=None,
94 | auth=None,
95 | host=None,
96 | port=None,
97 | path=None,
98 | query=None,
99 | fragment=None,
100 | ):
101 | if path and not path.startswith("/"):
102 | path = "/" + path
103 | if scheme is not None:
104 | scheme = scheme.lower()
105 | return super(Url, cls).__new__(
106 | cls, scheme, auth, host, port, path, query, fragment
107 | )
108 |
109 | @property
110 | def hostname(self):
111 | """For backwards-compatibility with urlparse. We're nice like that."""
112 | return self.host
113 |
114 | @property
115 | def request_uri(self):
116 | """Absolute path including the query string."""
117 | uri = self.path or "/"
118 |
119 | if self.query is not None:
120 | uri += "?" + self.query
121 |
122 | return uri
123 |
124 | @property
125 | def netloc(self):
126 | """Network location including host and port"""
127 | if self.port:
128 | return "%s:%d" % (self.host, self.port)
129 | return self.host
130 |
131 | @property
132 | def url(self):
133 | """
134 | Convert self into a url
135 |
136 | This function should more or less round-trip with :func:`.parse_url`. The
137 | returned url may not be exactly the same as the url inputted to
138 | :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
139 | with a blank port will have : removed).
140 |
141 | Example: ::
142 |
143 | >>> U = parse_url('http://google.com/mail/')
144 | >>> U.url
145 | 'http://google.com/mail/'
146 | >>> Url('http', 'username:password', 'host.com', 80,
147 | ... '/path', 'query', 'fragment').url
148 | 'http://username:[email protected]:80/path?query#fragment'
149 | """
150 | scheme, auth, host, port, path, query, fragment = self
151 | url = u""
152 |
153 | # We use "is not None" we want things to happen with empty strings (or 0 port)
154 | if scheme is not None:
155 | url += scheme + u"://"
156 | if auth is not None:
157 | url += auth + u"@"
158 | if host is not None:
159 | url += host
160 | if port is not None:
161 | url += u":" + str(port)
162 | if path is not None:
163 | url += path
164 | if query is not None:
165 | url += u"?" + query
166 | if fragment is not None:
167 | url += u"#" + fragment
168 |
169 | return url
170 |
171 | def __str__(self):
172 | return self.url
173 |
174 |
175 | def split_first(s, delims):
176 | """
177 | .. deprecated:: 1.25
178 |
179 | Given a string and an iterable of delimiters, split on the first found
180 | delimiter. Return two split parts and the matched delimiter.
181 |
182 | If not found, then the first part is the full input string.
183 |
184 | Example::
185 |
186 | >>> split_first('foo/bar?baz', '?/=')
187 | ('foo', 'bar?baz', '/')
188 | >>> split_first('foo/bar?baz', '123')
189 | ('foo/bar?baz', '', None)
190 |
191 | Scales linearly with number of delims. Not ideal for large number of delims.
192 | """
193 | min_idx = None
194 | min_delim = None
195 | for d in delims:
196 | idx = s.find(d)
197 | if idx < 0:
198 | continue
199 |
200 | if min_idx is None or idx < min_idx:
201 | min_idx = idx
202 | min_delim = d
203 |
204 | if min_idx is None or min_idx < 0:
205 | return s, "", None
206 |
207 | return s[:min_idx], s[min_idx + 1 :], min_delim
208 |
209 |
210 | def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"):
211 | """Percent-encodes a URI component without reapplying
212 | onto an already percent-encoded component.
213 | """
214 | if component is None:
215 | return component
216 |
217 | component = six.ensure_text(component)
218 |
219 | # Normalize existing percent-encoded bytes.
220 | # Try to see if the component we're encoding is already percent-encoded
221 | # so we can skip all '%' characters but still encode all others.
222 | component, percent_encodings = PERCENT_RE.subn(
223 | lambda match: match.group(0).upper(), component
224 | )
225 |
226 | uri_bytes = component.encode("utf-8", "surrogatepass")
227 | is_percent_encoded = percent_encodings == uri_bytes.count(b"%")
228 | encoded_component = bytearray()
229 |
230 | for i in range(0, len(uri_bytes)):
231 | # Will return a single character bytestring on both Python 2 & 3
232 | byte = uri_bytes[i : i + 1]
233 | byte_ord = ord(byte)
234 | if (is_percent_encoded and byte == b"%") or (
235 | byte_ord < 128 and byte.decode() in allowed_chars
236 | ):
237 | encoded_component += byte
238 | continue
239 | encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper()))
240 |
241 | return encoded_component.decode(encoding)
242 |
243 |
244 | def _remove_path_dot_segments(path):
245 | # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code
246 | segments = path.split("/") # Turn the path into a list of segments
247 | output = [] # Initialize the variable to use to store output
248 |
249 | for segment in segments:
250 | # '.' is the current directory, so ignore it, it is superfluous
251 | if segment == ".":
252 | continue
253 | # Anything other than '..', should be appended to the output
254 | elif segment != "..":
255 | output.append(segment)
256 | # In this case segment == '..', if we can, we should pop the last
257 | # element
258 | elif output:
259 | output.pop()
260 |
261 | # If the path starts with '/' and the output is empty or the first string
262 | # is non-empty
263 | if path.startswith("/") and (not output or output[0]):
264 | output.insert(0, "")
265 |
266 | # If the path starts with '/.' or '/..' ensure we add one more empty
267 | # string to add a trailing '/'
268 | if path.endswith(("/.", "/..")):
269 | output.append("")
270 |
271 | return "/".join(output)
272 |
273 |
274 | def _normalize_host(host, scheme):
275 | if host:
276 | if isinstance(host, six.binary_type):
277 | host = six.ensure_str(host)
278 |
279 | if scheme in NORMALIZABLE_SCHEMES:
280 | is_ipv6 = IPV6_ADDRZ_RE.match(host)
281 | if is_ipv6:
282 | # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as
283 | # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID
284 | # separator as necessary to return a valid RFC 4007 scoped IP.
285 | match = ZONE_ID_RE.search(host)
286 | if match:
287 | start, end = match.span(1)
288 | zone_id = host[start:end]
289 |
290 | if zone_id.startswith("%25") and zone_id != "%25":
291 | zone_id = zone_id[3:]
292 | else:
293 | zone_id = zone_id[1:]
294 | zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS)
295 | return host[:start].lower() + zone_id + host[end:]
296 | else:
297 | return host.lower()
298 | elif not IPV4_RE.match(host):
299 | return six.ensure_str(
300 | b".".join([_idna_encode(label) for label in host.split(".")])
301 | )
302 | return host
303 |
304 |
305 | def _idna_encode(name):
306 | if name and any(ord(x) >= 128 for x in name):
307 | try:
308 | from pip._vendor import idna
309 | except ImportError:
310 | six.raise_from(
311 | LocationParseError("Unable to parse URL without the 'idna' module"),
312 | None,
313 | )
314 | try:
315 | return idna.encode(name.lower(), strict=True, std3_rules=True)
316 | except idna.IDNAError:
317 | six.raise_from(
318 | LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None
319 | )
320 | return name.lower().encode("ascii")
321 |
322 |
323 | def _encode_target(target):
324 | """Percent-encodes a request target so that there are no invalid characters"""
325 | path, query = TARGET_RE.match(target).groups()
326 | target = _encode_invalid_chars(path, PATH_CHARS)
327 | query = _encode_invalid_chars(query, QUERY_CHARS)
328 | if query is not None:
329 | target += "?" + query
330 | return target
331 |
332 |
333 | def parse_url(url):
334 | """
335 | Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
336 | performed to parse incomplete urls. Fields not provided will be None.
337 | This parser is RFC 3986 and RFC 6874 compliant.
338 |
339 | The parser logic and helper functions are based heavily on
340 | work done in the ``rfc3986`` module.
341 |
342 | :param str url: URL to parse into a :class:`.Url` namedtuple.
343 |
344 | Partly backwards-compatible with :mod:`urlparse`.
345 |
346 | Example::
347 |
348 | >>> parse_url('http://google.com/mail/')
349 | Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
350 | >>> parse_url('google.com:80')
351 | Url(scheme=None, host='google.com', port=80, path=None, ...)
352 | >>> parse_url('/foo?bar')
353 | Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
354 | """
355 | if not url:
356 | # Empty
357 | return Url()
358 |
359 | source_url = url
360 | if not SCHEME_RE.search(url):
361 | url = "//" + url
362 |
363 | try:
364 | scheme, authority, path, query, fragment = URI_RE.match(url).groups()
365 | normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES
366 |
367 | if scheme:
368 | scheme = scheme.lower()
369 |
370 | if authority:
371 | auth, _, host_port = authority.rpartition("@")
372 | auth = auth or None
373 | host, port = _HOST_PORT_RE.match(host_port).groups()
374 | if auth and normalize_uri:
375 | auth = _encode_invalid_chars(auth, USERINFO_CHARS)
376 | if port == "":
377 | port = None
378 | else:
379 | auth, host, port = None, None, None
380 |
381 | if port is not None:
382 | port = int(port)
383 | if not (0 <= port <= 65535):
384 | raise LocationParseError(url)
385 |
386 | host = _normalize_host(host, scheme)
387 |
388 | if normalize_uri and path:
389 | path = _remove_path_dot_segments(path)
390 | path = _encode_invalid_chars(path, PATH_CHARS)
391 | if normalize_uri and query:
392 | query = _encode_invalid_chars(query, QUERY_CHARS)
393 | if normalize_uri and fragment:
394 | fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS)
395 |
396 | except (ValueError, AttributeError):
397 | return six.raise_from(LocationParseError(source_url), None)
398 |
399 | # For the sake of backwards compatibility we put empty
400 | # string values for path if there are any defined values
401 | # beyond the path in the URL.
402 | # TODO: Remove this when we break backwards compatibility.
403 | if not path:
404 | if query is not None or fragment is not None:
405 | path = ""
406 | else:
407 | path = None
408 |
409 | # Ensure that each part of the URL is a `str` for
410 | # backwards compatibility.
411 | if isinstance(url, six.text_type):
412 | ensure_func = six.ensure_text
413 | else:
414 | ensure_func = six.ensure_str
415 |
416 | def ensure_type(x):
417 | return x if x is None else ensure_func(x)
418 |
419 | return Url(
420 | scheme=ensure_type(scheme),
421 | auth=ensure_type(auth),
422 | host=ensure_type(host),
423 | port=port,
424 | path=ensure_type(path),
425 | query=ensure_type(query),
426 | fragment=ensure_type(fragment),
427 | )
428 |
429 |
430 | def get_host(url):
431 | """
432 | Deprecated. Use :func:`parse_url` instead.
433 | """
434 | p = parse_url(url)
435 | return p.scheme or "http", p.hostname, p.port
436 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/werkzeug/middleware/lint.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | WSGI Protocol Linter
3 | ====================
4 |
5 | This module provides a middleware that performs sanity checks on the
6 | behavior of the WSGI server and application. It checks that the
7 | :pep:`3333` WSGI spec is properly implemented. It also warns on some
8 | common HTTP errors such as non-empty responses for 304 status codes.
9 |
10 | .. autoclass:: LintMiddleware
11 |
12 | :copyright: 2007 Pallets
13 | :license: BSD-3-Clause
14 | """
15 |
16 | from __future__ import annotations
17 |
18 | import typing as t
19 | from types import TracebackType
20 | from urllib.parse import urlparse
21 | from warnings import warn
22 |
23 | from ..datastructures import Headers
24 | from ..http import is_entity_header
25 | from ..wsgi import FileWrapper
26 |
27 | if t.TYPE_CHECKING:
28 | from _typeshed.wsgi import StartResponse
29 | from _typeshed.wsgi import WSGIApplication
30 | from _typeshed.wsgi import WSGIEnvironment
31 |
32 |
33 | class WSGIWarning(Warning):
34 | """Warning class for WSGI warnings."""
35 |
36 |
37 | class HTTPWarning(Warning):
38 | """Warning class for HTTP warnings."""
39 |
40 |
41 | def check_type(context: str, obj: object, need: type = str) -> None:
42 | if type(obj) is not need:
43 | warn(
44 | f"{context!r} requires {need.__name__!r}, got {type(obj).__name__!r}.",
45 | WSGIWarning,
46 | stacklevel=3,
47 | )
48 |
49 |
50 | class InputStream:
51 | def __init__(self, stream: t.IO[bytes]) -> None:
52 | self._stream = stream
53 |
54 | def read(self, *args: t.Any) -> bytes:
55 | if len(args) == 0:
56 | warn(
57 | "WSGI does not guarantee an EOF marker on the input stream, thus making"
58 | " calls to 'wsgi.input.read()' unsafe. Conforming servers may never"
59 | " return from this call.",
60 | WSGIWarning,
61 | stacklevel=2,
62 | )
63 | elif len(args) != 1:
64 | warn(
65 | "Too many parameters passed to 'wsgi.input.read()'.",
66 | WSGIWarning,
67 | stacklevel=2,
68 | )
69 | return self._stream.read(*args)
70 |
71 | def readline(self, *args: t.Any) -> bytes:
72 | if len(args) == 0:
73 | warn(
74 | "Calls to 'wsgi.input.readline()' without arguments are unsafe. Use"
75 | " 'wsgi.input.read()' instead.",
76 | WSGIWarning,
77 | stacklevel=2,
78 | )
79 | elif len(args) == 1:
80 | warn(
81 | "'wsgi.input.readline()' was called with a size hint. WSGI does not"
82 | " support this, although it's available on all major servers.",
83 | WSGIWarning,
84 | stacklevel=2,
85 | )
86 | else:
87 | raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.")
88 | return self._stream.readline(*args)
89 |
90 | def __iter__(self) -> t.Iterator[bytes]:
91 | try:
92 | return iter(self._stream)
93 | except TypeError:
94 | warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2)
95 | return iter(())
96 |
97 | def close(self) -> None:
98 | warn("The application closed the input stream!", WSGIWarning, stacklevel=2)
99 | self._stream.close()
100 |
101 |
102 | class ErrorStream:
103 | def __init__(self, stream: t.IO[str]) -> None:
104 | self._stream = stream
105 |
106 | def write(self, s: str) -> None:
107 | check_type("wsgi.error.write()", s, str)
108 | self._stream.write(s)
109 |
110 | def flush(self) -> None:
111 | self._stream.flush()
112 |
113 | def writelines(self, seq: t.Iterable[str]) -> None:
114 | for line in seq:
115 | self.write(line)
116 |
117 | def close(self) -> None:
118 | warn("The application closed the error stream!", WSGIWarning, stacklevel=2)
119 | self._stream.close()
120 |
121 |
122 | class GuardedWrite:
123 | def __init__(self, write: t.Callable[[bytes], object], chunks: list[int]) -> None:
124 | self._write = write
125 | self._chunks = chunks
126 |
127 | def __call__(self, s: bytes) -> None:
128 | check_type("write()", s, bytes)
129 | self._write(s)
130 | self._chunks.append(len(s))
131 |
132 |
133 | class GuardedIterator:
134 | def __init__(
135 | self,
136 | iterator: t.Iterable[bytes],
137 | headers_set: tuple[int, Headers],
138 | chunks: list[int],
139 | ) -> None:
140 | self._iterator = iterator
141 | self._next = iter(iterator).__next__
142 | self.closed = False
143 | self.headers_set = headers_set
144 | self.chunks = chunks
145 |
146 | def __iter__(self) -> GuardedIterator:
147 | return self
148 |
149 | def __next__(self) -> bytes:
150 | if self.closed:
151 | warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2)
152 |
153 | rv = self._next()
154 |
155 | if not self.headers_set:
156 | warn(
157 | "The application returned before it started the response.",
158 | WSGIWarning,
159 | stacklevel=2,
160 | )
161 |
162 | check_type("application iterator items", rv, bytes)
163 | self.chunks.append(len(rv))
164 | return rv
165 |
166 | def close(self) -> None:
167 | self.closed = True
168 |
169 | if hasattr(self._iterator, "close"):
170 | self._iterator.close()
171 |
172 | if self.headers_set:
173 | status_code, headers = self.headers_set
174 | bytes_sent = sum(self.chunks)
175 | content_length = headers.get("content-length", type=int)
176 |
177 | if status_code == 304:
178 | for key, _value in headers:
179 | key = key.lower()
180 | if key not in ("expires", "content-location") and is_entity_header(
181 | key
182 | ):
183 | warn(
184 | f"Entity header {key!r} found in 304 response.",
185 | HTTPWarning,
186 | stacklevel=2,
187 | )
188 | if bytes_sent:
189 | warn(
190 | "304 responses must not have a body.",
191 | HTTPWarning,
192 | stacklevel=2,
193 | )
194 | elif 100 <= status_code < 200 or status_code == 204:
195 | if content_length != 0:
196 | warn(
197 | f"{status_code} responses must have an empty content length.",
198 | HTTPWarning,
199 | stacklevel=2,
200 | )
201 | if bytes_sent:
202 | warn(
203 | f"{status_code} responses must not have a body.",
204 | HTTPWarning,
205 | stacklevel=2,
206 | )
207 | elif content_length is not None and content_length != bytes_sent:
208 | warn(
209 | "Content-Length and the number of bytes sent to the"
210 | " client do not match.",
211 | WSGIWarning,
212 | stacklevel=2,
213 | )
214 |
215 | def __del__(self) -> None:
216 | if not self.closed:
217 | try:
218 | warn(
219 | "Iterator was garbage collected before it was closed.",
220 | WSGIWarning,
221 | stacklevel=2,
222 | )
223 | except Exception:
224 | pass
225 |
226 |
227 | class LintMiddleware:
228 | """Warns about common errors in the WSGI and HTTP behavior of the
229 | server and wrapped application. Some of the issues it checks are:
230 |
231 | - invalid status codes
232 | - non-bytes sent to the WSGI server
233 | - strings returned from the WSGI application
234 | - non-empty conditional responses
235 | - unquoted etags
236 | - relative URLs in the Location header
237 | - unsafe calls to wsgi.input
238 | - unclosed iterators
239 |
240 | Error information is emitted using the :mod:`warnings` module.
241 |
242 | :param app: The WSGI application to wrap.
243 |
244 | .. code-block:: python
245 |
246 | from werkzeug.middleware.lint import LintMiddleware
247 | app = LintMiddleware(app)
248 | """
249 |
250 | def __init__(self, app: WSGIApplication) -> None:
251 | self.app = app
252 |
253 | def check_environ(self, environ: WSGIEnvironment) -> None:
254 | if type(environ) is not dict: # noqa: E721
255 | warn(
256 | "WSGI environment is not a standard Python dict.",
257 | WSGIWarning,
258 | stacklevel=4,
259 | )
260 | for key in (
261 | "REQUEST_METHOD",
262 | "SERVER_NAME",
263 | "SERVER_PORT",
264 | "wsgi.version",
265 | "wsgi.input",
266 | "wsgi.errors",
267 | "wsgi.multithread",
268 | "wsgi.multiprocess",
269 | "wsgi.run_once",
270 | ):
271 | if key not in environ:
272 | warn(
273 | f"Required environment key {key!r} not found",
274 | WSGIWarning,
275 | stacklevel=3,
276 | )
277 | if environ["wsgi.version"] != (1, 0):
278 | warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3)
279 |
280 | script_name = environ.get("SCRIPT_NAME", "")
281 | path_info = environ.get("PATH_INFO", "")
282 |
283 | if script_name and script_name[0] != "/":
284 | warn(
285 | f"'SCRIPT_NAME' does not start with a slash: {script_name!r}",
286 | WSGIWarning,
287 | stacklevel=3,
288 | )
289 |
290 | if path_info and path_info[0] != "/":
291 | warn(
292 | f"'PATH_INFO' does not start with a slash: {path_info!r}",
293 | WSGIWarning,
294 | stacklevel=3,
295 | )
296 |
297 | def check_start_response(
298 | self,
299 | status: str,
300 | headers: list[tuple[str, str]],
301 | exc_info: None | (tuple[type[BaseException], BaseException, TracebackType]),
302 | ) -> tuple[int, Headers]:
303 | check_type("status", status, str)
304 | status_code_str = status.split(None, 1)[0]
305 |
306 | if len(status_code_str) != 3 or not status_code_str.isdecimal():
307 | warn("Status code must be three digits.", WSGIWarning, stacklevel=3)
308 |
309 | if len(status) < 4 or status[3] != " ":
310 | warn(
311 | f"Invalid value for status {status!r}. Valid status strings are three"
312 | " digits, a space and a status explanation.",
313 | WSGIWarning,
314 | stacklevel=3,
315 | )
316 |
317 | status_code = int(status_code_str)
318 |
319 | if status_code < 100:
320 | warn("Status code < 100 detected.", WSGIWarning, stacklevel=3)
321 |
322 | if type(headers) is not list: # noqa: E721
323 | warn("Header list is not a list.", WSGIWarning, stacklevel=3)
324 |
325 | for item in headers:
326 | if type(item) is not tuple or len(item) != 2:
327 | warn("Header items must be 2-item tuples.", WSGIWarning, stacklevel=3)
328 | name, value = item
329 | if type(name) is not str or type(value) is not str: # noqa: E721
330 | warn(
331 | "Header keys and values must be strings.", WSGIWarning, stacklevel=3
332 | )
333 | if name.lower() == "status":
334 | warn(
335 | "The status header is not supported due to"
336 | " conflicts with the CGI spec.",
337 | WSGIWarning,
338 | stacklevel=3,
339 | )
340 |
341 | if exc_info is not None and not isinstance(exc_info, tuple):
342 | warn("Invalid value for exc_info.", WSGIWarning, stacklevel=3)
343 |
344 | headers_obj = Headers(headers)
345 | self.check_headers(headers_obj)
346 |
347 | return status_code, headers_obj
348 |
349 | def check_headers(self, headers: Headers) -> None:
350 | etag = headers.get("etag")
351 |
352 | if etag is not None:
353 | if etag.startswith(("W/", "w/")):
354 | if etag.startswith("w/"):
355 | warn(
356 | "Weak etag indicator should be upper case.",
357 | HTTPWarning,
358 | stacklevel=4,
359 | )
360 |
361 | etag = etag[2:]
362 |
363 | if not (etag[:1] == etag[-1:] == '"'):
364 | warn("Unquoted etag emitted.", HTTPWarning, stacklevel=4)
365 |
366 | location = headers.get("location")
367 |
368 | if location is not None:
369 | if not urlparse(location).netloc:
370 | warn(
371 | "Absolute URLs required for location header.",
372 | HTTPWarning,
373 | stacklevel=4,
374 | )
375 |
376 | def check_iterator(self, app_iter: t.Iterable[bytes]) -> None:
377 | if isinstance(app_iter, str):
378 | warn(
379 | "The application returned a string. The response will send one"
380 | " character at a time to the client, which will kill performance."
381 | " Return a list or iterable instead.",
382 | WSGIWarning,
383 | stacklevel=3,
384 | )
385 |
386 | def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Iterable[bytes]:
387 | if len(args) != 2:
388 | warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2)
389 |
390 | if kwargs:
391 | warn(
392 | "A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2
393 | )
394 |
395 | environ: WSGIEnvironment = args[0]
396 | start_response: StartResponse = args[1]
397 |
398 | self.check_environ(environ)
399 | environ["wsgi.input"] = InputStream(environ["wsgi.input"])
400 | environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"])
401 |
402 | # Hook our own file wrapper in so that applications will always
403 | # iterate to the end and we can check the content length.
404 | environ["wsgi.file_wrapper"] = FileWrapper
405 |
406 | headers_set: list[t.Any] = []
407 | chunks: list[int] = []
408 |
409 | def checking_start_response(
410 | *args: t.Any, **kwargs: t.Any
411 | ) -> t.Callable[[bytes], None]:
412 | if len(args) not in {2, 3}:
413 | warn(
414 | f"Invalid number of arguments: {len(args)}, expected 2 or 3.",
415 | WSGIWarning,
416 | stacklevel=2,
417 | )
418 |
419 | if kwargs:
420 | warn(
421 | "'start_response' does not take keyword arguments.",
422 | WSGIWarning,
423 | stacklevel=2,
424 | )
425 |
426 | status: str = args[0]
427 | headers: list[tuple[str, str]] = args[1]
428 | exc_info: (
429 | None | (tuple[type[BaseException], BaseException, TracebackType])
430 | ) = args[2] if len(args) == 3 else None
431 |
432 | headers_set[:] = self.check_start_response(status, headers, exc_info)
433 | return GuardedWrite(start_response(status, headers, exc_info), chunks)
434 |
435 | app_iter = self.app(environ, t.cast("StartResponse", checking_start_response))
436 | self.check_iterator(app_iter)
437 | return GuardedIterator(
438 | app_iter, t.cast(t.Tuple[int, Headers], headers_set), chunks
439 | )
440 |
```