This is page 67 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/urllib3/contrib/pyopenssl.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Module for using pyOpenSSL as a TLS backend. This module was relevant before
3 | the standard library ``ssl`` module supported SNI, but now that we've dropped
4 | support for Python 2.7 all relevant Python versions support SNI so
5 | **this module is no longer recommended**.
6 |
7 | This needs the following packages installed:
8 |
9 | * `pyOpenSSL`_ (tested with 16.0.0)
10 | * `cryptography`_ (minimum 1.3.4, from pyopenssl)
11 | * `idna`_ (minimum 2.0)
12 |
13 | However, pyOpenSSL depends on cryptography, so while we use all three directly here we
14 | end up having relatively few packages required.
15 |
16 | You can install them with the following command:
17 |
18 | .. code-block:: bash
19 |
20 | $ python -m pip install pyopenssl cryptography idna
21 |
22 | To activate certificate checking, call
23 | :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code
24 | before you begin making HTTP requests. This can be done in a ``sitecustomize``
25 | module, or at any other time before your application begins using ``urllib3``,
26 | like this:
27 |
28 | .. code-block:: python
29 |
30 | try:
31 | import urllib3.contrib.pyopenssl
32 | urllib3.contrib.pyopenssl.inject_into_urllib3()
33 | except ImportError:
34 | pass
35 |
36 | .. _pyopenssl: https://www.pyopenssl.org
37 | .. _cryptography: https://cryptography.io
38 | .. _idna: https://github.com/kjd/idna
39 | """
40 |
41 | from __future__ import annotations
42 |
43 | import OpenSSL.SSL # type: ignore[import-untyped]
44 | from cryptography import x509
45 |
46 | try:
47 | from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined]
48 | except ImportError:
49 | # UnsupportedExtension is gone in cryptography >= 2.1.0
50 | class UnsupportedExtension(Exception): # type: ignore[no-redef]
51 | pass
52 |
53 |
54 | import logging
55 | import ssl
56 | import typing
57 | from io import BytesIO
58 | from socket import socket as socket_cls
59 | from socket import timeout
60 |
61 | from .. import util
62 |
63 | if typing.TYPE_CHECKING:
64 | from OpenSSL.crypto import X509 # type: ignore[import-untyped]
65 |
66 |
67 | __all__ = ["inject_into_urllib3", "extract_from_urllib3"]
68 |
69 | # Map from urllib3 to PyOpenSSL compatible parameter-values.
70 | _openssl_versions: dict[int, int] = {
71 | util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined]
72 | util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined]
73 | ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
74 | }
75 |
76 | if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"):
77 | _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
78 |
79 | if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"):
80 | _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
81 |
82 |
83 | _stdlib_to_openssl_verify = {
84 | ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
85 | ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
86 | ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
87 | + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
88 | }
89 | _openssl_to_stdlib_verify = {v: k for k, v in _stdlib_to_openssl_verify.items()}
90 |
91 | # The SSLvX values are the most likely to be missing in the future
92 | # but we check them all just to be sure.
93 | _OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr(
94 | OpenSSL.SSL, "OP_NO_SSLv3", 0
95 | )
96 | _OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0)
97 | _OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0)
98 | _OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0)
99 | _OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0)
100 |
101 | _openssl_to_ssl_minimum_version: dict[int, int] = {
102 | ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3,
103 | ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3,
104 | ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1,
105 | ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1,
106 | ssl.TLSVersion.TLSv1_3: (
107 | _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2
108 | ),
109 | ssl.TLSVersion.MAXIMUM_SUPPORTED: (
110 | _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2
111 | ),
112 | }
113 | _openssl_to_ssl_maximum_version: dict[int, int] = {
114 | ssl.TLSVersion.MINIMUM_SUPPORTED: (
115 | _OP_NO_SSLv2_OR_SSLv3
116 | | _OP_NO_TLSv1
117 | | _OP_NO_TLSv1_1
118 | | _OP_NO_TLSv1_2
119 | | _OP_NO_TLSv1_3
120 | ),
121 | ssl.TLSVersion.TLSv1: (
122 | _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3
123 | ),
124 | ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3,
125 | ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3,
126 | ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3,
127 | ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3,
128 | }
129 |
130 | # OpenSSL will only write 16K at a time
131 | SSL_WRITE_BLOCKSIZE = 16384
132 |
133 | orig_util_SSLContext = util.ssl_.SSLContext
134 |
135 |
136 | log = logging.getLogger(__name__)
137 |
138 |
139 | def inject_into_urllib3() -> None:
140 | "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support."
141 |
142 | _validate_dependencies_met()
143 |
144 | util.SSLContext = PyOpenSSLContext # type: ignore[assignment]
145 | util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment]
146 | util.IS_PYOPENSSL = True
147 | util.ssl_.IS_PYOPENSSL = True
148 |
149 |
150 | def extract_from_urllib3() -> None:
151 | "Undo monkey-patching by :func:`inject_into_urllib3`."
152 |
153 | util.SSLContext = orig_util_SSLContext
154 | util.ssl_.SSLContext = orig_util_SSLContext
155 | util.IS_PYOPENSSL = False
156 | util.ssl_.IS_PYOPENSSL = False
157 |
158 |
159 | def _validate_dependencies_met() -> None:
160 | """
161 | Verifies that PyOpenSSL's package-level dependencies have been met.
162 | Throws `ImportError` if they are not met.
163 | """
164 | # Method added in `cryptography==1.1`; not available in older versions
165 | from cryptography.x509.extensions import Extensions
166 |
167 | if getattr(Extensions, "get_extension_for_class", None) is None:
168 | raise ImportError(
169 | "'cryptography' module missing required functionality. "
170 | "Try upgrading to v1.3.4 or newer."
171 | )
172 |
173 | # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509
174 | # attribute is only present on those versions.
175 | from OpenSSL.crypto import X509
176 |
177 | x509 = X509()
178 | if getattr(x509, "_x509", None) is None:
179 | raise ImportError(
180 | "'pyOpenSSL' module missing required functionality. "
181 | "Try upgrading to v0.14 or newer."
182 | )
183 |
184 |
185 | def _dnsname_to_stdlib(name: str) -> str | None:
186 | """
187 | Converts a dNSName SubjectAlternativeName field to the form used by the
188 | standard library on the given Python version.
189 |
190 | Cryptography produces a dNSName as a unicode string that was idna-decoded
191 | from ASCII bytes. We need to idna-encode that string to get it back, and
192 | then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib
193 | uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).
194 |
195 | If the name cannot be idna-encoded then we return None signalling that
196 | the name given should be skipped.
197 | """
198 |
199 | def idna_encode(name: str) -> bytes | None:
200 | """
201 | Borrowed wholesale from the Python Cryptography Project. It turns out
202 | that we can't just safely call `idna.encode`: it can explode for
203 | wildcard names. This avoids that problem.
204 | """
205 | import idna
206 |
207 | try:
208 | for prefix in ["*.", "."]:
209 | if name.startswith(prefix):
210 | name = name[len(prefix) :]
211 | return prefix.encode("ascii") + idna.encode(name)
212 | return idna.encode(name)
213 | except idna.core.IDNAError:
214 | return None
215 |
216 | # Don't send IPv6 addresses through the IDNA encoder.
217 | if ":" in name:
218 | return name
219 |
220 | encoded_name = idna_encode(name)
221 | if encoded_name is None:
222 | return None
223 | return encoded_name.decode("utf-8")
224 |
225 |
226 | def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]:
227 | """
228 | Given an PyOpenSSL certificate, provides all the subject alternative names.
229 | """
230 | cert = peer_cert.to_cryptography()
231 |
232 | # We want to find the SAN extension. Ask Cryptography to locate it (it's
233 | # faster than looping in Python)
234 | try:
235 | ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
236 | except x509.ExtensionNotFound:
237 | # No such extension, return the empty list.
238 | return []
239 | except (
240 | x509.DuplicateExtension,
241 | UnsupportedExtension,
242 | x509.UnsupportedGeneralNameType,
243 | UnicodeError,
244 | ) as e:
245 | # A problem has been found with the quality of the certificate. Assume
246 | # no SAN field is present.
247 | log.warning(
248 | "A problem was encountered with the certificate that prevented "
249 | "urllib3 from finding the SubjectAlternativeName field. This can "
250 | "affect certificate validation. The error was %s",
251 | e,
252 | )
253 | return []
254 |
255 | # We want to return dNSName and iPAddress fields. We need to cast the IPs
256 | # back to strings because the match_hostname function wants them as
257 | # strings.
258 | # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8
259 | # decoded. This is pretty frustrating, but that's what the standard library
260 | # does with certificates, and so we need to attempt to do the same.
261 | # We also want to skip over names which cannot be idna encoded.
262 | names = [
263 | ("DNS", name)
264 | for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName))
265 | if name is not None
266 | ]
267 | names.extend(
268 | ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress)
269 | )
270 |
271 | return names
272 |
273 |
274 | class WrappedSocket:
275 | """API-compatibility wrapper for Python OpenSSL's Connection-class."""
276 |
277 | def __init__(
278 | self,
279 | connection: OpenSSL.SSL.Connection,
280 | socket: socket_cls,
281 | suppress_ragged_eofs: bool = True,
282 | ) -> None:
283 | self.connection = connection
284 | self.socket = socket
285 | self.suppress_ragged_eofs = suppress_ragged_eofs
286 | self._io_refs = 0
287 | self._closed = False
288 |
289 | def fileno(self) -> int:
290 | return self.socket.fileno()
291 |
292 | # Copy-pasted from Python 3.5 source code
293 | def _decref_socketios(self) -> None:
294 | if self._io_refs > 0:
295 | self._io_refs -= 1
296 | if self._closed:
297 | self.close()
298 |
299 | def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes:
300 | try:
301 | data = self.connection.recv(*args, **kwargs)
302 | except OpenSSL.SSL.SysCallError as e:
303 | if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):
304 | return b""
305 | else:
306 | raise OSError(e.args[0], str(e)) from e
307 | except OpenSSL.SSL.ZeroReturnError:
308 | if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
309 | return b""
310 | else:
311 | raise
312 | except OpenSSL.SSL.WantReadError as e:
313 | if not util.wait_for_read(self.socket, self.socket.gettimeout()):
314 | raise timeout("The read operation timed out") from e
315 | else:
316 | return self.recv(*args, **kwargs)
317 |
318 | # TLS 1.3 post-handshake authentication
319 | except OpenSSL.SSL.Error as e:
320 | raise ssl.SSLError(f"read error: {e!r}") from e
321 | else:
322 | return data # type: ignore[no-any-return]
323 |
324 | def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int:
325 | try:
326 | return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return]
327 | except OpenSSL.SSL.SysCallError as e:
328 | if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):
329 | return 0
330 | else:
331 | raise OSError(e.args[0], str(e)) from e
332 | except OpenSSL.SSL.ZeroReturnError:
333 | if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
334 | return 0
335 | else:
336 | raise
337 | except OpenSSL.SSL.WantReadError as e:
338 | if not util.wait_for_read(self.socket, self.socket.gettimeout()):
339 | raise timeout("The read operation timed out") from e
340 | else:
341 | return self.recv_into(*args, **kwargs)
342 |
343 | # TLS 1.3 post-handshake authentication
344 | except OpenSSL.SSL.Error as e:
345 | raise ssl.SSLError(f"read error: {e!r}") from e
346 |
347 | def settimeout(self, timeout: float) -> None:
348 | return self.socket.settimeout(timeout)
349 |
350 | def _send_until_done(self, data: bytes) -> int:
351 | while True:
352 | try:
353 | return self.connection.send(data) # type: ignore[no-any-return]
354 | except OpenSSL.SSL.WantWriteError as e:
355 | if not util.wait_for_write(self.socket, self.socket.gettimeout()):
356 | raise timeout() from e
357 | continue
358 | except OpenSSL.SSL.SysCallError as e:
359 | raise OSError(e.args[0], str(e)) from e
360 |
361 | def sendall(self, data: bytes) -> None:
362 | total_sent = 0
363 | while total_sent < len(data):
364 | sent = self._send_until_done(
365 | data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]
366 | )
367 | total_sent += sent
368 |
369 | def shutdown(self) -> None:
370 | # FIXME rethrow compatible exceptions should we ever use this
371 | self.connection.shutdown()
372 |
373 | def close(self) -> None:
374 | self._closed = True
375 | if self._io_refs <= 0:
376 | self._real_close()
377 |
378 | def _real_close(self) -> None:
379 | try:
380 | return self.connection.close() # type: ignore[no-any-return]
381 | except OpenSSL.SSL.Error:
382 | return
383 |
384 | def getpeercert(
385 | self, binary_form: bool = False
386 | ) -> dict[str, list[typing.Any]] | None:
387 | x509 = self.connection.get_peer_certificate()
388 |
389 | if not x509:
390 | return x509 # type: ignore[no-any-return]
391 |
392 | if binary_form:
393 | return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return]
394 |
395 | return {
396 | "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item]
397 | "subjectAltName": get_subj_alt_name(x509),
398 | }
399 |
400 | def version(self) -> str:
401 | return self.connection.get_protocol_version_name() # type: ignore[no-any-return]
402 |
403 | def selected_alpn_protocol(self) -> str | None:
404 | alpn_proto = self.connection.get_alpn_proto_negotiated()
405 | return alpn_proto.decode() if alpn_proto else None
406 |
407 |
408 | WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined]
409 |
410 |
411 | class PyOpenSSLContext:
412 | """
413 | I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible
414 | for translating the interface of the standard library ``SSLContext`` object
415 | to calls into PyOpenSSL.
416 | """
417 |
418 | def __init__(self, protocol: int) -> None:
419 | self.protocol = _openssl_versions[protocol]
420 | self._ctx = OpenSSL.SSL.Context(self.protocol)
421 | self._options = 0
422 | self.check_hostname = False
423 | self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED
424 | self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED
425 |
426 | @property
427 | def options(self) -> int:
428 | return self._options
429 |
430 | @options.setter
431 | def options(self, value: int) -> None:
432 | self._options = value
433 | self._set_ctx_options()
434 |
435 | @property
436 | def verify_mode(self) -> int:
437 | return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()]
438 |
439 | @verify_mode.setter
440 | def verify_mode(self, value: ssl.VerifyMode) -> None:
441 | self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback)
442 |
443 | def set_default_verify_paths(self) -> None:
444 | self._ctx.set_default_verify_paths()
445 |
446 | def set_ciphers(self, ciphers: bytes | str) -> None:
447 | if isinstance(ciphers, str):
448 | ciphers = ciphers.encode("utf-8")
449 | self._ctx.set_cipher_list(ciphers)
450 |
451 | def load_verify_locations(
452 | self,
453 | cafile: str | None = None,
454 | capath: str | None = None,
455 | cadata: bytes | None = None,
456 | ) -> None:
457 | if cafile is not None:
458 | cafile = cafile.encode("utf-8") # type: ignore[assignment]
459 | if capath is not None:
460 | capath = capath.encode("utf-8") # type: ignore[assignment]
461 | try:
462 | self._ctx.load_verify_locations(cafile, capath)
463 | if cadata is not None:
464 | self._ctx.load_verify_locations(BytesIO(cadata))
465 | except OpenSSL.SSL.Error as e:
466 | raise ssl.SSLError(f"unable to load trusted certificates: {e!r}") from e
467 |
468 | def load_cert_chain(
469 | self,
470 | certfile: str,
471 | keyfile: str | None = None,
472 | password: str | None = None,
473 | ) -> None:
474 | try:
475 | self._ctx.use_certificate_chain_file(certfile)
476 | if password is not None:
477 | if not isinstance(password, bytes):
478 | password = password.encode("utf-8") # type: ignore[assignment]
479 | self._ctx.set_passwd_cb(lambda *_: password)
480 | self._ctx.use_privatekey_file(keyfile or certfile)
481 | except OpenSSL.SSL.Error as e:
482 | raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e
483 |
484 | def set_alpn_protocols(self, protocols: list[bytes | str]) -> None:
485 | protocols = [util.util.to_bytes(p, "ascii") for p in protocols]
486 | return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return]
487 |
488 | def wrap_socket(
489 | self,
490 | sock: socket_cls,
491 | server_side: bool = False,
492 | do_handshake_on_connect: bool = True,
493 | suppress_ragged_eofs: bool = True,
494 | server_hostname: bytes | str | None = None,
495 | ) -> WrappedSocket:
496 | cnx = OpenSSL.SSL.Connection(self._ctx, sock)
497 |
498 | # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3
499 | if server_hostname and not util.ssl_.is_ipaddress(server_hostname):
500 | if isinstance(server_hostname, str):
501 | server_hostname = server_hostname.encode("utf-8")
502 | cnx.set_tlsext_host_name(server_hostname)
503 |
504 | cnx.set_connect_state()
505 |
506 | while True:
507 | try:
508 | cnx.do_handshake()
509 | except OpenSSL.SSL.WantReadError as e:
510 | if not util.wait_for_read(sock, sock.gettimeout()):
511 | raise timeout("select timed out") from e
512 | continue
513 | except OpenSSL.SSL.Error as e:
514 | raise ssl.SSLError(f"bad handshake: {e!r}") from e
515 | break
516 |
517 | return WrappedSocket(cnx, sock)
518 |
519 | def _set_ctx_options(self) -> None:
520 | self._ctx.set_options(
521 | self._options
522 | | _openssl_to_ssl_minimum_version[self._minimum_version]
523 | | _openssl_to_ssl_maximum_version[self._maximum_version]
524 | )
525 |
526 | @property
527 | def minimum_version(self) -> int:
528 | return self._minimum_version
529 |
530 | @minimum_version.setter
531 | def minimum_version(self, minimum_version: int) -> None:
532 | self._minimum_version = minimum_version
533 | self._set_ctx_options()
534 |
535 | @property
536 | def maximum_version(self) -> int:
537 | return self._maximum_version
538 |
539 | @maximum_version.setter
540 | def maximum_version(self, maximum_version: int) -> None:
541 | self._maximum_version = maximum_version
542 | self._set_ctx_options()
543 |
544 |
545 | def _verify_callback(
546 | cnx: OpenSSL.SSL.Connection,
547 | x509: X509,
548 | err_no: int,
549 | err_depth: int,
550 | return_code: int,
551 | ) -> bool:
552 | return err_no == 0
553 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py:
--------------------------------------------------------------------------------
```python
1 | import logging
2 | import sys
3 | from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
4 |
5 | from pip._vendor.packaging.requirements import InvalidRequirement
6 | from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
7 | from pip._vendor.packaging.version import Version
8 |
9 | from pip._internal.exceptions import (
10 | HashError,
11 | InstallationSubprocessError,
12 | MetadataInconsistent,
13 | MetadataInvalid,
14 | )
15 | from pip._internal.metadata import BaseDistribution
16 | from pip._internal.models.link import Link, links_equivalent
17 | from pip._internal.models.wheel import Wheel
18 | from pip._internal.req.constructors import (
19 | install_req_from_editable,
20 | install_req_from_line,
21 | )
22 | from pip._internal.req.req_install import InstallRequirement
23 | from pip._internal.utils.direct_url_helpers import direct_url_from_link
24 | from pip._internal.utils.misc import normalize_version_info
25 |
26 | from .base import Candidate, Requirement, format_name
27 |
28 | if TYPE_CHECKING:
29 | from .factory import Factory
30 |
31 | logger = logging.getLogger(__name__)
32 |
33 | BaseCandidate = Union[
34 | "AlreadyInstalledCandidate",
35 | "EditableCandidate",
36 | "LinkCandidate",
37 | ]
38 |
39 | # Avoid conflicting with the PyPI package "Python".
40 | REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
41 |
42 |
43 | def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
44 | """The runtime version of BaseCandidate."""
45 | base_candidate_classes = (
46 | AlreadyInstalledCandidate,
47 | EditableCandidate,
48 | LinkCandidate,
49 | )
50 | if isinstance(candidate, base_candidate_classes):
51 | return candidate
52 | return None
53 |
54 |
55 | def make_install_req_from_link(
56 | link: Link, template: InstallRequirement
57 | ) -> InstallRequirement:
58 | assert not template.editable, "template is editable"
59 | if template.req:
60 | line = str(template.req)
61 | else:
62 | line = link.url
63 | ireq = install_req_from_line(
64 | line,
65 | user_supplied=template.user_supplied,
66 | comes_from=template.comes_from,
67 | use_pep517=template.use_pep517,
68 | isolated=template.isolated,
69 | constraint=template.constraint,
70 | global_options=template.global_options,
71 | hash_options=template.hash_options,
72 | config_settings=template.config_settings,
73 | )
74 | ireq.original_link = template.original_link
75 | ireq.link = link
76 | ireq.extras = template.extras
77 | return ireq
78 |
79 |
80 | def make_install_req_from_editable(
81 | link: Link, template: InstallRequirement
82 | ) -> InstallRequirement:
83 | assert template.editable, "template not editable"
84 | ireq = install_req_from_editable(
85 | link.url,
86 | user_supplied=template.user_supplied,
87 | comes_from=template.comes_from,
88 | use_pep517=template.use_pep517,
89 | isolated=template.isolated,
90 | constraint=template.constraint,
91 | permit_editable_wheels=template.permit_editable_wheels,
92 | global_options=template.global_options,
93 | hash_options=template.hash_options,
94 | config_settings=template.config_settings,
95 | )
96 | ireq.extras = template.extras
97 | return ireq
98 |
99 |
100 | def _make_install_req_from_dist(
101 | dist: BaseDistribution, template: InstallRequirement
102 | ) -> InstallRequirement:
103 | if template.req:
104 | line = str(template.req)
105 | elif template.link:
106 | line = f"{dist.canonical_name} @ {template.link.url}"
107 | else:
108 | line = f"{dist.canonical_name}=={dist.version}"
109 | ireq = install_req_from_line(
110 | line,
111 | user_supplied=template.user_supplied,
112 | comes_from=template.comes_from,
113 | use_pep517=template.use_pep517,
114 | isolated=template.isolated,
115 | constraint=template.constraint,
116 | global_options=template.global_options,
117 | hash_options=template.hash_options,
118 | config_settings=template.config_settings,
119 | )
120 | ireq.satisfied_by = dist
121 | return ireq
122 |
123 |
124 | class _InstallRequirementBackedCandidate(Candidate):
125 | """A candidate backed by an ``InstallRequirement``.
126 |
127 | This represents a package request with the target not being already
128 | in the environment, and needs to be fetched and installed. The backing
129 | ``InstallRequirement`` is responsible for most of the leg work; this
130 | class exposes appropriate information to the resolver.
131 |
132 | :param link: The link passed to the ``InstallRequirement``. The backing
133 | ``InstallRequirement`` will use this link to fetch the distribution.
134 | :param source_link: The link this candidate "originates" from. This is
135 | different from ``link`` when the link is found in the wheel cache.
136 | ``link`` would point to the wheel cache, while this points to the
137 | found remote link (e.g. from pypi.org).
138 | """
139 |
140 | dist: BaseDistribution
141 | is_installed = False
142 |
143 | def __init__(
144 | self,
145 | link: Link,
146 | source_link: Link,
147 | ireq: InstallRequirement,
148 | factory: "Factory",
149 | name: Optional[NormalizedName] = None,
150 | version: Optional[Version] = None,
151 | ) -> None:
152 | self._link = link
153 | self._source_link = source_link
154 | self._factory = factory
155 | self._ireq = ireq
156 | self._name = name
157 | self._version = version
158 | self.dist = self._prepare()
159 | self._hash: Optional[int] = None
160 |
161 | def __str__(self) -> str:
162 | return f"{self.name} {self.version}"
163 |
164 | def __repr__(self) -> str:
165 | return f"{self.__class__.__name__}({str(self._link)!r})"
166 |
167 | def __hash__(self) -> int:
168 | if self._hash is not None:
169 | return self._hash
170 |
171 | self._hash = hash((self.__class__, self._link))
172 | return self._hash
173 |
174 | def __eq__(self, other: Any) -> bool:
175 | if isinstance(other, self.__class__):
176 | return links_equivalent(self._link, other._link)
177 | return False
178 |
179 | @property
180 | def source_link(self) -> Optional[Link]:
181 | return self._source_link
182 |
183 | @property
184 | def project_name(self) -> NormalizedName:
185 | """The normalised name of the project the candidate refers to"""
186 | if self._name is None:
187 | self._name = self.dist.canonical_name
188 | return self._name
189 |
190 | @property
191 | def name(self) -> str:
192 | return self.project_name
193 |
194 | @property
195 | def version(self) -> Version:
196 | if self._version is None:
197 | self._version = self.dist.version
198 | return self._version
199 |
200 | def format_for_error(self) -> str:
201 | return (
202 | f"{self.name} {self.version} "
203 | f"(from {self._link.file_path if self._link.is_file else self._link})"
204 | )
205 |
206 | def _prepare_distribution(self) -> BaseDistribution:
207 | raise NotImplementedError("Override in subclass")
208 |
209 | def _check_metadata_consistency(self, dist: BaseDistribution) -> None:
210 | """Check for consistency of project name and version of dist."""
211 | if self._name is not None and self._name != dist.canonical_name:
212 | raise MetadataInconsistent(
213 | self._ireq,
214 | "name",
215 | self._name,
216 | dist.canonical_name,
217 | )
218 | if self._version is not None and self._version != dist.version:
219 | raise MetadataInconsistent(
220 | self._ireq,
221 | "version",
222 | str(self._version),
223 | str(dist.version),
224 | )
225 | # check dependencies are valid
226 | # TODO performance: this means we iterate the dependencies at least twice,
227 | # we may want to cache parsed Requires-Dist
228 | try:
229 | list(dist.iter_dependencies(list(dist.iter_provided_extras())))
230 | except InvalidRequirement as e:
231 | raise MetadataInvalid(self._ireq, str(e))
232 |
233 | def _prepare(self) -> BaseDistribution:
234 | try:
235 | dist = self._prepare_distribution()
236 | except HashError as e:
237 | # Provide HashError the underlying ireq that caused it. This
238 | # provides context for the resulting error message to show the
239 | # offending line to the user.
240 | e.req = self._ireq
241 | raise
242 | except InstallationSubprocessError as exc:
243 | # The output has been presented already, so don't duplicate it.
244 | exc.context = "See above for output."
245 | raise
246 |
247 | self._check_metadata_consistency(dist)
248 | return dist
249 |
250 | def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
251 | requires = self.dist.iter_dependencies() if with_requires else ()
252 | for r in requires:
253 | yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
254 | yield self._factory.make_requires_python_requirement(self.dist.requires_python)
255 |
256 | def get_install_requirement(self) -> Optional[InstallRequirement]:
257 | return self._ireq
258 |
259 |
260 | class LinkCandidate(_InstallRequirementBackedCandidate):
261 | is_editable = False
262 |
263 | def __init__(
264 | self,
265 | link: Link,
266 | template: InstallRequirement,
267 | factory: "Factory",
268 | name: Optional[NormalizedName] = None,
269 | version: Optional[Version] = None,
270 | ) -> None:
271 | source_link = link
272 | cache_entry = factory.get_wheel_cache_entry(source_link, name)
273 | if cache_entry is not None:
274 | logger.debug("Using cached wheel link: %s", cache_entry.link)
275 | link = cache_entry.link
276 | ireq = make_install_req_from_link(link, template)
277 | assert ireq.link == link
278 | if ireq.link.is_wheel and not ireq.link.is_file:
279 | wheel = Wheel(ireq.link.filename)
280 | wheel_name = canonicalize_name(wheel.name)
281 | assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
282 | # Version may not be present for PEP 508 direct URLs
283 | if version is not None:
284 | wheel_version = Version(wheel.version)
285 | assert (
286 | version == wheel_version
287 | ), f"{version!r} != {wheel_version!r} for wheel {name}"
288 |
289 | if cache_entry is not None:
290 | assert ireq.link.is_wheel
291 | assert ireq.link.is_file
292 | if cache_entry.persistent and template.link is template.original_link:
293 | ireq.cached_wheel_source_link = source_link
294 | if cache_entry.origin is not None:
295 | ireq.download_info = cache_entry.origin
296 | else:
297 | # Legacy cache entry that does not have origin.json.
298 | # download_info may miss the archive_info.hashes field.
299 | ireq.download_info = direct_url_from_link(
300 | source_link, link_is_in_wheel_cache=cache_entry.persistent
301 | )
302 |
303 | super().__init__(
304 | link=link,
305 | source_link=source_link,
306 | ireq=ireq,
307 | factory=factory,
308 | name=name,
309 | version=version,
310 | )
311 |
312 | def _prepare_distribution(self) -> BaseDistribution:
313 | preparer = self._factory.preparer
314 | return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
315 |
316 |
317 | class EditableCandidate(_InstallRequirementBackedCandidate):
318 | is_editable = True
319 |
320 | def __init__(
321 | self,
322 | link: Link,
323 | template: InstallRequirement,
324 | factory: "Factory",
325 | name: Optional[NormalizedName] = None,
326 | version: Optional[Version] = None,
327 | ) -> None:
328 | super().__init__(
329 | link=link,
330 | source_link=link,
331 | ireq=make_install_req_from_editable(link, template),
332 | factory=factory,
333 | name=name,
334 | version=version,
335 | )
336 |
337 | def _prepare_distribution(self) -> BaseDistribution:
338 | return self._factory.preparer.prepare_editable_requirement(self._ireq)
339 |
340 |
341 | class AlreadyInstalledCandidate(Candidate):
342 | is_installed = True
343 | source_link = None
344 |
345 | def __init__(
346 | self,
347 | dist: BaseDistribution,
348 | template: InstallRequirement,
349 | factory: "Factory",
350 | ) -> None:
351 | self.dist = dist
352 | self._ireq = _make_install_req_from_dist(dist, template)
353 | self._factory = factory
354 | self._version = None
355 |
356 | # This is just logging some messages, so we can do it eagerly.
357 | # The returned dist would be exactly the same as self.dist because we
358 | # set satisfied_by in _make_install_req_from_dist.
359 | # TODO: Supply reason based on force_reinstall and upgrade_strategy.
360 | skip_reason = "already satisfied"
361 | factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
362 |
363 | def __str__(self) -> str:
364 | return str(self.dist)
365 |
366 | def __repr__(self) -> str:
367 | return f"{self.__class__.__name__}({self.dist!r})"
368 |
369 | def __eq__(self, other: object) -> bool:
370 | if not isinstance(other, AlreadyInstalledCandidate):
371 | return NotImplemented
372 | return self.name == other.name and self.version == other.version
373 |
374 | def __hash__(self) -> int:
375 | return hash((self.name, self.version))
376 |
377 | @property
378 | def project_name(self) -> NormalizedName:
379 | return self.dist.canonical_name
380 |
381 | @property
382 | def name(self) -> str:
383 | return self.project_name
384 |
385 | @property
386 | def version(self) -> Version:
387 | if self._version is None:
388 | self._version = self.dist.version
389 | return self._version
390 |
391 | @property
392 | def is_editable(self) -> bool:
393 | return self.dist.editable
394 |
395 | def format_for_error(self) -> str:
396 | return f"{self.name} {self.version} (Installed)"
397 |
398 | def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
399 | if not with_requires:
400 | return
401 | for r in self.dist.iter_dependencies():
402 | yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
403 |
404 | def get_install_requirement(self) -> Optional[InstallRequirement]:
405 | return None
406 |
407 |
408 | class ExtrasCandidate(Candidate):
409 | """A candidate that has 'extras', indicating additional dependencies.
410 |
411 | Requirements can be for a project with dependencies, something like
412 | foo[extra]. The extras don't affect the project/version being installed
413 | directly, but indicate that we need additional dependencies. We model that
414 | by having an artificial ExtrasCandidate that wraps the "base" candidate.
415 |
416 | The ExtrasCandidate differs from the base in the following ways:
417 |
418 | 1. It has a unique name, of the form foo[extra]. This causes the resolver
419 | to treat it as a separate node in the dependency graph.
420 | 2. When we're getting the candidate's dependencies,
421 | a) We specify that we want the extra dependencies as well.
422 | b) We add a dependency on the base candidate.
423 | See below for why this is needed.
424 | 3. We return None for the underlying InstallRequirement, as the base
425 | candidate will provide it, and we don't want to end up with duplicates.
426 |
427 | The dependency on the base candidate is needed so that the resolver can't
428 | decide that it should recommend foo[extra1] version 1.0 and foo[extra2]
429 | version 2.0. Having those candidates depend on foo=1.0 and foo=2.0
430 | respectively forces the resolver to recognise that this is a conflict.
431 | """
432 |
433 | def __init__(
434 | self,
435 | base: BaseCandidate,
436 | extras: FrozenSet[str],
437 | *,
438 | comes_from: Optional[InstallRequirement] = None,
439 | ) -> None:
440 | """
441 | :param comes_from: the InstallRequirement that led to this candidate if it
442 | differs from the base's InstallRequirement. This will often be the
443 | case in the sense that this candidate's requirement has the extras
444 | while the base's does not. Unlike the InstallRequirement backed
445 | candidates, this requirement is used solely for reporting purposes,
446 | it does not do any leg work.
447 | """
448 | self.base = base
449 | self.extras = frozenset(canonicalize_name(e) for e in extras)
450 | self._comes_from = comes_from if comes_from is not None else self.base._ireq
451 |
452 | def __str__(self) -> str:
453 | name, rest = str(self.base).split(" ", 1)
454 | return "{}[{}] {}".format(name, ",".join(self.extras), rest)
455 |
456 | def __repr__(self) -> str:
457 | return f"{self.__class__.__name__}(base={self.base!r}, extras={self.extras!r})"
458 |
459 | def __hash__(self) -> int:
460 | return hash((self.base, self.extras))
461 |
462 | def __eq__(self, other: Any) -> bool:
463 | if isinstance(other, self.__class__):
464 | return self.base == other.base and self.extras == other.extras
465 | return False
466 |
467 | @property
468 | def project_name(self) -> NormalizedName:
469 | return self.base.project_name
470 |
471 | @property
472 | def name(self) -> str:
473 | """The normalised name of the project the candidate refers to"""
474 | return format_name(self.base.project_name, self.extras)
475 |
476 | @property
477 | def version(self) -> Version:
478 | return self.base.version
479 |
480 | def format_for_error(self) -> str:
481 | return "{} [{}]".format(
482 | self.base.format_for_error(), ", ".join(sorted(self.extras))
483 | )
484 |
485 | @property
486 | def is_installed(self) -> bool:
487 | return self.base.is_installed
488 |
489 | @property
490 | def is_editable(self) -> bool:
491 | return self.base.is_editable
492 |
493 | @property
494 | def source_link(self) -> Optional[Link]:
495 | return self.base.source_link
496 |
497 | def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
498 | factory = self.base._factory
499 |
500 | # Add a dependency on the exact base
501 | # (See note 2b in the class docstring)
502 | yield factory.make_requirement_from_candidate(self.base)
503 | if not with_requires:
504 | return
505 |
506 | # The user may have specified extras that the candidate doesn't
507 | # support. We ignore any unsupported extras here.
508 | valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
509 | invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
510 | for extra in sorted(invalid_extras):
511 | logger.warning(
512 | "%s %s does not provide the extra '%s'",
513 | self.base.name,
514 | self.version,
515 | extra,
516 | )
517 |
518 | for r in self.base.dist.iter_dependencies(valid_extras):
519 | yield from factory.make_requirements_from_spec(
520 | str(r),
521 | self._comes_from,
522 | valid_extras,
523 | )
524 |
525 | def get_install_requirement(self) -> Optional[InstallRequirement]:
526 | # We don't return anything here, because we always
527 | # depend on the base candidate, and we'll get the
528 | # install requirement from that.
529 | return None
530 |
531 |
532 | class RequiresPythonCandidate(Candidate):
533 | is_installed = False
534 | source_link = None
535 |
536 | def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
537 | if py_version_info is not None:
538 | version_info = normalize_version_info(py_version_info)
539 | else:
540 | version_info = sys.version_info[:3]
541 | self._version = Version(".".join(str(c) for c in version_info))
542 |
543 | # We don't need to implement __eq__() and __ne__() since there is always
544 | # only one RequiresPythonCandidate in a resolution, i.e. the host Python.
545 | # The built-in object.__eq__() and object.__ne__() do exactly what we want.
546 |
547 | def __str__(self) -> str:
548 | return f"Python {self._version}"
549 |
550 | @property
551 | def project_name(self) -> NormalizedName:
552 | return REQUIRES_PYTHON_IDENTIFIER
553 |
554 | @property
555 | def name(self) -> str:
556 | return REQUIRES_PYTHON_IDENTIFIER
557 |
558 | @property
559 | def version(self) -> Version:
560 | return self._version
561 |
562 | def format_for_error(self) -> str:
563 | return f"Python {self.version}"
564 |
565 | def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
566 | return ()
567 |
568 | def get_install_requirement(self) -> Optional[InstallRequirement]:
569 | return None
570 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/bs4/tests/test_soup.py:
--------------------------------------------------------------------------------
```python
1 | # -*- coding: utf-8 -*-
2 | """Tests of Beautiful Soup as a whole."""
3 |
4 | from pdb import set_trace
5 | import logging
6 | import os
7 | import pickle
8 | import pytest
9 | import sys
10 | import tempfile
11 |
12 | from bs4 import (
13 | BeautifulSoup,
14 | BeautifulStoneSoup,
15 | GuessedAtParserWarning,
16 | MarkupResemblesLocatorWarning,
17 | dammit,
18 | )
19 | from bs4.builder import (
20 | builder_registry,
21 | TreeBuilder,
22 | ParserRejectedMarkup,
23 | )
24 | from bs4.element import (
25 | Comment,
26 | SoupStrainer,
27 | PYTHON_SPECIFIC_ENCODINGS,
28 | Tag,
29 | NavigableString,
30 | )
31 |
32 | from . import (
33 | default_builder,
34 | LXML_PRESENT,
35 | SoupTest,
36 | )
37 | import warnings
38 |
39 | class TestConstructor(SoupTest):
40 |
41 | def test_short_unicode_input(self):
42 | data = "<h1>éé</h1>"
43 | soup = self.soup(data)
44 | assert "éé" == soup.h1.string
45 |
46 | def test_embedded_null(self):
47 | data = "<h1>foo\0bar</h1>"
48 | soup = self.soup(data)
49 | assert "foo\0bar" == soup.h1.string
50 |
51 | def test_exclude_encodings(self):
52 | utf8_data = "Räksmörgås".encode("utf-8")
53 | soup = self.soup(utf8_data, exclude_encodings=["utf-8"])
54 | assert "windows-1252" == soup.original_encoding
55 |
56 | def test_custom_builder_class(self):
57 | # Verify that you can pass in a custom Builder class and
58 | # it'll be instantiated with the appropriate keyword arguments.
59 | class Mock(object):
60 | def __init__(self, **kwargs):
61 | self.called_with = kwargs
62 | self.is_xml = True
63 | self.store_line_numbers = False
64 | self.cdata_list_attributes = []
65 | self.preserve_whitespace_tags = []
66 | self.string_containers = {}
67 | def initialize_soup(self, soup):
68 | pass
69 | def feed(self, markup):
70 | self.fed = markup
71 | def reset(self):
72 | pass
73 | def ignore(self, ignore):
74 | pass
75 | set_up_substitutions = can_be_empty_element = ignore
76 | def prepare_markup(self, *args, **kwargs):
77 | yield "prepared markup", "original encoding", "declared encoding", "contains replacement characters"
78 |
79 | kwargs = dict(
80 | var="value",
81 | # This is a deprecated BS3-era keyword argument, which
82 | # will be stripped out.
83 | convertEntities=True,
84 | )
85 | with warnings.catch_warnings(record=True):
86 | soup = BeautifulSoup('', builder=Mock, **kwargs)
87 | assert isinstance(soup.builder, Mock)
88 | assert dict(var="value") == soup.builder.called_with
89 | assert "prepared markup" == soup.builder.fed
90 |
91 | # You can also instantiate the TreeBuilder yourself. In this
92 | # case, that specific object is used and any keyword arguments
93 | # to the BeautifulSoup constructor are ignored.
94 | builder = Mock(**kwargs)
95 | with warnings.catch_warnings(record=True) as w:
96 | soup = BeautifulSoup(
97 | '', builder=builder, ignored_value=True,
98 | )
99 | msg = str(w[0].message)
100 | assert msg.startswith("Keyword arguments to the BeautifulSoup constructor will be ignored.")
101 | assert builder == soup.builder
102 | assert kwargs == builder.called_with
103 |
104 | def test_parser_markup_rejection(self):
105 | # If markup is completely rejected by the parser, an
106 | # explanatory ParserRejectedMarkup exception is raised.
107 | class Mock(TreeBuilder):
108 | def feed(self, *args, **kwargs):
109 | raise ParserRejectedMarkup("Nope.")
110 |
111 | def prepare_markup(self, *args, **kwargs):
112 | # We're going to try two different ways of preparing this markup,
113 | # but feed() will reject both of them.
114 | yield markup, None, None, False
115 | yield markup, None, None, False
116 |
117 |
118 | import re
119 | with pytest.raises(ParserRejectedMarkup) as exc_info:
120 | BeautifulSoup('', builder=Mock)
121 | assert "The markup you provided was rejected by the parser. Trying a different parser or a different encoding may help." in str(exc_info.value)
122 |
123 | def test_cdata_list_attributes(self):
124 | # Most attribute values are represented as scalars, but the
125 | # HTML standard says that some attributes, like 'class' have
126 | # space-separated lists as values.
127 | markup = '<a id=" an id " class=" a class "></a>'
128 | soup = self.soup(markup)
129 |
130 | # Note that the spaces are stripped for 'class' but not for 'id'.
131 | a = soup.a
132 | assert " an id " == a['id']
133 | assert ["a", "class"] == a['class']
134 |
135 | # TreeBuilder takes an argument called 'multi_valued_attributes' which lets
136 | # you customize or disable this. As always, you can customize the TreeBuilder
137 | # by passing in a keyword argument to the BeautifulSoup constructor.
138 | soup = self.soup(markup, builder=default_builder, multi_valued_attributes=None)
139 | assert " a class " == soup.a['class']
140 |
141 | # Here are two ways of saying that `id` is a multi-valued
142 | # attribute in this context, but 'class' is not.
143 | for switcheroo in ({'*': 'id'}, {'a': 'id'}):
144 | with warnings.catch_warnings(record=True) as w:
145 | # This will create a warning about not explicitly
146 | # specifying a parser, but we'll ignore it.
147 | soup = self.soup(markup, builder=None, multi_valued_attributes=switcheroo)
148 | a = soup.a
149 | assert ["an", "id"] == a['id']
150 | assert " a class " == a['class']
151 |
152 | def test_replacement_classes(self):
153 | # Test the ability to pass in replacements for element classes
154 | # which will be used when building the tree.
155 | class TagPlus(Tag):
156 | pass
157 |
158 | class StringPlus(NavigableString):
159 | pass
160 |
161 | class CommentPlus(Comment):
162 | pass
163 |
164 | soup = self.soup(
165 | "<a><b>foo</b>bar</a><!--whee-->",
166 | element_classes = {
167 | Tag: TagPlus,
168 | NavigableString: StringPlus,
169 | Comment: CommentPlus,
170 | }
171 | )
172 |
173 | # The tree was built with TagPlus, StringPlus, and CommentPlus objects,
174 | # rather than Tag, String, and Comment objects.
175 | assert all(
176 | isinstance(x, (TagPlus, StringPlus, CommentPlus))
177 | for x in soup.recursiveChildGenerator()
178 | )
179 |
180 | def test_alternate_string_containers(self):
181 | # Test the ability to customize the string containers for
182 | # different types of tags.
183 | class PString(NavigableString):
184 | pass
185 |
186 | class BString(NavigableString):
187 | pass
188 |
189 | soup = self.soup(
190 | "<div>Hello.<p>Here is <b>some <i>bolded</i></b> text",
191 | string_containers = {
192 | 'b': BString,
193 | 'p': PString,
194 | }
195 | )
196 |
197 | # The string before the <p> tag is a regular NavigableString.
198 | assert isinstance(soup.div.contents[0], NavigableString)
199 |
200 | # The string inside the <p> tag, but not inside the <i> tag,
201 | # is a PString.
202 | assert isinstance(soup.p.contents[0], PString)
203 |
204 | # Every string inside the <b> tag is a BString, even the one that
205 | # was also inside an <i> tag.
206 | for s in soup.b.strings:
207 | assert isinstance(s, BString)
208 |
209 | # Now that parsing was complete, the string_container_stack
210 | # (where this information was kept) has been cleared out.
211 | assert [] == soup.string_container_stack
212 |
213 |
214 | class TestOutput(SoupTest):
215 |
216 | @pytest.mark.parametrize(
217 | "eventual_encoding,actual_encoding", [
218 | ("utf-8", "utf-8"),
219 | ("utf-16", "utf-16"),
220 | ]
221 | )
222 | def test_decode_xml_declaration(self, eventual_encoding, actual_encoding):
223 | # Most of the time, calling decode() on an XML document will
224 | # give you a document declaration that mentions the encoding
225 | # you intend to use when encoding the document as a
226 | # bytestring.
227 | soup = self.soup("<tag></tag>")
228 | soup.is_xml = True
229 | assert (f'<?xml version="1.0" encoding="{actual_encoding}"?>\n<tag></tag>'
230 | == soup.decode(eventual_encoding=eventual_encoding))
231 |
232 | @pytest.mark.parametrize(
233 | "eventual_encoding", [x for x in PYTHON_SPECIFIC_ENCODINGS] + [None]
234 | )
235 | def test_decode_xml_declaration_with_missing_or_python_internal_eventual_encoding(self, eventual_encoding):
236 | # But if you pass a Python internal encoding into decode(), or
237 | # omit the eventual_encoding altogether, the document
238 | # declaration won't mention any particular encoding.
239 | soup = BeautifulSoup("<tag></tag>", "html.parser")
240 | soup.is_xml = True
241 | assert (f'<?xml version="1.0"?>\n<tag></tag>'
242 | == soup.decode(eventual_encoding=eventual_encoding))
243 |
244 | def test(self):
245 | # BeautifulSoup subclasses Tag and extends the decode() method.
246 | # Make sure the other Tag methods which call decode() call
247 | # it correctly.
248 | soup = self.soup("<tag></tag>")
249 | assert b"<tag></tag>" == soup.encode(encoding="utf-8")
250 | assert b"<tag></tag>" == soup.encode_contents(encoding="utf-8")
251 | assert "<tag></tag>" == soup.decode_contents()
252 | assert "<tag>\n</tag>\n" == soup.prettify()
253 |
254 |
255 | class TestWarnings(SoupTest):
256 | # Note that some of the tests in this class create BeautifulSoup
257 | # objects directly rather than using self.soup(). That's
258 | # because SoupTest.soup is defined in a different file,
259 | # which will throw off the assertion in _assert_warning
260 | # that the code that triggered the warning is in the same
261 | # file as the test.
262 |
263 | def _assert_warning(self, warnings, cls):
264 | for w in warnings:
265 | if isinstance(w.message, cls):
266 | assert w.filename == __file__
267 | return w
268 | raise Exception("%s warning not found in %r" % (cls, warnings))
269 |
270 | def _assert_no_parser_specified(self, w):
271 | warning = self._assert_warning(w, GuessedAtParserWarning)
272 | message = str(warning.message)
273 | assert message.startswith(BeautifulSoup.NO_PARSER_SPECIFIED_WARNING[:60])
274 |
275 | def test_warning_if_no_parser_specified(self):
276 | with warnings.catch_warnings(record=True) as w:
277 | soup = BeautifulSoup("<a><b></b></a>")
278 | self._assert_no_parser_specified(w)
279 |
280 | def test_warning_if_parser_specified_too_vague(self):
281 | with warnings.catch_warnings(record=True) as w:
282 | soup = BeautifulSoup("<a><b></b></a>", "html")
283 | self._assert_no_parser_specified(w)
284 |
285 | def test_no_warning_if_explicit_parser_specified(self):
286 | with warnings.catch_warnings(record=True) as w:
287 | soup = self.soup("<a><b></b></a>")
288 | assert [] == w
289 |
290 | def test_parseOnlyThese_renamed_to_parse_only(self):
291 | with warnings.catch_warnings(record=True) as w:
292 | soup = BeautifulSoup(
293 | "<a><b></b></a>", "html.parser",
294 | parseOnlyThese=SoupStrainer("b"),
295 | )
296 | warning = self._assert_warning(w, DeprecationWarning)
297 | msg = str(warning.message)
298 | assert "parseOnlyThese" in msg
299 | assert "parse_only" in msg
300 | assert b"<b></b>" == soup.encode()
301 |
302 | def test_fromEncoding_renamed_to_from_encoding(self):
303 | with warnings.catch_warnings(record=True) as w:
304 | utf8 = b"\xc3\xa9"
305 | soup = BeautifulSoup(
306 | utf8, "html.parser", fromEncoding="utf8"
307 | )
308 | warning = self._assert_warning(w, DeprecationWarning)
309 | msg = str(warning.message)
310 | assert "fromEncoding" in msg
311 | assert "from_encoding" in msg
312 | assert "utf8" == soup.original_encoding
313 |
314 | def test_unrecognized_keyword_argument(self):
315 | with pytest.raises(TypeError):
316 | self.soup("<a>", no_such_argument=True)
317 |
318 | @pytest.mark.parametrize(
319 | "extension",
320 | ['markup.html', 'markup.htm', 'markup.HTML', 'markup.txt',
321 | 'markup.xhtml', 'markup.xml', "/home/user/file", "c:\\user\file"]
322 | )
323 | def test_resembles_filename_warning(self, extension):
324 | # A warning is issued if the "markup" looks like the name of
325 | # an HTML or text file, or a full path to a file on disk.
326 | with warnings.catch_warnings(record=True) as w:
327 | soup = BeautifulSoup("markup" + extension, "html.parser")
328 | warning = self._assert_warning(w, MarkupResemblesLocatorWarning)
329 | assert "looks more like a filename" in str(warning.message)
330 |
331 | @pytest.mark.parametrize(
332 | "extension",
333 | ['markuphtml', 'markup.com', '', 'markup.js']
334 | )
335 | def test_resembles_filename_no_warning(self, extension):
336 | # The 'looks more like a filename' warning is not issued if
337 | # the markup looks like a bare string, a domain name, or a
338 | # file that's not an HTML file.
339 | with warnings.catch_warnings(record=True) as w:
340 | soup = self.soup("markup" + extension)
341 | assert [] == w
342 |
343 | def test_url_warning_with_bytes_url(self):
344 | url = b"http://www.crummybytes.com/"
345 | with warnings.catch_warnings(record=True) as warning_list:
346 | soup = BeautifulSoup(url, "html.parser")
347 | warning = self._assert_warning(
348 | warning_list, MarkupResemblesLocatorWarning
349 | )
350 | assert "looks more like a URL" in str(warning.message)
351 | assert url not in str(warning.message).encode("utf8")
352 |
353 | def test_url_warning_with_unicode_url(self):
354 | url = "http://www.crummyunicode.com/"
355 | with warnings.catch_warnings(record=True) as warning_list:
356 | # note - this url must differ from the bytes one otherwise
357 | # python's warnings system swallows the second warning
358 | soup = BeautifulSoup(url, "html.parser")
359 | warning = self._assert_warning(
360 | warning_list, MarkupResemblesLocatorWarning
361 | )
362 | assert "looks more like a URL" in str(warning.message)
363 | assert url not in str(warning.message)
364 |
365 | def test_url_warning_with_bytes_and_space(self):
366 | # Here the markup contains something besides a URL, so no warning
367 | # is issued.
368 | with warnings.catch_warnings(record=True) as warning_list:
369 | soup = self.soup(b"http://www.crummybytes.com/ is great")
370 | assert not any("looks more like a URL" in str(w.message)
371 | for w in warning_list)
372 |
373 | def test_url_warning_with_unicode_and_space(self):
374 | with warnings.catch_warnings(record=True) as warning_list:
375 | soup = self.soup("http://www.crummyunicode.com/ is great")
376 | assert not any("looks more like a URL" in str(w.message)
377 | for w in warning_list)
378 |
379 |
380 | class TestSelectiveParsing(SoupTest):
381 |
382 | def test_parse_with_soupstrainer(self):
383 | markup = "No<b>Yes</b><a>No<b>Yes <c>Yes</c></b>"
384 | strainer = SoupStrainer("b")
385 | soup = self.soup(markup, parse_only=strainer)
386 | assert soup.encode() == b"<b>Yes</b><b>Yes <c>Yes</c></b>"
387 |
388 |
389 | class TestNewTag(SoupTest):
390 | """Test the BeautifulSoup.new_tag() method."""
391 | def test_new_tag(self):
392 | soup = self.soup("")
393 | new_tag = soup.new_tag("foo", bar="baz", attrs={"name": "a name"})
394 | assert isinstance(new_tag, Tag)
395 | assert "foo" == new_tag.name
396 | assert dict(bar="baz", name="a name") == new_tag.attrs
397 | assert None == new_tag.parent
398 |
399 | @pytest.mark.skipif(
400 | not LXML_PRESENT,
401 | reason="lxml not installed, cannot parse XML document"
402 | )
403 | def test_xml_tag_inherits_self_closing_rules_from_builder(self):
404 | xml_soup = BeautifulSoup("", "xml")
405 | xml_br = xml_soup.new_tag("br")
406 | xml_p = xml_soup.new_tag("p")
407 |
408 | # Both the <br> and <p> tag are empty-element, just because
409 | # they have no contents.
410 | assert b"<br/>" == xml_br.encode()
411 | assert b"<p/>" == xml_p.encode()
412 |
413 | def test_tag_inherits_self_closing_rules_from_builder(self):
414 | html_soup = BeautifulSoup("", "html.parser")
415 | html_br = html_soup.new_tag("br")
416 | html_p = html_soup.new_tag("p")
417 |
418 | # The HTML builder users HTML's rules about which tags are
419 | # empty-element tags, and the new tags reflect these rules.
420 | assert b"<br/>" == html_br.encode()
421 | assert b"<p></p>" == html_p.encode()
422 |
423 | class TestNewString(SoupTest):
424 | """Test the BeautifulSoup.new_string() method."""
425 | def test_new_string_creates_navigablestring(self):
426 | soup = self.soup("")
427 | s = soup.new_string("foo")
428 | assert "foo" == s
429 | assert isinstance(s, NavigableString)
430 |
431 | def test_new_string_can_create_navigablestring_subclass(self):
432 | soup = self.soup("")
433 | s = soup.new_string("foo", Comment)
434 | assert "foo" == s
435 | assert isinstance(s, Comment)
436 |
437 |
438 | class TestPickle(SoupTest):
439 | # Test our ability to pickle the BeautifulSoup object itself.
440 |
441 | def test_normal_pickle(self):
442 | soup = self.soup("<a>some markup</a>")
443 | pickled = pickle.dumps(soup)
444 | unpickled = pickle.loads(pickled)
445 | assert "some markup" == unpickled.a.string
446 |
447 | def test_pickle_with_no_builder(self):
448 | # We had a bug that prevented pickling from working if
449 | # the builder wasn't set.
450 | soup = self.soup("some markup")
451 | soup.builder = None
452 | pickled = pickle.dumps(soup)
453 | unpickled = pickle.loads(pickled)
454 | assert "some markup" == unpickled.string
455 |
456 | class TestEncodingConversion(SoupTest):
457 | # Test Beautiful Soup's ability to decode and encode from various
458 | # encodings.
459 |
460 | def setup_method(self):
461 | self.unicode_data = '<html><head><meta charset="utf-8"/></head><body><foo>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</foo></body></html>'
462 | self.utf8_data = self.unicode_data.encode("utf-8")
463 | # Just so you know what it looks like.
464 | assert self.utf8_data == b'<html><head><meta charset="utf-8"/></head><body><foo>Sacr\xc3\xa9 bleu!</foo></body></html>'
465 |
466 | def test_ascii_in_unicode_out(self):
467 | # ASCII input is converted to Unicode. The original_encoding
468 | # attribute is set to 'utf-8', a superset of ASCII.
469 | chardet = dammit.chardet_dammit
470 | logging.disable(logging.WARNING)
471 | try:
472 | def noop(str):
473 | return None
474 | # Disable chardet, which will realize that the ASCII is ASCII.
475 | dammit.chardet_dammit = noop
476 | ascii = b"<foo>a</foo>"
477 | soup_from_ascii = self.soup(ascii)
478 | unicode_output = soup_from_ascii.decode()
479 | assert isinstance(unicode_output, str)
480 | assert unicode_output == self.document_for(ascii.decode())
481 | assert soup_from_ascii.original_encoding.lower() == "utf-8"
482 | finally:
483 | logging.disable(logging.NOTSET)
484 | dammit.chardet_dammit = chardet
485 |
486 | def test_unicode_in_unicode_out(self):
487 | # Unicode input is left alone. The original_encoding attribute
488 | # is not set.
489 | soup_from_unicode = self.soup(self.unicode_data)
490 | assert soup_from_unicode.decode() == self.unicode_data
491 | assert soup_from_unicode.foo.string == 'Sacr\xe9 bleu!'
492 | assert soup_from_unicode.original_encoding == None
493 |
494 | def test_utf8_in_unicode_out(self):
495 | # UTF-8 input is converted to Unicode. The original_encoding
496 | # attribute is set.
497 | soup_from_utf8 = self.soup(self.utf8_data)
498 | assert soup_from_utf8.decode() == self.unicode_data
499 | assert soup_from_utf8.foo.string == 'Sacr\xe9 bleu!'
500 |
501 | def test_utf8_out(self):
502 | # The internal data structures can be encoded as UTF-8.
503 | soup_from_unicode = self.soup(self.unicode_data)
504 | assert soup_from_unicode.encode('utf-8') == self.utf8_data
505 |
```