This is page 141 of 168. Use http://codebase.md/romanshablio/mcp_server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .DS_Store
├── .venv
│ ├── __pycache__
│ │ └── hello.cpython-312.pyc
│ ├── bin
│ │ ├── activate
│ │ ├── activate.csh
│ │ ├── activate.fish
│ │ ├── Activate.ps1
│ │ ├── flask
│ │ ├── normalizer
│ │ ├── pip
│ │ ├── pip3
│ │ ├── pip3.12
│ │ ├── python
│ │ ├── python3
│ │ └── python3.12
│ ├── hello.py
│ ├── lib
│ │ └── python3.12
│ │ └── site-packages
│ │ ├── beautifulsoup4-4.12.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ ├── AUTHORS
│ │ │ │ └── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── blinker
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _utilities.cpython-312.pyc
│ │ │ │ └── base.cpython-312.pyc
│ │ │ ├── _utilities.py
│ │ │ ├── base.py
│ │ │ └── py.typed
│ │ ├── blinker-1.8.2.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── bs4
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── css.cpython-312.pyc
│ │ │ │ ├── dammit.cpython-312.pyc
│ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ ├── element.cpython-312.pyc
│ │ │ │ └── formatter.cpython-312.pyc
│ │ │ ├── builder
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _html5lib.cpython-312.pyc
│ │ │ │ │ ├── _htmlparser.cpython-312.pyc
│ │ │ │ │ └── _lxml.cpython-312.pyc
│ │ │ │ ├── _html5lib.py
│ │ │ │ ├── _htmlparser.py
│ │ │ │ └── _lxml.py
│ │ │ ├── css.py
│ │ │ ├── dammit.py
│ │ │ ├── diagnose.py
│ │ │ ├── element.py
│ │ │ ├── formatter.py
│ │ │ └── tests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── test_builder_registry.cpython-312.pyc
│ │ │ │ ├── test_builder.cpython-312.pyc
│ │ │ │ ├── test_css.cpython-312.pyc
│ │ │ │ ├── test_dammit.cpython-312.pyc
│ │ │ │ ├── test_docs.cpython-312.pyc
│ │ │ │ ├── test_element.cpython-312.pyc
│ │ │ │ ├── test_formatter.cpython-312.pyc
│ │ │ │ ├── test_fuzz.cpython-312.pyc
│ │ │ │ ├── test_html5lib.cpython-312.pyc
│ │ │ │ ├── test_htmlparser.cpython-312.pyc
│ │ │ │ ├── test_lxml.cpython-312.pyc
│ │ │ │ ├── test_navigablestring.cpython-312.pyc
│ │ │ │ ├── test_pageelement.cpython-312.pyc
│ │ │ │ ├── test_soup.cpython-312.pyc
│ │ │ │ ├── test_tag.cpython-312.pyc
│ │ │ │ └── test_tree.cpython-312.pyc
│ │ │ ├── fuzz
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4670634698080256.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4818336571064320.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4999465949331456.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5000587759190016.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5167584867909632.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5270998950477824.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5375146639360000.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5492400320282624.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5703933063462912.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5843991618256896.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5984173902397440.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6124268085182464.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6241471367348224.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6306874195312640.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6450958476902400.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6600557255327744.testcase
│ │ │ │ ├── crash-0d306a50c8ed8bcd0785b67000fcd5dea1d33f08.testcase
│ │ │ │ └── crash-ffbdfa8a2b26f13537b68d3794b0478a4090ee4a.testcase
│ │ │ ├── test_builder_registry.py
│ │ │ ├── test_builder.py
│ │ │ ├── test_css.py
│ │ │ ├── test_dammit.py
│ │ │ ├── test_docs.py
│ │ │ ├── test_element.py
│ │ │ ├── test_formatter.py
│ │ │ ├── test_fuzz.py
│ │ │ ├── test_html5lib.py
│ │ │ ├── test_htmlparser.py
│ │ │ ├── test_lxml.py
│ │ │ ├── test_navigablestring.py
│ │ │ ├── test_pageelement.py
│ │ │ ├── test_soup.py
│ │ │ ├── test_tag.py
│ │ │ └── test_tree.py
│ │ ├── certifi
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── core.cpython-312.pyc
│ │ │ ├── cacert.pem
│ │ │ ├── core.py
│ │ │ └── py.typed
│ │ ├── certifi-2024.8.30.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── charset_normalizer
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── cd.cpython-312.pyc
│ │ │ │ ├── constant.cpython-312.pyc
│ │ │ │ ├── legacy.cpython-312.pyc
│ │ │ │ ├── md.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── version.cpython-312.pyc
│ │ │ ├── api.py
│ │ │ ├── cd.py
│ │ │ ├── cli
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __main__.py
│ │ │ │ └── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── __main__.cpython-312.pyc
│ │ │ ├── constant.py
│ │ │ ├── legacy.py
│ │ │ ├── md__mypyc.cpython-312-darwin.so
│ │ │ ├── md.cpython-312-darwin.so
│ │ │ ├── md.py
│ │ │ ├── models.py
│ │ │ ├── py.typed
│ │ │ ├── utils.py
│ │ │ └── version.py
│ │ ├── charset_normalizer-3.4.0.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── click
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ ├── _termui_impl.cpython-312.pyc
│ │ │ │ ├── _textwrap.cpython-312.pyc
│ │ │ │ ├── _winconsole.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── decorators.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formatting.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── shell_completion.cpython-312.pyc
│ │ │ │ ├── termui.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── types.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── _compat.py
│ │ │ ├── _termui_impl.py
│ │ │ ├── _textwrap.py
│ │ │ ├── _winconsole.py
│ │ │ ├── core.py
│ │ │ ├── decorators.py
│ │ │ ├── exceptions.py
│ │ │ ├── formatting.py
│ │ │ ├── globals.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── shell_completion.py
│ │ │ ├── termui.py
│ │ │ ├── testing.py
│ │ │ ├── types.py
│ │ │ └── utils.py
│ │ ├── click-8.1.7.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.rst
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── fake_useragent
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ ├── fake.cpython-312.pyc
│ │ │ │ ├── log.cpython-312.pyc
│ │ │ │ ├── settings.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── data
│ │ │ │ └── browsers.json
│ │ │ ├── errors.py
│ │ │ ├── fake.py
│ │ │ ├── log.py
│ │ │ ├── settings.py
│ │ │ └── utils.py
│ │ ├── fake_useragent-1.5.1.dist-info
│ │ │ ├── AUTHORS
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── flask
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ ├── cli.cpython-312.pyc
│ │ │ │ ├── config.cpython-312.pyc
│ │ │ │ ├── ctx.cpython-312.pyc
│ │ │ │ ├── debughelpers.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── helpers.cpython-312.pyc
│ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── signals.cpython-312.pyc
│ │ │ │ ├── templating.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── typing.cpython-312.pyc
│ │ │ │ ├── views.cpython-312.pyc
│ │ │ │ └── wrappers.cpython-312.pyc
│ │ │ ├── app.py
│ │ │ ├── blueprints.py
│ │ │ ├── cli.py
│ │ │ ├── config.py
│ │ │ ├── ctx.py
│ │ │ ├── debughelpers.py
│ │ │ ├── globals.py
│ │ │ ├── helpers.py
│ │ │ ├── json
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ └── tag.cpython-312.pyc
│ │ │ │ ├── provider.py
│ │ │ │ └── tag.py
│ │ │ ├── logging.py
│ │ │ ├── py.typed
│ │ │ ├── sansio
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ │ └── scaffold.cpython-312.pyc
│ │ │ │ ├── app.py
│ │ │ │ ├── blueprints.py
│ │ │ │ ├── README.md
│ │ │ │ └── scaffold.py
│ │ │ ├── sessions.py
│ │ │ ├── signals.py
│ │ │ ├── templating.py
│ │ │ ├── testing.py
│ │ │ ├── typing.py
│ │ │ ├── views.py
│ │ │ └── wrappers.py
│ │ ├── flask-3.0.3.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── idna
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ ├── codec.py
│ │ │ ├── compat.py
│ │ │ ├── core.py
│ │ │ ├── idnadata.py
│ │ │ ├── intranges.py
│ │ │ ├── package_data.py
│ │ │ ├── py.typed
│ │ │ └── uts46data.py
│ │ ├── idna-3.10.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── itsdangerous
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ ├── exc.cpython-312.pyc
│ │ │ │ ├── serializer.cpython-312.pyc
│ │ │ │ ├── signer.cpython-312.pyc
│ │ │ │ ├── timed.cpython-312.pyc
│ │ │ │ └── url_safe.cpython-312.pyc
│ │ │ ├── _json.py
│ │ │ ├── encoding.py
│ │ │ ├── exc.py
│ │ │ ├── py.typed
│ │ │ ├── serializer.py
│ │ │ ├── signer.py
│ │ │ ├── timed.py
│ │ │ └── url_safe.py
│ │ ├── itsdangerous-2.2.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── jinja2
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _identifier.cpython-312.pyc
│ │ │ │ ├── async_utils.cpython-312.pyc
│ │ │ │ ├── bccache.cpython-312.pyc
│ │ │ │ ├── compiler.cpython-312.pyc
│ │ │ │ ├── constants.cpython-312.pyc
│ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ ├── defaults.cpython-312.pyc
│ │ │ │ ├── environment.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ ├── filters.cpython-312.pyc
│ │ │ │ ├── idtracking.cpython-312.pyc
│ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ ├── loaders.cpython-312.pyc
│ │ │ │ ├── meta.cpython-312.pyc
│ │ │ │ ├── nativetypes.cpython-312.pyc
│ │ │ │ ├── nodes.cpython-312.pyc
│ │ │ │ ├── optimizer.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── runtime.cpython-312.pyc
│ │ │ │ ├── sandbox.cpython-312.pyc
│ │ │ │ ├── tests.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── visitor.cpython-312.pyc
│ │ │ ├── _identifier.py
│ │ │ ├── async_utils.py
│ │ │ ├── bccache.py
│ │ │ ├── compiler.py
│ │ │ ├── constants.py
│ │ │ ├── debug.py
│ │ │ ├── defaults.py
│ │ │ ├── environment.py
│ │ │ ├── exceptions.py
│ │ │ ├── ext.py
│ │ │ ├── filters.py
│ │ │ ├── idtracking.py
│ │ │ ├── lexer.py
│ │ │ ├── loaders.py
│ │ │ ├── meta.py
│ │ │ ├── nativetypes.py
│ │ │ ├── nodes.py
│ │ │ ├── optimizer.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── runtime.py
│ │ │ ├── sandbox.py
│ │ │ ├── tests.py
│ │ │ ├── utils.py
│ │ │ └── visitor.py
│ │ ├── jinja2-3.1.4.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── lxml
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _elementpath.cpython-312.pyc
│ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ ├── cssselect.cpython-312.pyc
│ │ │ │ ├── doctestcompare.cpython-312.pyc
│ │ │ │ ├── ElementInclude.cpython-312.pyc
│ │ │ │ ├── pyclasslookup.cpython-312.pyc
│ │ │ │ ├── sax.cpython-312.pyc
│ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ ├── _elementpath.cpython-312-darwin.so
│ │ │ ├── _elementpath.py
│ │ │ ├── apihelpers.pxi
│ │ │ ├── builder.cpython-312-darwin.so
│ │ │ ├── builder.py
│ │ │ ├── classlookup.pxi
│ │ │ ├── cleanup.pxi
│ │ │ ├── cssselect.py
│ │ │ ├── debug.pxi
│ │ │ ├── docloader.pxi
│ │ │ ├── doctestcompare.py
│ │ │ ├── dtd.pxi
│ │ │ ├── ElementInclude.py
│ │ │ ├── etree_api.h
│ │ │ ├── etree.cpython-312-darwin.so
│ │ │ ├── etree.h
│ │ │ ├── etree.pyx
│ │ │ ├── extensions.pxi
│ │ │ ├── html
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _diffcommand.cpython-312.pyc
│ │ │ │ │ ├── _html5builder.cpython-312.pyc
│ │ │ │ │ ├── _setmixin.cpython-312.pyc
│ │ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ │ ├── clean.cpython-312.pyc
│ │ │ │ │ ├── defs.cpython-312.pyc
│ │ │ │ │ ├── diff.cpython-312.pyc
│ │ │ │ │ ├── ElementSoup.cpython-312.pyc
│ │ │ │ │ ├── formfill.cpython-312.pyc
│ │ │ │ │ ├── html5parser.cpython-312.pyc
│ │ │ │ │ ├── soupparser.cpython-312.pyc
│ │ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ │ ├── _diffcommand.py
│ │ │ │ ├── _html5builder.py
│ │ │ │ ├── _setmixin.py
│ │ │ │ ├── builder.py
│ │ │ │ ├── clean.py
│ │ │ │ ├── defs.py
│ │ │ │ ├── diff.cpython-312-darwin.so
│ │ │ │ ├── diff.py
│ │ │ │ ├── ElementSoup.py
│ │ │ │ ├── formfill.py
│ │ │ │ ├── html5parser.py
│ │ │ │ ├── soupparser.py
│ │ │ │ └── usedoctest.py
│ │ │ ├── includes
│ │ │ │ ├── __init__.pxd
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── c14n.pxd
│ │ │ │ ├── config.pxd
│ │ │ │ ├── dtdvalid.pxd
│ │ │ │ ├── etree_defs.h
│ │ │ │ ├── etreepublic.pxd
│ │ │ │ ├── extlibs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── libcharset.h
│ │ │ │ │ ├── localcharset.h
│ │ │ │ │ ├── zconf.h
│ │ │ │ │ └── zlib.h
│ │ │ │ ├── htmlparser.pxd
│ │ │ │ ├── libexslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── exslt.h
│ │ │ │ │ ├── exsltconfig.h
│ │ │ │ │ └── exsltexports.h
│ │ │ │ ├── libxml
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── c14n.h
│ │ │ │ │ ├── catalog.h
│ │ │ │ │ ├── chvalid.h
│ │ │ │ │ ├── debugXML.h
│ │ │ │ │ ├── dict.h
│ │ │ │ │ ├── encoding.h
│ │ │ │ │ ├── entities.h
│ │ │ │ │ ├── globals.h
│ │ │ │ │ ├── hash.h
│ │ │ │ │ ├── HTMLparser.h
│ │ │ │ │ ├── HTMLtree.h
│ │ │ │ │ ├── list.h
│ │ │ │ │ ├── nanoftp.h
│ │ │ │ │ ├── nanohttp.h
│ │ │ │ │ ├── parser.h
│ │ │ │ │ ├── parserInternals.h
│ │ │ │ │ ├── relaxng.h
│ │ │ │ │ ├── SAX.h
│ │ │ │ │ ├── SAX2.h
│ │ │ │ │ ├── schemasInternals.h
│ │ │ │ │ ├── schematron.h
│ │ │ │ │ ├── threads.h
│ │ │ │ │ ├── tree.h
│ │ │ │ │ ├── uri.h
│ │ │ │ │ ├── valid.h
│ │ │ │ │ ├── xinclude.h
│ │ │ │ │ ├── xlink.h
│ │ │ │ │ ├── xmlautomata.h
│ │ │ │ │ ├── xmlerror.h
│ │ │ │ │ ├── xmlexports.h
│ │ │ │ │ ├── xmlIO.h
│ │ │ │ │ ├── xmlmemory.h
│ │ │ │ │ ├── xmlmodule.h
│ │ │ │ │ ├── xmlreader.h
│ │ │ │ │ ├── xmlregexp.h
│ │ │ │ │ ├── xmlsave.h
│ │ │ │ │ ├── xmlschemas.h
│ │ │ │ │ ├── xmlschemastypes.h
│ │ │ │ │ ├── xmlstring.h
│ │ │ │ │ ├── xmlunicode.h
│ │ │ │ │ ├── xmlversion.h
│ │ │ │ │ ├── xmlwriter.h
│ │ │ │ │ ├── xpath.h
│ │ │ │ │ ├── xpathInternals.h
│ │ │ │ │ └── xpointer.h
│ │ │ │ ├── libxslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── attributes.h
│ │ │ │ │ ├── documents.h
│ │ │ │ │ ├── extensions.h
│ │ │ │ │ ├── extra.h
│ │ │ │ │ ├── functions.h
│ │ │ │ │ ├── imports.h
│ │ │ │ │ ├── keys.h
│ │ │ │ │ ├── namespaces.h
│ │ │ │ │ ├── numbersInternals.h
│ │ │ │ │ ├── pattern.h
│ │ │ │ │ ├── preproc.h
│ │ │ │ │ ├── security.h
│ │ │ │ │ ├── templates.h
│ │ │ │ │ ├── transform.h
│ │ │ │ │ ├── variables.h
│ │ │ │ │ ├── xslt.h
│ │ │ │ │ ├── xsltconfig.h
│ │ │ │ │ ├── xsltexports.h
│ │ │ │ │ ├── xsltInternals.h
│ │ │ │ │ ├── xsltlocale.h
│ │ │ │ │ └── xsltutils.h
│ │ │ │ ├── lxml-version.h
│ │ │ │ ├── relaxng.pxd
│ │ │ │ ├── schematron.pxd
│ │ │ │ ├── tree.pxd
│ │ │ │ ├── uri.pxd
│ │ │ │ ├── xinclude.pxd
│ │ │ │ ├── xmlerror.pxd
│ │ │ │ ├── xmlparser.pxd
│ │ │ │ ├── xmlschema.pxd
│ │ │ │ ├── xpath.pxd
│ │ │ │ └── xslt.pxd
│ │ │ ├── isoschematron
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ └── resources
│ │ │ │ ├── rng
│ │ │ │ │ └── iso-schematron.rng
│ │ │ │ └── xsl
│ │ │ │ ├── iso-schematron-xslt1
│ │ │ │ │ ├── iso_abstract_expand.xsl
│ │ │ │ │ ├── iso_dsdl_include.xsl
│ │ │ │ │ ├── iso_schematron_message.xsl
│ │ │ │ │ ├── iso_schematron_skeleton_for_xslt1.xsl
│ │ │ │ │ ├── iso_svrl_for_xslt1.xsl
│ │ │ │ │ └── readme.txt
│ │ │ │ ├── RNG2Schtrn.xsl
│ │ │ │ └── XSD2Schtrn.xsl
│ │ │ ├── iterparse.pxi
│ │ │ ├── lxml.etree_api.h
│ │ │ ├── lxml.etree.h
│ │ │ ├── nsclasses.pxi
│ │ │ ├── objectify.cpython-312-darwin.so
│ │ │ ├── objectify.pyx
│ │ │ ├── objectpath.pxi
│ │ │ ├── parser.pxi
│ │ │ ├── parsertarget.pxi
│ │ │ ├── proxy.pxi
│ │ │ ├── public-api.pxi
│ │ │ ├── pyclasslookup.py
│ │ │ ├── readonlytree.pxi
│ │ │ ├── relaxng.pxi
│ │ │ ├── sax.cpython-312-darwin.so
│ │ │ ├── sax.py
│ │ │ ├── saxparser.pxi
│ │ │ ├── schematron.pxi
│ │ │ ├── serializer.pxi
│ │ │ ├── usedoctest.py
│ │ │ ├── xinclude.pxi
│ │ │ ├── xmlerror.pxi
│ │ │ ├── xmlid.pxi
│ │ │ ├── xmlschema.pxi
│ │ │ ├── xpath.pxi
│ │ │ ├── xslt.pxi
│ │ │ └── xsltext.pxi
│ │ ├── lxml-5.3.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── LICENSES.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── markupsafe
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── _native.cpython-312.pyc
│ │ │ ├── _native.py
│ │ │ ├── _speedups.c
│ │ │ ├── _speedups.cpython-312-darwin.so
│ │ │ ├── _speedups.pyi
│ │ │ └── py.typed
│ │ ├── MarkupSafe-3.0.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── pip
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pip-runner__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── __pip-runner__.cpython-312.pyc
│ │ │ ├── _internal
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── build_env.cpython-312.pyc
│ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ ├── pyproject.cpython-312.pyc
│ │ │ │ │ ├── self_outdated_check.cpython-312.pyc
│ │ │ │ │ └── wheel_builder.cpython-312.pyc
│ │ │ │ ├── build_env.py
│ │ │ │ ├── cache.py
│ │ │ │ ├── cli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── autocompletion.cpython-312.pyc
│ │ │ │ │ │ ├── base_command.cpython-312.pyc
│ │ │ │ │ │ ├── cmdoptions.cpython-312.pyc
│ │ │ │ │ │ ├── command_context.cpython-312.pyc
│ │ │ │ │ │ ├── index_command.cpython-312.pyc
│ │ │ │ │ │ ├── main_parser.cpython-312.pyc
│ │ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bars.cpython-312.pyc
│ │ │ │ │ │ ├── req_command.cpython-312.pyc
│ │ │ │ │ │ ├── spinners.cpython-312.pyc
│ │ │ │ │ │ └── status_codes.cpython-312.pyc
│ │ │ │ │ ├── autocompletion.py
│ │ │ │ │ ├── base_command.py
│ │ │ │ │ ├── cmdoptions.py
│ │ │ │ │ ├── command_context.py
│ │ │ │ │ ├── index_command.py
│ │ │ │ │ ├── main_parser.py
│ │ │ │ │ ├── main.py
│ │ │ │ │ ├── parser.py
│ │ │ │ │ ├── progress_bars.py
│ │ │ │ │ ├── req_command.py
│ │ │ │ │ ├── spinners.py
│ │ │ │ │ └── status_codes.py
│ │ │ │ ├── commands
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── completion.cpython-312.pyc
│ │ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ ├── hash.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── inspect.cpython-312.pyc
│ │ │ │ │ │ ├── install.cpython-312.pyc
│ │ │ │ │ │ ├── list.cpython-312.pyc
│ │ │ │ │ │ ├── search.cpython-312.pyc
│ │ │ │ │ │ ├── show.cpython-312.pyc
│ │ │ │ │ │ ├── uninstall.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── completion.py
│ │ │ │ │ ├── configuration.py
│ │ │ │ │ ├── debug.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── hash.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── inspect.py
│ │ │ │ │ ├── install.py
│ │ │ │ │ ├── list.py
│ │ │ │ │ ├── search.py
│ │ │ │ │ ├── show.py
│ │ │ │ │ ├── uninstall.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── configuration.py
│ │ │ │ ├── distributions
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── installed.cpython-312.pyc
│ │ │ │ │ │ ├── sdist.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── installed.py
│ │ │ │ │ ├── sdist.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── index
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── collector.cpython-312.pyc
│ │ │ │ │ │ ├── package_finder.cpython-312.pyc
│ │ │ │ │ │ └── sources.cpython-312.pyc
│ │ │ │ │ ├── collector.py
│ │ │ │ │ ├── package_finder.py
│ │ │ │ │ └── sources.py
│ │ │ │ ├── locations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _distutils.cpython-312.pyc
│ │ │ │ │ │ ├── _sysconfig.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── _distutils.py
│ │ │ │ │ ├── _sysconfig.py
│ │ │ │ │ └── base.py
│ │ │ │ ├── main.py
│ │ │ │ ├── metadata
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ └── pkg_resources.cpython-312.pyc
│ │ │ │ │ ├── _json.py
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── importlib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ │ ├── _dists.cpython-312.pyc
│ │ │ │ │ │ │ └── _envs.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.py
│ │ │ │ │ │ ├── _dists.py
│ │ │ │ │ │ └── _envs.py
│ │ │ │ │ └── pkg_resources.py
│ │ │ │ ├── models
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── candidate.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url.cpython-312.pyc
│ │ │ │ │ │ ├── format_control.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── installation_report.cpython-312.pyc
│ │ │ │ │ │ ├── link.cpython-312.pyc
│ │ │ │ │ │ ├── scheme.cpython-312.pyc
│ │ │ │ │ │ ├── search_scope.cpython-312.pyc
│ │ │ │ │ │ ├── selection_prefs.cpython-312.pyc
│ │ │ │ │ │ ├── target_python.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── candidate.py
│ │ │ │ │ ├── direct_url.py
│ │ │ │ │ ├── format_control.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── installation_report.py
│ │ │ │ │ ├── link.py
│ │ │ │ │ ├── scheme.py
│ │ │ │ │ ├── search_scope.py
│ │ │ │ │ ├── selection_prefs.py
│ │ │ │ │ ├── target_python.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── network
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── lazy_wheel.cpython-312.pyc
│ │ │ │ │ │ ├── session.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── xmlrpc.cpython-312.pyc
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── lazy_wheel.py
│ │ │ │ │ ├── session.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── xmlrpc.py
│ │ │ │ ├── operations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ └── prepare.cpython-312.pyc
│ │ │ │ │ ├── build
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── build_tracker.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_legacy.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── build_tracker.py
│ │ │ │ │ │ ├── metadata_editable.py
│ │ │ │ │ │ ├── metadata_legacy.py
│ │ │ │ │ │ ├── metadata.py
│ │ │ │ │ │ ├── wheel_editable.py
│ │ │ │ │ │ ├── wheel_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── install
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── editable_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── editable_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ └── prepare.py
│ │ │ │ ├── pyproject.py
│ │ │ │ ├── req
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── constructors.cpython-312.pyc
│ │ │ │ │ │ ├── req_file.cpython-312.pyc
│ │ │ │ │ │ ├── req_install.cpython-312.pyc
│ │ │ │ │ │ ├── req_set.cpython-312.pyc
│ │ │ │ │ │ └── req_uninstall.cpython-312.pyc
│ │ │ │ │ ├── constructors.py
│ │ │ │ │ ├── req_file.py
│ │ │ │ │ ├── req_install.py
│ │ │ │ │ ├── req_set.py
│ │ │ │ │ └── req_uninstall.py
│ │ │ │ ├── resolution
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── legacy
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ │ └── resolver.py
│ │ │ │ │ └── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── candidates.cpython-312.pyc
│ │ │ │ │ │ ├── factory.cpython-312.pyc
│ │ │ │ │ │ ├── found_candidates.cpython-312.pyc
│ │ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ │ ├── reporter.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── candidates.py
│ │ │ │ │ ├── factory.py
│ │ │ │ │ ├── found_candidates.py
│ │ │ │ │ ├── provider.py
│ │ │ │ │ ├── reporter.py
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ └── resolver.py
│ │ │ │ ├── self_outdated_check.py
│ │ │ │ ├── utils
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _jaraco_text.cpython-312.pyc
│ │ │ │ │ │ ├── _log.cpython-312.pyc
│ │ │ │ │ │ ├── appdirs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── compatibility_tags.cpython-312.pyc
│ │ │ │ │ │ ├── datetime.cpython-312.pyc
│ │ │ │ │ │ ├── deprecation.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url_helpers.cpython-312.pyc
│ │ │ │ │ │ ├── egg_link.cpython-312.pyc
│ │ │ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ │ │ ├── entrypoints.cpython-312.pyc
│ │ │ │ │ │ ├── filesystem.cpython-312.pyc
│ │ │ │ │ │ ├── filetypes.cpython-312.pyc
│ │ │ │ │ │ ├── glibc.cpython-312.pyc
│ │ │ │ │ │ ├── hashes.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── misc.cpython-312.pyc
│ │ │ │ │ │ ├── packaging.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── setuptools_build.cpython-312.pyc
│ │ │ │ │ │ ├── subprocess.cpython-312.pyc
│ │ │ │ │ │ ├── temp_dir.cpython-312.pyc
│ │ │ │ │ │ ├── unpacking.cpython-312.pyc
│ │ │ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ │ │ ├── virtualenv.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── _jaraco_text.py
│ │ │ │ │ ├── _log.py
│ │ │ │ │ ├── appdirs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── compatibility_tags.py
│ │ │ │ │ ├── datetime.py
│ │ │ │ │ ├── deprecation.py
│ │ │ │ │ ├── direct_url_helpers.py
│ │ │ │ │ ├── egg_link.py
│ │ │ │ │ ├── encoding.py
│ │ │ │ │ ├── entrypoints.py
│ │ │ │ │ ├── filesystem.py
│ │ │ │ │ ├── filetypes.py
│ │ │ │ │ ├── glibc.py
│ │ │ │ │ ├── hashes.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── misc.py
│ │ │ │ │ ├── packaging.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── setuptools_build.py
│ │ │ │ │ ├── subprocess.py
│ │ │ │ │ ├── temp_dir.py
│ │ │ │ │ ├── unpacking.py
│ │ │ │ │ ├── urls.py
│ │ │ │ │ ├── virtualenv.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── vcs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── bazaar.cpython-312.pyc
│ │ │ │ │ │ ├── git.cpython-312.pyc
│ │ │ │ │ │ ├── mercurial.cpython-312.pyc
│ │ │ │ │ │ ├── subversion.cpython-312.pyc
│ │ │ │ │ │ └── versioncontrol.cpython-312.pyc
│ │ │ │ │ ├── bazaar.py
│ │ │ │ │ ├── git.py
│ │ │ │ │ ├── mercurial.py
│ │ │ │ │ ├── subversion.py
│ │ │ │ │ └── versioncontrol.py
│ │ │ │ └── wheel_builder.py
│ │ │ ├── _vendor
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ └── typing_extensions.cpython-312.pyc
│ │ │ │ ├── cachecontrol
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _cmd.cpython-312.pyc
│ │ │ │ │ │ ├── adapter.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── controller.cpython-312.pyc
│ │ │ │ │ │ ├── filewrapper.cpython-312.pyc
│ │ │ │ │ │ ├── heuristics.cpython-312.pyc
│ │ │ │ │ │ ├── serialize.cpython-312.pyc
│ │ │ │ │ │ └── wrapper.cpython-312.pyc
│ │ │ │ │ ├── _cmd.py
│ │ │ │ │ ├── adapter.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── caches
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── file_cache.cpython-312.pyc
│ │ │ │ │ │ │ └── redis_cache.cpython-312.pyc
│ │ │ │ │ │ ├── file_cache.py
│ │ │ │ │ │ └── redis_cache.py
│ │ │ │ │ ├── controller.py
│ │ │ │ │ ├── filewrapper.py
│ │ │ │ │ ├── heuristics.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── serialize.py
│ │ │ │ │ └── wrapper.py
│ │ │ │ ├── certifi
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── core.cpython-312.pyc
│ │ │ │ │ ├── cacert.pem
│ │ │ │ │ ├── core.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── distlib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── database.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── locators.cpython-312.pyc
│ │ │ │ │ │ ├── manifest.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── resources.cpython-312.pyc
│ │ │ │ │ │ ├── scripts.cpython-312.pyc
│ │ │ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── database.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── locators.py
│ │ │ │ │ ├── manifest.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── resources.py
│ │ │ │ │ ├── scripts.py
│ │ │ │ │ ├── t32.exe
│ │ │ │ │ ├── t64-arm.exe
│ │ │ │ │ ├── t64.exe
│ │ │ │ │ ├── util.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ ├── w32.exe
│ │ │ │ │ ├── w64-arm.exe
│ │ │ │ │ ├── w64.exe
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── distro
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── distro.cpython-312.pyc
│ │ │ │ │ ├── distro.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── idna
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ │ │ ├── codec.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── core.py
│ │ │ │ │ ├── idnadata.py
│ │ │ │ │ ├── intranges.py
│ │ │ │ │ ├── package_data.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ └── uts46data.py
│ │ │ │ ├── msgpack
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ │ │ └── fallback.cpython-312.pyc
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── ext.py
│ │ │ │ │ └── fallback.py
│ │ │ │ ├── packaging
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _elffile.cpython-312.pyc
│ │ │ │ │ │ ├── _manylinux.cpython-312.pyc
│ │ │ │ │ │ ├── _musllinux.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _structures.cpython-312.pyc
│ │ │ │ │ │ ├── _tokenizer.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ ├── specifiers.cpython-312.pyc
│ │ │ │ │ │ ├── tags.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── version.cpython-312.pyc
│ │ │ │ │ ├── _elffile.py
│ │ │ │ │ ├── _manylinux.py
│ │ │ │ │ ├── _musllinux.py
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _structures.py
│ │ │ │ │ ├── _tokenizer.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ ├── specifiers.py
│ │ │ │ │ ├── tags.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── version.py
│ │ │ │ ├── pkg_resources
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── platformdirs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── android.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── macos.cpython-312.pyc
│ │ │ │ │ │ ├── unix.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── windows.cpython-312.pyc
│ │ │ │ │ ├── android.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── macos.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── unix.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ └── windows.py
│ │ │ │ ├── pygments
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── cmdline.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── filter.cpython-312.pyc
│ │ │ │ │ │ ├── formatter.cpython-312.pyc
│ │ │ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ │ │ ├── modeline.cpython-312.pyc
│ │ │ │ │ │ ├── plugin.cpython-312.pyc
│ │ │ │ │ │ ├── regexopt.cpython-312.pyc
│ │ │ │ │ │ ├── scanner.cpython-312.pyc
│ │ │ │ │ │ ├── sphinxext.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── token.cpython-312.pyc
│ │ │ │ │ │ ├── unistring.cpython-312.pyc
│ │ │ │ │ │ └── util.cpython-312.pyc
│ │ │ │ │ ├── cmdline.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── filter.py
│ │ │ │ │ ├── filters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ └── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── formatter.py
│ │ │ │ │ ├── formatters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ ├── bbcode.cpython-312.pyc
│ │ │ │ │ │ │ ├── groff.cpython-312.pyc
│ │ │ │ │ │ │ ├── html.cpython-312.pyc
│ │ │ │ │ │ │ ├── img.cpython-312.pyc
│ │ │ │ │ │ │ ├── irc.cpython-312.pyc
│ │ │ │ │ │ │ ├── latex.cpython-312.pyc
│ │ │ │ │ │ │ ├── other.cpython-312.pyc
│ │ │ │ │ │ │ ├── pangomarkup.cpython-312.pyc
│ │ │ │ │ │ │ ├── rtf.cpython-312.pyc
│ │ │ │ │ │ │ ├── svg.cpython-312.pyc
│ │ │ │ │ │ │ ├── terminal.cpython-312.pyc
│ │ │ │ │ │ │ └── terminal256.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ ├── bbcode.py
│ │ │ │ │ │ ├── groff.py
│ │ │ │ │ │ ├── html.py
│ │ │ │ │ │ ├── img.py
│ │ │ │ │ │ ├── irc.py
│ │ │ │ │ │ ├── latex.py
│ │ │ │ │ │ ├── other.py
│ │ │ │ │ │ ├── pangomarkup.py
│ │ │ │ │ │ ├── rtf.py
│ │ │ │ │ │ ├── svg.py
│ │ │ │ │ │ ├── terminal.py
│ │ │ │ │ │ └── terminal256.py
│ │ │ │ │ ├── lexer.py
│ │ │ │ │ ├── lexers
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ └── python.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ └── python.py
│ │ │ │ │ ├── modeline.py
│ │ │ │ │ ├── plugin.py
│ │ │ │ │ ├── regexopt.py
│ │ │ │ │ ├── scanner.py
│ │ │ │ │ ├── sphinxext.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styles
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── _mapping.cpython-312.pyc
│ │ │ │ │ │ └── _mapping.py
│ │ │ │ │ ├── token.py
│ │ │ │ │ ├── unistring.py
│ │ │ │ │ └── util.py
│ │ │ │ ├── pyproject_hooks
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ └── _impl.cpython-312.pyc
│ │ │ │ │ ├── _compat.py
│ │ │ │ │ ├── _impl.py
│ │ │ │ │ └── _in_process
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── _in_process.cpython-312.pyc
│ │ │ │ │ └── _in_process.py
│ │ │ │ ├── requests
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ │ ├── __version__.py
│ │ │ │ │ ├── _internal_utils.py
│ │ │ │ │ ├── adapters.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── certs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── cookies.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── hooks.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── packages.py
│ │ │ │ │ ├── sessions.py
│ │ │ │ │ ├── status_codes.py
│ │ │ │ │ ├── structures.py
│ │ │ │ │ └── utils.py
│ │ │ │ ├── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── providers.cpython-312.pyc
│ │ │ │ │ │ ├── reporters.cpython-312.pyc
│ │ │ │ │ │ ├── resolvers.cpython-312.pyc
│ │ │ │ │ │ └── structs.cpython-312.pyc
│ │ │ │ │ ├── compat
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── collections_abc.cpython-312.pyc
│ │ │ │ │ │ └── collections_abc.py
│ │ │ │ │ ├── providers.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── reporters.py
│ │ │ │ │ ├── resolvers.py
│ │ │ │ │ └── structs.py
│ │ │ │ ├── rich
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── _cell_widths.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_codes.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_replace.cpython-312.pyc
│ │ │ │ │ │ ├── _export_format.cpython-312.pyc
│ │ │ │ │ │ ├── _extension.cpython-312.pyc
│ │ │ │ │ │ ├── _fileno.cpython-312.pyc
│ │ │ │ │ │ ├── _inspect.cpython-312.pyc
│ │ │ │ │ │ ├── _log_render.cpython-312.pyc
│ │ │ │ │ │ ├── _loop.cpython-312.pyc
│ │ │ │ │ │ ├── _null_file.cpython-312.pyc
│ │ │ │ │ │ ├── _palettes.cpython-312.pyc
│ │ │ │ │ │ ├── _pick.cpython-312.pyc
│ │ │ │ │ │ ├── _ratio.cpython-312.pyc
│ │ │ │ │ │ ├── _spinners.cpython-312.pyc
│ │ │ │ │ │ ├── _stack.cpython-312.pyc
│ │ │ │ │ │ ├── _timer.cpython-312.pyc
│ │ │ │ │ │ ├── _win32_console.cpython-312.pyc
│ │ │ │ │ │ ├── _windows_renderer.cpython-312.pyc
│ │ │ │ │ │ ├── _windows.cpython-312.pyc
│ │ │ │ │ │ ├── _wrap.cpython-312.pyc
│ │ │ │ │ │ ├── abc.cpython-312.pyc
│ │ │ │ │ │ ├── align.cpython-312.pyc
│ │ │ │ │ │ ├── ansi.cpython-312.pyc
│ │ │ │ │ │ ├── bar.cpython-312.pyc
│ │ │ │ │ │ ├── box.cpython-312.pyc
│ │ │ │ │ │ ├── cells.cpython-312.pyc
│ │ │ │ │ │ ├── color_triplet.cpython-312.pyc
│ │ │ │ │ │ ├── color.cpython-312.pyc
│ │ │ │ │ │ ├── columns.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── constrain.cpython-312.pyc
│ │ │ │ │ │ ├── containers.cpython-312.pyc
│ │ │ │ │ │ ├── control.cpython-312.pyc
│ │ │ │ │ │ ├── default_styles.cpython-312.pyc
│ │ │ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ │ │ ├── emoji.cpython-312.pyc
│ │ │ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ │ │ ├── file_proxy.cpython-312.pyc
│ │ │ │ │ │ ├── filesize.cpython-312.pyc
│ │ │ │ │ │ ├── highlighter.cpython-312.pyc
│ │ │ │ │ │ ├── json.cpython-312.pyc
│ │ │ │ │ │ ├── jupyter.cpython-312.pyc
│ │ │ │ │ │ ├── layout.cpython-312.pyc
│ │ │ │ │ │ ├── live_render.cpython-312.pyc
│ │ │ │ │ │ ├── live.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── markup.cpython-312.pyc
│ │ │ │ │ │ ├── measure.cpython-312.pyc
│ │ │ │ │ │ ├── padding.cpython-312.pyc
│ │ │ │ │ │ ├── pager.cpython-312.pyc
│ │ │ │ │ │ ├── palette.cpython-312.pyc
│ │ │ │ │ │ ├── panel.cpython-312.pyc
│ │ │ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bar.cpython-312.pyc
│ │ │ │ │ │ ├── progress.cpython-312.pyc
│ │ │ │ │ │ ├── prompt.cpython-312.pyc
│ │ │ │ │ │ ├── protocol.cpython-312.pyc
│ │ │ │ │ │ ├── region.cpython-312.pyc
│ │ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ │ ├── rule.cpython-312.pyc
│ │ │ │ │ │ ├── scope.cpython-312.pyc
│ │ │ │ │ │ ├── screen.cpython-312.pyc
│ │ │ │ │ │ ├── segment.cpython-312.pyc
│ │ │ │ │ │ ├── spinner.cpython-312.pyc
│ │ │ │ │ │ ├── status.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── styled.cpython-312.pyc
│ │ │ │ │ │ ├── syntax.cpython-312.pyc
│ │ │ │ │ │ ├── table.cpython-312.pyc
│ │ │ │ │ │ ├── terminal_theme.cpython-312.pyc
│ │ │ │ │ │ ├── text.cpython-312.pyc
│ │ │ │ │ │ ├── theme.cpython-312.pyc
│ │ │ │ │ │ ├── themes.cpython-312.pyc
│ │ │ │ │ │ ├── traceback.cpython-312.pyc
│ │ │ │ │ │ └── tree.cpython-312.pyc
│ │ │ │ │ ├── _cell_widths.py
│ │ │ │ │ ├── _emoji_codes.py
│ │ │ │ │ ├── _emoji_replace.py
│ │ │ │ │ ├── _export_format.py
│ │ │ │ │ ├── _extension.py
│ │ │ │ │ ├── _fileno.py
│ │ │ │ │ ├── _inspect.py
│ │ │ │ │ ├── _log_render.py
│ │ │ │ │ ├── _loop.py
│ │ │ │ │ ├── _null_file.py
│ │ │ │ │ ├── _palettes.py
│ │ │ │ │ ├── _pick.py
│ │ │ │ │ ├── _ratio.py
│ │ │ │ │ ├── _spinners.py
│ │ │ │ │ ├── _stack.py
│ │ │ │ │ ├── _timer.py
│ │ │ │ │ ├── _win32_console.py
│ │ │ │ │ ├── _windows_renderer.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ ├── _wrap.py
│ │ │ │ │ ├── abc.py
│ │ │ │ │ ├── align.py
│ │ │ │ │ ├── ansi.py
│ │ │ │ │ ├── bar.py
│ │ │ │ │ ├── box.py
│ │ │ │ │ ├── cells.py
│ │ │ │ │ ├── color_triplet.py
│ │ │ │ │ ├── color.py
│ │ │ │ │ ├── columns.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── constrain.py
│ │ │ │ │ ├── containers.py
│ │ │ │ │ ├── control.py
│ │ │ │ │ ├── default_styles.py
│ │ │ │ │ ├── diagnose.py
│ │ │ │ │ ├── emoji.py
│ │ │ │ │ ├── errors.py
│ │ │ │ │ ├── file_proxy.py
│ │ │ │ │ ├── filesize.py
│ │ │ │ │ ├── highlighter.py
│ │ │ │ │ ├── json.py
│ │ │ │ │ ├── jupyter.py
│ │ │ │ │ ├── layout.py
│ │ │ │ │ ├── live_render.py
│ │ │ │ │ ├── live.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── markup.py
│ │ │ │ │ ├── measure.py
│ │ │ │ │ ├── padding.py
│ │ │ │ │ ├── pager.py
│ │ │ │ │ ├── palette.py
│ │ │ │ │ ├── panel.py
│ │ │ │ │ ├── pretty.py
│ │ │ │ │ ├── progress_bar.py
│ │ │ │ │ ├── progress.py
│ │ │ │ │ ├── prompt.py
│ │ │ │ │ ├── protocol.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── region.py
│ │ │ │ │ ├── repr.py
│ │ │ │ │ ├── rule.py
│ │ │ │ │ ├── scope.py
│ │ │ │ │ ├── screen.py
│ │ │ │ │ ├── segment.py
│ │ │ │ │ ├── spinner.py
│ │ │ │ │ ├── status.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styled.py
│ │ │ │ │ ├── syntax.py
│ │ │ │ │ ├── table.py
│ │ │ │ │ ├── terminal_theme.py
│ │ │ │ │ ├── text.py
│ │ │ │ │ ├── theme.py
│ │ │ │ │ ├── themes.py
│ │ │ │ │ ├── traceback.py
│ │ │ │ │ └── tree.py
│ │ │ │ ├── tomli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _re.cpython-312.pyc
│ │ │ │ │ │ └── _types.cpython-312.pyc
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _re.py
│ │ │ │ │ ├── _types.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── truststore
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _api.cpython-312.pyc
│ │ │ │ │ │ ├── _macos.cpython-312.pyc
│ │ │ │ │ │ ├── _openssl.cpython-312.pyc
│ │ │ │ │ │ ├── _ssl_constants.cpython-312.pyc
│ │ │ │ │ │ └── _windows.cpython-312.pyc
│ │ │ │ │ ├── _api.py
│ │ │ │ │ ├── _macos.py
│ │ │ │ │ ├── _openssl.py
│ │ │ │ │ ├── _ssl_constants.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── typing_extensions.py
│ │ │ │ ├── urllib3
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── _collections.py
│ │ │ │ │ ├── _version.py
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── connectionpool.py
│ │ │ │ │ ├── contrib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _appengine_environ.cpython-312.pyc
│ │ │ │ │ │ │ ├── appengine.cpython-312.pyc
│ │ │ │ │ │ │ ├── ntlmpool.cpython-312.pyc
│ │ │ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ │ │ ├── securetransport.cpython-312.pyc
│ │ │ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ │ │ ├── _appengine_environ.py
│ │ │ │ │ │ ├── _securetransport
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── bindings.cpython-312.pyc
│ │ │ │ │ │ │ │ └── low_level.cpython-312.pyc
│ │ │ │ │ │ │ ├── bindings.py
│ │ │ │ │ │ │ └── low_level.py
│ │ │ │ │ │ ├── appengine.py
│ │ │ │ │ │ ├── ntlmpool.py
│ │ │ │ │ │ ├── pyopenssl.py
│ │ │ │ │ │ ├── securetransport.py
│ │ │ │ │ │ └── socks.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── fields.py
│ │ │ │ │ ├── filepost.py
│ │ │ │ │ ├── packages
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── six.cpython-312.pyc
│ │ │ │ │ │ ├── backports
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── makefile.cpython-312.pyc
│ │ │ │ │ │ │ │ └── weakref_finalize.cpython-312.pyc
│ │ │ │ │ │ │ ├── makefile.py
│ │ │ │ │ │ │ └── weakref_finalize.py
│ │ │ │ │ │ └── six.py
│ │ │ │ │ ├── poolmanager.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ └── util
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ │ │ ├── queue.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ │ │ └── wait.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── proxy.py
│ │ │ │ │ ├── queue.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── ssl_.py
│ │ │ │ │ ├── ssl_match_hostname.py
│ │ │ │ │ ├── ssltransport.py
│ │ │ │ │ ├── timeout.py
│ │ │ │ │ ├── url.py
│ │ │ │ │ └── wait.py
│ │ │ │ └── vendor.txt
│ │ │ └── py.typed
│ │ ├── pip-24.2.dist-info
│ │ │ ├── AUTHORS.txt
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── requests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── __version__.py
│ │ │ ├── _internal_utils.py
│ │ │ ├── adapters.py
│ │ │ ├── api.py
│ │ │ ├── auth.py
│ │ │ ├── certs.py
│ │ │ ├── compat.py
│ │ │ ├── cookies.py
│ │ │ ├── exceptions.py
│ │ │ ├── help.py
│ │ │ ├── hooks.py
│ │ │ ├── models.py
│ │ │ ├── packages.py
│ │ │ ├── sessions.py
│ │ │ ├── status_codes.py
│ │ │ ├── structures.py
│ │ │ └── utils.py
│ │ ├── requests-2.32.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── soupsieve
│ │ │ ├── __init__.py
│ │ │ ├── __meta__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __meta__.cpython-312.pyc
│ │ │ │ ├── css_match.cpython-312.pyc
│ │ │ │ ├── css_parser.cpython-312.pyc
│ │ │ │ ├── css_types.cpython-312.pyc
│ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ └── util.cpython-312.pyc
│ │ │ ├── css_match.py
│ │ │ ├── css_parser.py
│ │ │ ├── css_types.py
│ │ │ ├── pretty.py
│ │ │ ├── py.typed
│ │ │ └── util.py
│ │ ├── soupsieve-2.6.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── urllib3
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _base_connection.cpython-312.pyc
│ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ ├── _request_methods.cpython-312.pyc
│ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ └── response.cpython-312.pyc
│ │ │ ├── _base_connection.py
│ │ │ ├── _collections.py
│ │ │ ├── _request_methods.py
│ │ │ ├── _version.py
│ │ │ ├── connection.py
│ │ │ ├── connectionpool.py
│ │ │ ├── contrib
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ ├── emscripten
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── fetch.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── emscripten_fetch_worker.js
│ │ │ │ │ ├── fetch.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ └── response.py
│ │ │ │ ├── pyopenssl.py
│ │ │ │ └── socks.py
│ │ │ ├── exceptions.py
│ │ │ ├── fields.py
│ │ │ ├── filepost.py
│ │ │ ├── http2
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ └── probe.cpython-312.pyc
│ │ │ │ ├── connection.py
│ │ │ │ └── probe.py
│ │ │ ├── poolmanager.py
│ │ │ ├── py.typed
│ │ │ ├── response.py
│ │ │ └── util
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ └── wait.cpython-312.pyc
│ │ │ ├── connection.py
│ │ │ ├── proxy.py
│ │ │ ├── request.py
│ │ │ ├── response.py
│ │ │ ├── retry.py
│ │ │ ├── ssl_.py
│ │ │ ├── ssl_match_hostname.py
│ │ │ ├── ssltransport.py
│ │ │ ├── timeout.py
│ │ │ ├── url.py
│ │ │ ├── util.py
│ │ │ └── wait.py
│ │ ├── urllib3-2.2.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── useragent
│ │ │ ├── __init__.py
│ │ │ ├── __init__.pyc
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── resources
│ │ │ │ └── user_agent_data.json
│ │ │ └── test
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── test_additional_os.json
│ │ │ ├── test_browser.json
│ │ │ ├── test_device.json
│ │ │ ├── test_firefox.json
│ │ │ ├── test_os.json
│ │ │ └── test_pgts_browser.json
│ │ ├── useragent-0.1.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── werkzeug
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _internal.cpython-312.pyc
│ │ │ │ ├── _reloader.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formparser.cpython-312.pyc
│ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ ├── local.cpython-312.pyc
│ │ │ │ ├── security.cpython-312.pyc
│ │ │ │ ├── serving.cpython-312.pyc
│ │ │ │ ├── test.cpython-312.pyc
│ │ │ │ ├── testapp.cpython-312.pyc
│ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ ├── user_agent.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── wsgi.cpython-312.pyc
│ │ │ ├── _internal.py
│ │ │ ├── _reloader.py
│ │ │ ├── datastructures
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── accept.cpython-312.pyc
│ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ ├── cache_control.cpython-312.pyc
│ │ │ │ │ ├── csp.cpython-312.pyc
│ │ │ │ │ ├── etag.cpython-312.pyc
│ │ │ │ │ ├── file_storage.cpython-312.pyc
│ │ │ │ │ ├── headers.cpython-312.pyc
│ │ │ │ │ ├── mixins.cpython-312.pyc
│ │ │ │ │ ├── range.cpython-312.pyc
│ │ │ │ │ └── structures.cpython-312.pyc
│ │ │ │ ├── accept.py
│ │ │ │ ├── accept.pyi
│ │ │ │ ├── auth.py
│ │ │ │ ├── cache_control.py
│ │ │ │ ├── cache_control.pyi
│ │ │ │ ├── csp.py
│ │ │ │ ├── csp.pyi
│ │ │ │ ├── etag.py
│ │ │ │ ├── etag.pyi
│ │ │ │ ├── file_storage.py
│ │ │ │ ├── file_storage.pyi
│ │ │ │ ├── headers.py
│ │ │ │ ├── headers.pyi
│ │ │ │ ├── mixins.py
│ │ │ │ ├── mixins.pyi
│ │ │ │ ├── range.py
│ │ │ │ ├── range.pyi
│ │ │ │ ├── structures.py
│ │ │ │ └── structures.pyi
│ │ │ ├── debug
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ └── tbtools.cpython-312.pyc
│ │ │ │ ├── console.py
│ │ │ │ ├── repr.py
│ │ │ │ ├── shared
│ │ │ │ │ ├── console.png
│ │ │ │ │ ├── debugger.js
│ │ │ │ │ ├── ICON_LICENSE.md
│ │ │ │ │ ├── less.png
│ │ │ │ │ ├── more.png
│ │ │ │ │ └── style.css
│ │ │ │ └── tbtools.py
│ │ │ ├── exceptions.py
│ │ │ ├── formparser.py
│ │ │ ├── http.py
│ │ │ ├── local.py
│ │ │ ├── middleware
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── dispatcher.cpython-312.pyc
│ │ │ │ │ ├── http_proxy.cpython-312.pyc
│ │ │ │ │ ├── lint.cpython-312.pyc
│ │ │ │ │ ├── profiler.cpython-312.pyc
│ │ │ │ │ ├── proxy_fix.cpython-312.pyc
│ │ │ │ │ └── shared_data.cpython-312.pyc
│ │ │ │ ├── dispatcher.py
│ │ │ │ ├── http_proxy.py
│ │ │ │ ├── lint.py
│ │ │ │ ├── profiler.py
│ │ │ │ ├── proxy_fix.py
│ │ │ │ └── shared_data.py
│ │ │ ├── py.typed
│ │ │ ├── routing
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── converters.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── map.cpython-312.pyc
│ │ │ │ │ ├── matcher.cpython-312.pyc
│ │ │ │ │ └── rules.cpython-312.pyc
│ │ │ │ ├── converters.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── map.py
│ │ │ │ ├── matcher.py
│ │ │ │ └── rules.py
│ │ │ ├── sansio
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ │ ├── multipart.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ ├── http.py
│ │ │ │ ├── multipart.py
│ │ │ │ ├── request.py
│ │ │ │ ├── response.py
│ │ │ │ └── utils.py
│ │ │ ├── security.py
│ │ │ ├── serving.py
│ │ │ ├── test.py
│ │ │ ├── testapp.py
│ │ │ ├── urls.py
│ │ │ ├── user_agent.py
│ │ │ ├── utils.py
│ │ │ ├── wrappers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ ├── request.py
│ │ │ │ └── response.py
│ │ │ └── wsgi.py
│ │ └── werkzeug-3.0.4.dist-info
│ │ ├── INSTALLER
│ │ ├── LICENSE.txt
│ │ ├── METADATA
│ │ ├── RECORD
│ │ └── WHEEL
│ ├── pyvenv.cfg
│ ├── static
│ │ └── styles.css
│ ├── templates
│ │ └── index.html
│ └── test.py
├── cline_config.json
├── mcp_server.py
├── README.md
├── search_results.json
├── settings.json
└── test_files
├── text1.txt
└── text2.txt
```
# Files
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/bs4/tests/test_tree.py:
--------------------------------------------------------------------------------
```python
1 | # -*- coding: utf-8 -*-
2 | """Tests for Beautiful Soup's tree traversal methods.
3 |
4 | The tree traversal methods are the main advantage of using Beautiful
5 | Soup over just using a parser.
6 |
7 | Different parsers will build different Beautiful Soup trees given the
8 | same markup, but all Beautiful Soup trees can be traversed with the
9 | methods tested here.
10 | """
11 |
12 | from pdb import set_trace
13 | import pytest
14 | import re
15 | import warnings
16 | from bs4 import BeautifulSoup
17 | from bs4.builder import (
18 | builder_registry,
19 | HTMLParserTreeBuilder,
20 | )
21 | from bs4.element import (
22 | CData,
23 | Comment,
24 | Declaration,
25 | Doctype,
26 | Formatter,
27 | NavigableString,
28 | Script,
29 | SoupStrainer,
30 | Stylesheet,
31 | Tag,
32 | TemplateString,
33 | )
34 | from . import (
35 | SoupTest,
36 | )
37 |
38 | class TestFind(SoupTest):
39 | """Basic tests of the find() method.
40 |
41 | find() just calls find_all() with limit=1, so it's not tested all
42 | that thouroughly here.
43 | """
44 |
45 | def test_find_tag(self):
46 | soup = self.soup("<a>1</a><b>2</b><a>3</a><b>4</b>")
47 | assert soup.find("b").string == "2"
48 |
49 | def test_unicode_text_find(self):
50 | soup = self.soup('<h1>Räksmörgås</h1>')
51 | assert soup.find(string='Räksmörgås') == 'Räksmörgås'
52 |
53 | def test_unicode_attribute_find(self):
54 | soup = self.soup('<h1 id="Räksmörgås">here it is</h1>')
55 | str(soup)
56 | assert "here it is" == soup.find(id='Räksmörgås').text
57 |
58 |
59 | def test_find_everything(self):
60 | """Test an optimization that finds all tags."""
61 | soup = self.soup("<a>foo</a><b>bar</b>")
62 | assert 2 == len(soup.find_all())
63 |
64 | def test_find_everything_with_name(self):
65 | """Test an optimization that finds all tags with a given name."""
66 | soup = self.soup("<a>foo</a><b>bar</b><a>baz</a>")
67 | assert 2 == len(soup.find_all('a'))
68 |
69 | class TestFindAll(SoupTest):
70 | """Basic tests of the find_all() method."""
71 |
72 | def test_find_all_text_nodes(self):
73 | """You can search the tree for text nodes."""
74 | soup = self.soup("<html>Foo<b>bar</b>\xbb</html>")
75 | # Exact match.
76 | assert soup.find_all(string="bar") == ["bar"]
77 |
78 |
79 | # Match any of a number of strings.
80 | assert soup.find_all(string=["Foo", "bar"]) == ["Foo", "bar"]
81 | # Match a regular expression.
82 | assert soup.find_all(string=re.compile('.*')) == ["Foo", "bar", '\xbb']
83 | # Match anything.
84 | assert soup.find_all(string=True) == ["Foo", "bar", '\xbb']
85 |
86 | def test_find_all_limit(self):
87 | """You can limit the number of items returned by find_all."""
88 | soup = self.soup("<a>1</a><a>2</a><a>3</a><a>4</a><a>5</a>")
89 | self.assert_selects(soup.find_all('a', limit=3), ["1", "2", "3"])
90 | self.assert_selects(soup.find_all('a', limit=1), ["1"])
91 | self.assert_selects(
92 | soup.find_all('a', limit=10), ["1", "2", "3", "4", "5"])
93 |
94 | # A limit of 0 means no limit.
95 | self.assert_selects(
96 | soup.find_all('a', limit=0), ["1", "2", "3", "4", "5"])
97 |
98 | def test_calling_a_tag_is_calling_findall(self):
99 | soup = self.soup("<a>1</a><b>2<a id='foo'>3</a></b>")
100 | self.assert_selects(soup('a', limit=1), ["1"])
101 | self.assert_selects(soup.b(id="foo"), ["3"])
102 |
103 | def test_find_all_with_self_referential_data_structure_does_not_cause_infinite_recursion(self):
104 | soup = self.soup("<a></a>")
105 | # Create a self-referential list.
106 | l = []
107 | l.append(l)
108 |
109 | # Without special code in _normalize_search_value, this would cause infinite
110 | # recursion.
111 | assert [] == soup.find_all(l)
112 |
113 | def test_find_all_resultset(self):
114 | """All find_all calls return a ResultSet"""
115 | soup = self.soup("<a></a>")
116 | result = soup.find_all("a")
117 | assert hasattr(result, "source")
118 |
119 | result = soup.find_all(True)
120 | assert hasattr(result, "source")
121 |
122 | result = soup.find_all(string="foo")
123 | assert hasattr(result, "source")
124 |
125 |
126 | class TestFindAllBasicNamespaces(SoupTest):
127 |
128 | def test_find_by_namespaced_name(self):
129 | soup = self.soup('<mathml:msqrt>4</mathml:msqrt><a svg:fill="red">')
130 | assert "4" == soup.find("mathml:msqrt").string
131 | assert "a" == soup.find(attrs= { "svg:fill" : "red" }).name
132 |
133 |
134 | class TestFindAllByName(SoupTest):
135 | """Test ways of finding tags by tag name."""
136 |
137 | def setup_method(self):
138 | self.tree = self.soup("""<a>First tag.</a>
139 | <b>Second tag.</b>
140 | <c>Third <a>Nested tag.</a> tag.</c>""")
141 |
142 | def test_find_all_by_tag_name(self):
143 | # Find all the <a> tags.
144 | self.assert_selects(
145 | self.tree.find_all('a'), ['First tag.', 'Nested tag.'])
146 |
147 | def test_find_all_by_name_and_text(self):
148 | self.assert_selects(
149 | self.tree.find_all('a', string='First tag.'), ['First tag.'])
150 |
151 | self.assert_selects(
152 | self.tree.find_all('a', string=True), ['First tag.', 'Nested tag.'])
153 |
154 | self.assert_selects(
155 | self.tree.find_all('a', string=re.compile("tag")),
156 | ['First tag.', 'Nested tag.'])
157 |
158 |
159 | def test_find_all_on_non_root_element(self):
160 | # You can call find_all on any node, not just the root.
161 | self.assert_selects(self.tree.c.find_all('a'), ['Nested tag.'])
162 |
163 | def test_calling_element_invokes_find_all(self):
164 | self.assert_selects(self.tree('a'), ['First tag.', 'Nested tag.'])
165 |
166 | def test_find_all_by_tag_strainer(self):
167 | self.assert_selects(
168 | self.tree.find_all(SoupStrainer('a')),
169 | ['First tag.', 'Nested tag.'])
170 |
171 | def test_find_all_by_tag_names(self):
172 | self.assert_selects(
173 | self.tree.find_all(['a', 'b']),
174 | ['First tag.', 'Second tag.', 'Nested tag.'])
175 |
176 | def test_find_all_by_tag_dict(self):
177 | self.assert_selects(
178 | self.tree.find_all({'a' : True, 'b' : True}),
179 | ['First tag.', 'Second tag.', 'Nested tag.'])
180 |
181 | def test_find_all_by_tag_re(self):
182 | self.assert_selects(
183 | self.tree.find_all(re.compile('^[ab]$')),
184 | ['First tag.', 'Second tag.', 'Nested tag.'])
185 |
186 | def test_find_all_with_tags_matching_method(self):
187 | # You can define an oracle method that determines whether
188 | # a tag matches the search.
189 | def id_matches_name(tag):
190 | return tag.name == tag.get('id')
191 |
192 | tree = self.soup("""<a id="a">Match 1.</a>
193 | <a id="1">Does not match.</a>
194 | <b id="b">Match 2.</a>""")
195 |
196 | self.assert_selects(
197 | tree.find_all(id_matches_name), ["Match 1.", "Match 2."])
198 |
199 | def test_find_with_multi_valued_attribute(self):
200 | soup = self.soup(
201 | "<div class='a b'>1</div><div class='a c'>2</div><div class='a d'>3</div>"
202 | )
203 | r1 = soup.find('div', 'a d');
204 | r2 = soup.find('div', re.compile(r'a d'));
205 | r3, r4 = soup.find_all('div', ['a b', 'a d']);
206 | assert '3' == r1.string
207 | assert '3' == r2.string
208 | assert '1' == r3.string
209 | assert '3' == r4.string
210 |
211 |
212 | class TestFindAllByAttribute(SoupTest):
213 |
214 | def test_find_all_by_attribute_name(self):
215 | # You can pass in keyword arguments to find_all to search by
216 | # attribute.
217 | tree = self.soup("""
218 | <a id="first">Matching a.</a>
219 | <a id="second">
220 | Non-matching <b id="first">Matching b.</b>a.
221 | </a>""")
222 | self.assert_selects(tree.find_all(id='first'),
223 | ["Matching a.", "Matching b."])
224 |
225 | def test_find_all_by_utf8_attribute_value(self):
226 | peace = "םולש".encode("utf8")
227 | data = '<a title="םולש"></a>'.encode("utf8")
228 | soup = self.soup(data)
229 | assert [soup.a] == soup.find_all(title=peace)
230 | assert [soup.a] == soup.find_all(title=peace.decode("utf8"))
231 | assert [soup.a], soup.find_all(title=[peace, "something else"])
232 |
233 | def test_find_all_by_attribute_dict(self):
234 | # You can pass in a dictionary as the argument 'attrs'. This
235 | # lets you search for attributes like 'name' (a fixed argument
236 | # to find_all) and 'class' (a reserved word in Python.)
237 | tree = self.soup("""
238 | <a name="name1" class="class1">Name match.</a>
239 | <a name="name2" class="class2">Class match.</a>
240 | <a name="name3" class="class3">Non-match.</a>
241 | <name1>A tag called 'name1'.</name1>
242 | """)
243 |
244 | # This doesn't do what you want.
245 | self.assert_selects(tree.find_all(name='name1'),
246 | ["A tag called 'name1'."])
247 | # This does what you want.
248 | self.assert_selects(tree.find_all(attrs={'name' : 'name1'}),
249 | ["Name match."])
250 |
251 | self.assert_selects(tree.find_all(attrs={'class' : 'class2'}),
252 | ["Class match."])
253 |
254 | def test_find_all_by_class(self):
255 | tree = self.soup("""
256 | <a class="1">Class 1.</a>
257 | <a class="2">Class 2.</a>
258 | <b class="1">Class 1.</b>
259 | <c class="3 4">Class 3 and 4.</c>
260 | """)
261 |
262 | # Passing in the class_ keyword argument will search against
263 | # the 'class' attribute.
264 | self.assert_selects(tree.find_all('a', class_='1'), ['Class 1.'])
265 | self.assert_selects(tree.find_all('c', class_='3'), ['Class 3 and 4.'])
266 | self.assert_selects(tree.find_all('c', class_='4'), ['Class 3 and 4.'])
267 |
268 | # Passing in a string to 'attrs' will also search the CSS class.
269 | self.assert_selects(tree.find_all('a', '1'), ['Class 1.'])
270 | self.assert_selects(tree.find_all(attrs='1'), ['Class 1.', 'Class 1.'])
271 | self.assert_selects(tree.find_all('c', '3'), ['Class 3 and 4.'])
272 | self.assert_selects(tree.find_all('c', '4'), ['Class 3 and 4.'])
273 |
274 | def test_find_by_class_when_multiple_classes_present(self):
275 | tree = self.soup("<gar class='foo bar'>Found it</gar>")
276 |
277 | f = tree.find_all("gar", class_=re.compile("o"))
278 | self.assert_selects(f, ["Found it"])
279 |
280 | f = tree.find_all("gar", class_=re.compile("a"))
281 | self.assert_selects(f, ["Found it"])
282 |
283 | # If the search fails to match the individual strings "foo" and "bar",
284 | # it will be tried against the combined string "foo bar".
285 | f = tree.find_all("gar", class_=re.compile("o b"))
286 | self.assert_selects(f, ["Found it"])
287 |
288 | def test_find_all_with_non_dictionary_for_attrs_finds_by_class(self):
289 | soup = self.soup("<a class='bar'>Found it</a>")
290 |
291 | self.assert_selects(soup.find_all("a", re.compile("ba")), ["Found it"])
292 |
293 | def big_attribute_value(value):
294 | return len(value) > 3
295 |
296 | self.assert_selects(soup.find_all("a", big_attribute_value), [])
297 |
298 | def small_attribute_value(value):
299 | return len(value) <= 3
300 |
301 | self.assert_selects(
302 | soup.find_all("a", small_attribute_value), ["Found it"])
303 |
304 | def test_find_all_with_string_for_attrs_finds_multiple_classes(self):
305 | soup = self.soup('<a class="foo bar"></a><a class="foo"></a>')
306 | a, a2 = soup.find_all("a")
307 | assert [a, a2], soup.find_all("a", "foo")
308 | assert [a], soup.find_all("a", "bar")
309 |
310 | # If you specify the class as a string that contains a
311 | # space, only that specific value will be found.
312 | assert [a] == soup.find_all("a", class_="foo bar")
313 | assert [a] == soup.find_all("a", "foo bar")
314 | assert [] == soup.find_all("a", "bar foo")
315 |
316 | def test_find_all_by_attribute_soupstrainer(self):
317 | tree = self.soup("""
318 | <a id="first">Match.</a>
319 | <a id="second">Non-match.</a>""")
320 |
321 | strainer = SoupStrainer(attrs={'id' : 'first'})
322 | self.assert_selects(tree.find_all(strainer), ['Match.'])
323 |
324 | def test_find_all_with_missing_attribute(self):
325 | # You can pass in None as the value of an attribute to find_all.
326 | # This will match tags that do not have that attribute set.
327 | tree = self.soup("""<a id="1">ID present.</a>
328 | <a>No ID present.</a>
329 | <a id="">ID is empty.</a>""")
330 | self.assert_selects(tree.find_all('a', id=None), ["No ID present."])
331 |
332 | def test_find_all_with_defined_attribute(self):
333 | # You can pass in None as the value of an attribute to find_all.
334 | # This will match tags that have that attribute set to any value.
335 | tree = self.soup("""<a id="1">ID present.</a>
336 | <a>No ID present.</a>
337 | <a id="">ID is empty.</a>""")
338 | self.assert_selects(
339 | tree.find_all(id=True), ["ID present.", "ID is empty."])
340 |
341 | def test_find_all_with_numeric_attribute(self):
342 | # If you search for a number, it's treated as a string.
343 | tree = self.soup("""<a id=1>Unquoted attribute.</a>
344 | <a id="1">Quoted attribute.</a>""")
345 |
346 | expected = ["Unquoted attribute.", "Quoted attribute."]
347 | self.assert_selects(tree.find_all(id=1), expected)
348 | self.assert_selects(tree.find_all(id="1"), expected)
349 |
350 | def test_find_all_with_list_attribute_values(self):
351 | # You can pass a list of attribute values instead of just one,
352 | # and you'll get tags that match any of the values.
353 | tree = self.soup("""<a id="1">1</a>
354 | <a id="2">2</a>
355 | <a id="3">3</a>
356 | <a>No ID.</a>""")
357 | self.assert_selects(tree.find_all(id=["1", "3", "4"]),
358 | ["1", "3"])
359 |
360 | def test_find_all_with_regular_expression_attribute_value(self):
361 | # You can pass a regular expression as an attribute value, and
362 | # you'll get tags whose values for that attribute match the
363 | # regular expression.
364 | tree = self.soup("""<a id="a">One a.</a>
365 | <a id="aa">Two as.</a>
366 | <a id="ab">Mixed as and bs.</a>
367 | <a id="b">One b.</a>
368 | <a>No ID.</a>""")
369 |
370 | self.assert_selects(tree.find_all(id=re.compile("^a+$")),
371 | ["One a.", "Two as."])
372 |
373 | def test_find_by_name_and_containing_string(self):
374 | soup = self.soup("<b>foo</b><b>bar</b><a>foo</a>")
375 | a = soup.a
376 |
377 | assert [a] == soup.find_all("a", string="foo")
378 | assert [] == soup.find_all("a", string="bar")
379 |
380 | def test_find_by_name_and_containing_string_when_string_is_buried(self):
381 | soup = self.soup("<a>foo</a><a><b><c>foo</c></b></a>")
382 | assert soup.find_all("a") == soup.find_all("a", string="foo")
383 |
384 | def test_find_by_attribute_and_containing_string(self):
385 | soup = self.soup('<b id="1">foo</b><a id="2">foo</a>')
386 | a = soup.a
387 |
388 | assert [a] == soup.find_all(id=2, string="foo")
389 | assert [] == soup.find_all(id=1, string="bar")
390 |
391 |
392 | class TestSmooth(SoupTest):
393 | """Test Tag.smooth."""
394 |
395 | def test_smooth(self):
396 | soup = self.soup("<div>a</div>")
397 | div = soup.div
398 | div.append("b")
399 | div.append("c")
400 | div.append(Comment("Comment 1"))
401 | div.append(Comment("Comment 2"))
402 | div.append("d")
403 | builder = self.default_builder()
404 | span = Tag(soup, builder, 'span')
405 | span.append('1')
406 | span.append('2')
407 | div.append(span)
408 |
409 | # At this point the tree has a bunch of adjacent
410 | # NavigableStrings. This is normal, but it has no meaning in
411 | # terms of HTML, so we may want to smooth things out for
412 | # output.
413 |
414 | # Since the <span> tag has two children, its .string is None.
415 | assert None == div.span.string
416 |
417 | assert 7 == len(div.contents)
418 | div.smooth()
419 | assert 5 == len(div.contents)
420 |
421 | # The three strings at the beginning of div.contents have been
422 | # merged into on string.
423 | #
424 | assert 'abc' == div.contents[0]
425 |
426 | # The call is recursive -- the <span> tag was also smoothed.
427 | assert '12' == div.span.string
428 |
429 | # The two comments have _not_ been merged, even though
430 | # comments are strings. Merging comments would change the
431 | # meaning of the HTML.
432 | assert 'Comment 1' == div.contents[1]
433 | assert 'Comment 2' == div.contents[2]
434 |
435 |
436 | class TestIndex(SoupTest):
437 | """Test Tag.index"""
438 | def test_index(self):
439 | tree = self.soup("""<div>
440 | <a>Identical</a>
441 | <b>Not identical</b>
442 | <a>Identical</a>
443 |
444 | <c><d>Identical with child</d></c>
445 | <b>Also not identical</b>
446 | <c><d>Identical with child</d></c>
447 | </div>""")
448 | div = tree.div
449 | for i, element in enumerate(div.contents):
450 | assert i == div.index(element)
451 | with pytest.raises(ValueError):
452 | tree.index(1)
453 |
454 |
455 | class TestParentOperations(SoupTest):
456 | """Test navigation and searching through an element's parents."""
457 |
458 | def setup_method(self):
459 | self.tree = self.soup('''<ul id="empty"></ul>
460 | <ul id="top">
461 | <ul id="middle">
462 | <ul id="bottom">
463 | <b>Start here</b>
464 | </ul>
465 | </ul>''')
466 | self.start = self.tree.b
467 |
468 |
469 | def test_parent(self):
470 | assert self.start.parent['id'] == 'bottom'
471 | assert self.start.parent.parent['id'] == 'middle'
472 | assert self.start.parent.parent.parent['id'] == 'top'
473 |
474 | def test_parent_of_top_tag_is_soup_object(self):
475 | top_tag = self.tree.contents[0]
476 | assert top_tag.parent == self.tree
477 |
478 | def test_soup_object_has_no_parent(self):
479 | assert None == self.tree.parent
480 |
481 | def test_find_parents(self):
482 | self.assert_selects_ids(
483 | self.start.find_parents('ul'), ['bottom', 'middle', 'top'])
484 | self.assert_selects_ids(
485 | self.start.find_parents('ul', id="middle"), ['middle'])
486 |
487 | def test_find_parent(self):
488 | assert self.start.find_parent('ul')['id'] == 'bottom'
489 | assert self.start.find_parent('ul', id='top')['id'] == 'top'
490 |
491 | def test_parent_of_text_element(self):
492 | text = self.tree.find(string="Start here")
493 | assert text.parent.name == 'b'
494 |
495 | def test_text_element_find_parent(self):
496 | text = self.tree.find(string="Start here")
497 | assert text.find_parent('ul')['id'] == 'bottom'
498 |
499 | def test_parent_generator(self):
500 | parents = [parent['id'] for parent in self.start.parents
501 | if parent is not None and 'id' in parent.attrs]
502 | assert parents, ['bottom', 'middle' == 'top']
503 |
504 |
505 | class ProximityTest(SoupTest):
506 |
507 | def setup_method(self):
508 | self.tree = self.soup(
509 | '<html id="start"><head></head><body><b id="1">One</b><b id="2">Two</b><b id="3">Three</b></body></html>')
510 |
511 |
512 | class TestNextOperations(ProximityTest):
513 |
514 | def setup_method(self):
515 | super(TestNextOperations, self).setup_method()
516 | self.start = self.tree.b
517 |
518 | def test_next(self):
519 | assert self.start.next_element == "One"
520 | assert self.start.next_element.next_element['id'] == "2"
521 |
522 | def test_next_of_last_item_is_none(self):
523 | last = self.tree.find(string="Three")
524 | assert last.next_element == None
525 |
526 | def test_next_of_root_is_none(self):
527 | # The document root is outside the next/previous chain.
528 | assert self.tree.next_element == None
529 |
530 | def test_find_all_next(self):
531 | self.assert_selects(self.start.find_all_next('b'), ["Two", "Three"])
532 | self.start.find_all_next(id=3)
533 | self.assert_selects(self.start.find_all_next(id=3), ["Three"])
534 |
535 | def test_find_next(self):
536 | assert self.start.find_next('b')['id'] == '2'
537 | assert self.start.find_next(string="Three") == "Three"
538 |
539 | def test_find_next_for_text_element(self):
540 | text = self.tree.find(string="One")
541 | assert text.find_next("b").string == "Two"
542 | self.assert_selects(text.find_all_next("b"), ["Two", "Three"])
543 |
544 | def test_next_generator(self):
545 | start = self.tree.find(string="Two")
546 | successors = [node for node in start.next_elements]
547 | # There are two successors: the final <b> tag and its text contents.
548 | tag, contents = successors
549 | assert tag['id'] == '3'
550 | assert contents == "Three"
551 |
552 | class TestPreviousOperations(ProximityTest):
553 |
554 | def setup_method(self):
555 | super(TestPreviousOperations, self).setup_method()
556 | self.end = self.tree.find(string="Three")
557 |
558 | def test_previous(self):
559 | assert self.end.previous_element['id'] == "3"
560 | assert self.end.previous_element.previous_element == "Two"
561 |
562 | def test_previous_of_first_item_is_none(self):
563 | first = self.tree.find('html')
564 | assert first.previous_element == None
565 |
566 | def test_previous_of_root_is_none(self):
567 | # The document root is outside the next/previous chain.
568 | assert self.tree.previous_element == None
569 |
570 | def test_find_all_previous(self):
571 | # The <b> tag containing the "Three" node is the predecessor
572 | # of the "Three" node itself, which is why "Three" shows up
573 | # here.
574 | self.assert_selects(
575 | self.end.find_all_previous('b'), ["Three", "Two", "One"])
576 | self.assert_selects(self.end.find_all_previous(id=1), ["One"])
577 |
578 | def test_find_previous(self):
579 | assert self.end.find_previous('b')['id'] == '3'
580 | assert self.end.find_previous(string="One") == "One"
581 |
582 | def test_find_previous_for_text_element(self):
583 | text = self.tree.find(string="Three")
584 | assert text.find_previous("b").string == "Three"
585 | self.assert_selects(
586 | text.find_all_previous("b"), ["Three", "Two", "One"])
587 |
588 | def test_previous_generator(self):
589 | start = self.tree.find(string="One")
590 | predecessors = [node for node in start.previous_elements]
591 |
592 | # There are four predecessors: the <b> tag containing "One"
593 | # the <body> tag, the <head> tag, and the <html> tag.
594 | b, body, head, html = predecessors
595 | assert b['id'] == '1'
596 | assert body.name == "body"
597 | assert head.name == "head"
598 | assert html.name == "html"
599 |
600 |
601 | class SiblingTest(SoupTest):
602 |
603 | def setup_method(self):
604 | markup = '''<html>
605 | <span id="1">
606 | <span id="1.1"></span>
607 | </span>
608 | <span id="2">
609 | <span id="2.1"></span>
610 | </span>
611 | <span id="3">
612 | <span id="3.1"></span>
613 | </span>
614 | <span id="4"></span>
615 | </html>'''
616 | # All that whitespace looks good but makes the tests more
617 | # difficult. Get rid of it.
618 | markup = re.compile(r"\n\s*").sub("", markup)
619 | self.tree = self.soup(markup)
620 |
621 |
622 | class TestNextSibling(SiblingTest):
623 |
624 | def setup_method(self):
625 | super(TestNextSibling, self).setup_method()
626 | self.start = self.tree.find(id="1")
627 |
628 | def test_next_sibling_of_root_is_none(self):
629 | assert self.tree.next_sibling == None
630 |
631 | def test_next_sibling(self):
632 | assert self.start.next_sibling['id'] == '2'
633 | assert self.start.next_sibling.next_sibling['id'] == '3'
634 |
635 | # Note the difference between next_sibling and next_element.
636 | assert self.start.next_element['id'] == '1.1'
637 |
638 | def test_next_sibling_may_not_exist(self):
639 | assert self.tree.html.next_sibling == None
640 |
641 | nested_span = self.tree.find(id="1.1")
642 | assert nested_span.next_sibling == None
643 |
644 | last_span = self.tree.find(id="4")
645 | assert last_span.next_sibling == None
646 |
647 | def test_find_next_sibling(self):
648 | assert self.start.find_next_sibling('span')['id'] == '2'
649 |
650 | def test_next_siblings(self):
651 | self.assert_selects_ids(self.start.find_next_siblings("span"),
652 | ['2', '3', '4'])
653 |
654 | self.assert_selects_ids(self.start.find_next_siblings(id='3'), ['3'])
655 |
656 | def test_next_sibling_for_text_element(self):
657 | soup = self.soup("Foo<b>bar</b>baz")
658 | start = soup.find(string="Foo")
659 | assert start.next_sibling.name == 'b'
660 | assert start.next_sibling.next_sibling == 'baz'
661 |
662 | self.assert_selects(start.find_next_siblings('b'), ['bar'])
663 | assert start.find_next_sibling(string="baz") == "baz"
664 | assert start.find_next_sibling(string="nonesuch") == None
665 |
666 |
667 | class TestPreviousSibling(SiblingTest):
668 |
669 | def setup_method(self):
670 | super(TestPreviousSibling, self).setup_method()
671 | self.end = self.tree.find(id="4")
672 |
673 | def test_previous_sibling_of_root_is_none(self):
674 | assert self.tree.previous_sibling == None
675 |
676 | def test_previous_sibling(self):
677 | assert self.end.previous_sibling['id'] == '3'
678 | assert self.end.previous_sibling.previous_sibling['id'] == '2'
679 |
680 | # Note the difference between previous_sibling and previous_element.
681 | assert self.end.previous_element['id'] == '3.1'
682 |
683 | def test_previous_sibling_may_not_exist(self):
684 | assert self.tree.html.previous_sibling == None
685 |
686 | nested_span = self.tree.find(id="1.1")
687 | assert nested_span.previous_sibling == None
688 |
689 | first_span = self.tree.find(id="1")
690 | assert first_span.previous_sibling == None
691 |
692 | def test_find_previous_sibling(self):
693 | assert self.end.find_previous_sibling('span')['id'] == '3'
694 |
695 | def test_previous_siblings(self):
696 | self.assert_selects_ids(self.end.find_previous_siblings("span"),
697 | ['3', '2', '1'])
698 |
699 | self.assert_selects_ids(self.end.find_previous_siblings(id='1'), ['1'])
700 |
701 | def test_previous_sibling_for_text_element(self):
702 | soup = self.soup("Foo<b>bar</b>baz")
703 | start = soup.find(string="baz")
704 | assert start.previous_sibling.name == 'b'
705 | assert start.previous_sibling.previous_sibling == 'Foo'
706 |
707 | self.assert_selects(start.find_previous_siblings('b'), ['bar'])
708 | assert start.find_previous_sibling(string="Foo") == "Foo"
709 | assert start.find_previous_sibling(string="nonesuch") == None
710 |
711 |
712 | class TestTreeModification(SoupTest):
713 |
714 | def test_attribute_modification(self):
715 | soup = self.soup('<a id="1"></a>')
716 | soup.a['id'] = 2
717 | assert soup.decode() == self.document_for('<a id="2"></a>')
718 | del(soup.a['id'])
719 | assert soup.decode() == self.document_for('<a></a>')
720 | soup.a['id2'] = 'foo'
721 | assert soup.decode() == self.document_for('<a id2="foo"></a>')
722 |
723 | def test_new_tag_creation(self):
724 | builder = builder_registry.lookup('html')()
725 | soup = self.soup("<body></body>", builder=builder)
726 | a = Tag(soup, builder, 'a')
727 | ol = Tag(soup, builder, 'ol')
728 | a['href'] = 'http://foo.com/'
729 | soup.body.insert(0, a)
730 | soup.body.insert(1, ol)
731 | assert soup.body.encode() == b'<body><a href="http://foo.com/"></a><ol></ol></body>'
732 |
733 | def test_append_to_contents_moves_tag(self):
734 | doc = """<p id="1">Don't leave me <b>here</b>.</p>
735 | <p id="2">Don\'t leave!</p>"""
736 | soup = self.soup(doc)
737 | second_para = soup.find(id='2')
738 | bold = soup.b
739 |
740 | # Move the <b> tag to the end of the second paragraph.
741 | soup.find(id='2').append(soup.b)
742 |
743 | # The <b> tag is now a child of the second paragraph.
744 | assert bold.parent == second_para
745 |
746 | assert soup.decode() == self.document_for(
747 | '<p id="1">Don\'t leave me .</p>\n'
748 | '<p id="2">Don\'t leave!<b>here</b></p>'
749 | )
750 |
751 | def test_replace_with_returns_thing_that_was_replaced(self):
752 | text = "<a></a><b><c></c></b>"
753 | soup = self.soup(text)
754 | a = soup.a
755 | new_a = a.replace_with(soup.c)
756 | assert a == new_a
757 |
758 | def test_unwrap_returns_thing_that_was_replaced(self):
759 | text = "<a><b></b><c></c></a>"
760 | soup = self.soup(text)
761 | a = soup.a
762 | new_a = a.unwrap()
763 | assert a == new_a
764 |
765 | def test_replace_with_and_unwrap_give_useful_exception_when_tag_has_no_parent(self):
766 | soup = self.soup("<a><b>Foo</b></a><c>Bar</c>")
767 | a = soup.a
768 | a.extract()
769 | assert None == a.parent
770 | with pytest.raises(ValueError):
771 | a.unwrap()
772 | with pytest.raises(ValueError):
773 | a.replace_with(soup.c)
774 |
775 | def test_replace_tag_with_itself(self):
776 | text = "<a><b></b><c>Foo<d></d></c></a><a><e></e></a>"
777 | soup = self.soup(text)
778 | c = soup.c
779 | soup.c.replace_with(c)
780 | assert soup.decode() == self.document_for(text)
781 |
782 | def test_replace_tag_with_its_parent_raises_exception(self):
783 | text = "<a><b></b></a>"
784 | soup = self.soup(text)
785 | with pytest.raises(ValueError):
786 | soup.b.replace_with(soup.a)
787 |
788 | def test_insert_tag_into_itself_raises_exception(self):
789 | text = "<a><b></b></a>"
790 | soup = self.soup(text)
791 | with pytest.raises(ValueError):
792 | soup.a.insert(0, soup.a)
793 |
794 | def test_insert_beautifulsoup_object_inserts_children(self):
795 | """Inserting one BeautifulSoup object into another actually inserts all
796 | of its children -- you'll never combine BeautifulSoup objects.
797 | """
798 | soup = self.soup("<p>And now, a word:</p><p>And we're back.</p>")
799 |
800 | text = "<p>p2</p><p>p3</p>"
801 | to_insert = self.soup(text)
802 | soup.insert(1, to_insert)
803 |
804 | for i in soup.descendants:
805 | assert not isinstance(i, BeautifulSoup)
806 |
807 | p1, p2, p3, p4 = list(soup.children)
808 | assert "And now, a word:" == p1.string
809 | assert "p2" == p2.string
810 | assert "p3" == p3.string
811 | assert "And we're back." == p4.string
812 |
813 |
814 | def test_replace_with_maintains_next_element_throughout(self):
815 | soup = self.soup('<p><a>one</a><b>three</b></p>')
816 | a = soup.a
817 | b = a.contents[0]
818 | # Make it so the <a> tag has two text children.
819 | a.insert(1, "two")
820 |
821 | # Now replace each one with the empty string.
822 | left, right = a.contents
823 | left.replaceWith('')
824 | right.replaceWith('')
825 |
826 | # The <b> tag is still connected to the tree.
827 | assert "three" == soup.b.string
828 |
829 | def test_replace_final_node(self):
830 | soup = self.soup("<b>Argh!</b>")
831 | soup.find(string="Argh!").replace_with("Hooray!")
832 | new_text = soup.find(string="Hooray!")
833 | b = soup.b
834 | assert new_text.previous_element == b
835 | assert new_text.parent == b
836 | assert new_text.previous_element.next_element == new_text
837 | assert new_text.next_element == None
838 |
839 | def test_consecutive_text_nodes(self):
840 | # A builder should never create two consecutive text nodes,
841 | # but if you insert one next to another, Beautiful Soup will
842 | # handle it correctly.
843 | soup = self.soup("<a><b>Argh!</b><c></c></a>")
844 | soup.b.insert(1, "Hooray!")
845 |
846 | assert soup.decode() == self.document_for(
847 | "<a><b>Argh!Hooray!</b><c></c></a>"
848 | )
849 |
850 | new_text = soup.find(string="Hooray!")
851 | assert new_text.previous_element == "Argh!"
852 | assert new_text.previous_element.next_element == new_text
853 |
854 | assert new_text.previous_sibling == "Argh!"
855 | assert new_text.previous_sibling.next_sibling == new_text
856 |
857 | assert new_text.next_sibling == None
858 | assert new_text.next_element == soup.c
859 |
860 | def test_insert_string(self):
861 | soup = self.soup("<a></a>")
862 | soup.a.insert(0, "bar")
863 | soup.a.insert(0, "foo")
864 | # The string were added to the tag.
865 | assert ["foo", "bar"] == soup.a.contents
866 | # And they were converted to NavigableStrings.
867 | assert soup.a.contents[0].next_element == "bar"
868 |
869 | def test_insert_tag(self):
870 | builder = self.default_builder()
871 | soup = self.soup(
872 | "<a><b>Find</b><c>lady!</c><d></d></a>", builder=builder)
873 | magic_tag = Tag(soup, builder, 'magictag')
874 | magic_tag.insert(0, "the")
875 | soup.a.insert(1, magic_tag)
876 |
877 | assert soup.decode() == self.document_for(
878 | "<a><b>Find</b><magictag>the</magictag><c>lady!</c><d></d></a>"
879 | )
880 |
881 | # Make sure all the relationships are hooked up correctly.
882 | b_tag = soup.b
883 | assert b_tag.next_sibling == magic_tag
884 | assert magic_tag.previous_sibling == b_tag
885 |
886 | find = b_tag.find(string="Find")
887 | assert find.next_element == magic_tag
888 | assert magic_tag.previous_element == find
889 |
890 | c_tag = soup.c
891 | assert magic_tag.next_sibling == c_tag
892 | assert c_tag.previous_sibling == magic_tag
893 |
894 | the = magic_tag.find(string="the")
895 | assert the.parent == magic_tag
896 | assert the.next_element == c_tag
897 | assert c_tag.previous_element == the
898 |
899 | def test_append_child_thats_already_at_the_end(self):
900 | data = "<a><b></b></a>"
901 | soup = self.soup(data)
902 | soup.a.append(soup.b)
903 | assert data == soup.decode()
904 |
905 | def test_extend(self):
906 | data = "<a><b><c><d><e><f><g></g></f></e></d></c></b></a>"
907 | soup = self.soup(data)
908 | l = [soup.g, soup.f, soup.e, soup.d, soup.c, soup.b]
909 | soup.a.extend(l)
910 | assert "<a><g></g><f></f><e></e><d></d><c></c><b></b></a>" == soup.decode()
911 |
912 | @pytest.mark.parametrize(
913 | "get_tags", [lambda tag: tag, lambda tag: tag.contents]
914 | )
915 | def test_extend_with_another_tags_contents(self, get_tags):
916 | data = '<body><div id="d1"><a>1</a><a>2</a><a>3</a><a>4</a></div><div id="d2"></div></body>'
917 | soup = self.soup(data)
918 | d1 = soup.find('div', id='d1')
919 | d2 = soup.find('div', id='d2')
920 | tags = get_tags(d1)
921 | d2.extend(tags)
922 | assert '<div id="d1"></div>' == d1.decode()
923 | assert '<div id="d2"><a>1</a><a>2</a><a>3</a><a>4</a></div>' == d2.decode()
924 |
925 | def test_move_tag_to_beginning_of_parent(self):
926 | data = "<a><b></b><c></c><d></d></a>"
927 | soup = self.soup(data)
928 | soup.a.insert(0, soup.d)
929 | assert "<a><d></d><b></b><c></c></a>" == soup.decode()
930 |
931 | def test_insert_works_on_empty_element_tag(self):
932 | # This is a little strange, since most HTML parsers don't allow
933 | # markup like this to come through. But in general, we don't
934 | # know what the parser would or wouldn't have allowed, so
935 | # I'm letting this succeed for now.
936 | soup = self.soup("<br/>")
937 | soup.br.insert(1, "Contents")
938 | assert str(soup.br) == "<br>Contents</br>"
939 |
940 | def test_insert_before(self):
941 | soup = self.soup("<a>foo</a><b>bar</b>")
942 | soup.b.insert_before("BAZ")
943 | soup.a.insert_before("QUUX")
944 | assert soup.decode() == self.document_for(
945 | "QUUX<a>foo</a>BAZ<b>bar</b>"
946 | )
947 |
948 | soup.a.insert_before(soup.b)
949 | assert soup.decode() == self.document_for("QUUX<b>bar</b><a>foo</a>BAZ")
950 |
951 | # Can't insert an element before itself.
952 | b = soup.b
953 | with pytest.raises(ValueError):
954 | b.insert_before(b)
955 |
956 | # Can't insert before if an element has no parent.
957 | b.extract()
958 | with pytest.raises(ValueError):
959 | b.insert_before("nope")
960 |
961 | # Can insert an identical element
962 | soup = self.soup("<a>")
963 | soup.a.insert_before(soup.new_tag("a"))
964 |
965 | # TODO: OK but what happens?
966 |
967 | def test_insert_multiple_before(self):
968 | soup = self.soup("<a>foo</a><b>bar</b>")
969 | soup.b.insert_before("BAZ", " ", "QUUX")
970 | soup.a.insert_before("QUUX", " ", "BAZ")
971 | assert soup.decode() == self.document_for(
972 | "QUUX BAZ<a>foo</a>BAZ QUUX<b>bar</b>"
973 | )
974 |
975 | soup.a.insert_before(soup.b, "FOO")
976 | assert soup.decode() == self.document_for(
977 | "QUUX BAZ<b>bar</b>FOO<a>foo</a>BAZ QUUX"
978 | )
979 |
980 | def test_insert_after(self):
981 | soup = self.soup("<a>foo</a><b>bar</b>")
982 | soup.b.insert_after("BAZ")
983 | soup.a.insert_after("QUUX")
984 | assert soup.decode() == self.document_for(
985 | "<a>foo</a>QUUX<b>bar</b>BAZ"
986 | )
987 | soup.b.insert_after(soup.a)
988 | assert soup.decode() == self.document_for("QUUX<b>bar</b><a>foo</a>BAZ")
989 |
990 | # Can't insert an element after itself.
991 | b = soup.b
992 | with pytest.raises(ValueError):
993 | b.insert_after(b)
994 |
995 | # Can't insert after if an element has no parent.
996 | b.extract()
997 | with pytest.raises(ValueError):
998 | b.insert_after("nope")
999 |
1000 | # Can insert an identical element
1001 | soup = self.soup("<a>")
1002 | soup.a.insert_before(soup.new_tag("a"))
1003 |
1004 | # TODO: OK but what does it look like?
1005 |
1006 | def test_insert_multiple_after(self):
1007 | soup = self.soup("<a>foo</a><b>bar</b>")
1008 | soup.b.insert_after("BAZ", " ", "QUUX")
1009 | soup.a.insert_after("QUUX", " ", "BAZ")
1010 | assert soup.decode() == self.document_for(
1011 | "<a>foo</a>QUUX BAZ<b>bar</b>BAZ QUUX"
1012 | )
1013 | soup.b.insert_after(soup.a, "FOO ")
1014 | assert soup.decode() == self.document_for(
1015 | "QUUX BAZ<b>bar</b><a>foo</a>FOO BAZ QUUX"
1016 | )
1017 |
1018 | def test_insert_after_raises_exception_if_after_has_no_meaning(self):
1019 | soup = self.soup("")
1020 | tag = soup.new_tag("a")
1021 | string = soup.new_string("")
1022 | with pytest.raises(ValueError):
1023 | string.insert_after(tag)
1024 | with pytest.raises(NotImplementedError):
1025 | soup.insert_after(tag)
1026 | with pytest.raises(ValueError):
1027 | tag.insert_after(tag)
1028 |
1029 | def test_insert_before_raises_notimplementederror_if_before_has_no_meaning(self):
1030 | soup = self.soup("")
1031 | tag = soup.new_tag("a")
1032 | string = soup.new_string("")
1033 | with pytest.raises(ValueError):
1034 | string.insert_before(tag)
1035 | with pytest.raises(NotImplementedError):
1036 | soup.insert_before(tag)
1037 | with pytest.raises(ValueError):
1038 | tag.insert_before(tag)
1039 |
1040 | def test_replace_with(self):
1041 | soup = self.soup(
1042 | "<p>There's <b>no</b> business like <b>show</b> business</p>")
1043 | no, show = soup.find_all('b')
1044 | show.replace_with(no)
1045 | assert soup.decode() == self.document_for(
1046 | "<p>There's business like <b>no</b> business</p>"
1047 | )
1048 |
1049 | assert show.parent == None
1050 | assert no.parent == soup.p
1051 | assert no.next_element == "no"
1052 | assert no.next_sibling == " business"
1053 |
1054 | def test_replace_with_errors(self):
1055 | # Can't replace a tag that's not part of a tree.
1056 | a_tag = Tag(name="a")
1057 | with pytest.raises(ValueError):
1058 | a_tag.replace_with("won't work")
1059 |
1060 | # Can't replace a tag with its parent.
1061 | a_tag = self.soup("<a><b></b></a>").a
1062 | with pytest.raises(ValueError):
1063 | a_tag.b.replace_with(a_tag)
1064 |
1065 | # Or with a list that includes its parent.
1066 | with pytest.raises(ValueError):
1067 | a_tag.b.replace_with("string1", a_tag, "string2")
1068 |
1069 | def test_replace_with_multiple(self):
1070 | data = "<a><b></b><c></c></a>"
1071 | soup = self.soup(data)
1072 | d_tag = soup.new_tag("d")
1073 | d_tag.string = "Text In D Tag"
1074 | e_tag = soup.new_tag("e")
1075 | f_tag = soup.new_tag("f")
1076 | a_string = "Random Text"
1077 | soup.c.replace_with(d_tag, e_tag, a_string, f_tag)
1078 | assert soup.decode() == "<a><b></b><d>Text In D Tag</d><e></e>Random Text<f></f></a>"
1079 | assert soup.b.next_element == d_tag
1080 | assert d_tag.string.next_element==e_tag
1081 | assert e_tag.next_element.string == a_string
1082 | assert e_tag.next_element.next_element == f_tag
1083 |
1084 | def test_replace_first_child(self):
1085 | data = "<a><b></b><c></c></a>"
1086 | soup = self.soup(data)
1087 | soup.b.replace_with(soup.c)
1088 | assert "<a><c></c></a>" == soup.decode()
1089 |
1090 | def test_replace_last_child(self):
1091 | data = "<a><b></b><c></c></a>"
1092 | soup = self.soup(data)
1093 | soup.c.replace_with(soup.b)
1094 | assert "<a><b></b></a>" == soup.decode()
1095 |
1096 | def test_nested_tag_replace_with(self):
1097 | soup = self.soup(
1098 | """<a>We<b>reserve<c>the</c><d>right</d></b></a><e>to<f>refuse</f><g>service</g></e>""")
1099 |
1100 | # Replace the entire <b> tag and its contents ("reserve the
1101 | # right") with the <f> tag ("refuse").
1102 | remove_tag = soup.b
1103 | move_tag = soup.f
1104 | remove_tag.replace_with(move_tag)
1105 |
1106 | assert soup.decode() == self.document_for(
1107 | "<a>We<f>refuse</f></a><e>to<g>service</g></e>"
1108 | )
1109 |
1110 | # The <b> tag is now an orphan.
1111 | assert remove_tag.parent == None
1112 | assert remove_tag.find(string="right").next_element == None
1113 | assert remove_tag.previous_element == None
1114 | assert remove_tag.next_sibling == None
1115 | assert remove_tag.previous_sibling == None
1116 |
1117 | # The <f> tag is now connected to the <a> tag.
1118 | assert move_tag.parent == soup.a
1119 | assert move_tag.previous_element == "We"
1120 | assert move_tag.next_element.next_element == soup.e
1121 | assert move_tag.next_sibling == None
1122 |
1123 | # The gap where the <f> tag used to be has been mended, and
1124 | # the word "to" is now connected to the <g> tag.
1125 | to_text = soup.find(string="to")
1126 | g_tag = soup.g
1127 | assert to_text.next_element == g_tag
1128 | assert to_text.next_sibling == g_tag
1129 | assert g_tag.previous_element == to_text
1130 | assert g_tag.previous_sibling == to_text
1131 |
1132 | def test_unwrap(self):
1133 | tree = self.soup("""
1134 | <p>Unneeded <em>formatting</em> is unneeded</p>
1135 | """)
1136 | tree.em.unwrap()
1137 | assert tree.em == None
1138 | assert tree.p.text == "Unneeded formatting is unneeded"
1139 |
1140 | def test_wrap(self):
1141 | soup = self.soup("I wish I was bold.")
1142 | value = soup.string.wrap(soup.new_tag("b"))
1143 | assert value.decode() == "<b>I wish I was bold.</b>"
1144 | assert soup.decode() == self.document_for("<b>I wish I was bold.</b>")
1145 |
1146 | def test_wrap_extracts_tag_from_elsewhere(self):
1147 | soup = self.soup("<b></b>I wish I was bold.")
1148 | soup.b.next_sibling.wrap(soup.b)
1149 | assert soup.decode() == self.document_for("<b>I wish I was bold.</b>")
1150 |
1151 | def test_wrap_puts_new_contents_at_the_end(self):
1152 | soup = self.soup("<b>I like being bold.</b>I wish I was bold.")
1153 | soup.b.next_sibling.wrap(soup.b)
1154 | assert 2 == len(soup.b.contents)
1155 | assert soup.decode() == self.document_for(
1156 | "<b>I like being bold.I wish I was bold.</b>"
1157 | )
1158 |
1159 | def test_extract(self):
1160 | soup = self.soup(
1161 | '<html><body>Some content. <div id="nav">Nav crap</div> More content.</body></html>')
1162 |
1163 | assert len(soup.body.contents) == 3
1164 | extracted = soup.find(id="nav").extract()
1165 |
1166 | assert soup.decode() == "<html><body>Some content. More content.</body></html>"
1167 | assert extracted.decode() == '<div id="nav">Nav crap</div>'
1168 |
1169 | # The extracted tag is now an orphan.
1170 | assert len(soup.body.contents) == 2
1171 | assert extracted.parent == None
1172 | assert extracted.previous_element == None
1173 | assert extracted.next_element.next_element == None
1174 |
1175 | # The gap where the extracted tag used to be has been mended.
1176 | content_1 = soup.find(string="Some content. ")
1177 | content_2 = soup.find(string=" More content.")
1178 | assert content_1.next_element == content_2
1179 | assert content_1.next_sibling == content_2
1180 | assert content_2.previous_element == content_1
1181 | assert content_2.previous_sibling == content_1
1182 |
1183 | def test_extract_distinguishes_between_identical_strings(self):
1184 | soup = self.soup("<a>foo</a><b>bar</b>")
1185 | foo_1 = soup.a.string
1186 | bar_1 = soup.b.string
1187 | foo_2 = soup.new_string("foo")
1188 | bar_2 = soup.new_string("bar")
1189 | soup.a.append(foo_2)
1190 | soup.b.append(bar_2)
1191 |
1192 | # Now there are two identical strings in the <a> tag, and two
1193 | # in the <b> tag. Let's remove the first "foo" and the second
1194 | # "bar".
1195 | foo_1.extract()
1196 | bar_2.extract()
1197 | assert foo_2 == soup.a.string
1198 | assert bar_2 == soup.b.string
1199 |
1200 | def test_extract_multiples_of_same_tag(self):
1201 | soup = self.soup("""
1202 | <html>
1203 | <head>
1204 | <script>foo</script>
1205 | </head>
1206 | <body>
1207 | <script>bar</script>
1208 | <a></a>
1209 | </body>
1210 | <script>baz</script>
1211 | </html>""")
1212 | [soup.script.extract() for i in soup.find_all("script")]
1213 | assert "<body>\n\n<a></a>\n</body>" == str(soup.body)
1214 |
1215 |
1216 | def test_extract_works_when_element_is_surrounded_by_identical_strings(self):
1217 | soup = self.soup(
1218 | '<html>\n'
1219 | '<body>hi</body>\n'
1220 | '</html>')
1221 | soup.find('body').extract()
1222 | assert None == soup.find('body')
1223 |
1224 |
1225 | def test_clear(self):
1226 | """Tag.clear()"""
1227 | soup = self.soup("<p><a>String <em>Italicized</em></a> and another</p>")
1228 | # clear using extract()
1229 | a = soup.a
1230 | soup.p.clear()
1231 | assert len(soup.p.contents) == 0
1232 | assert hasattr(a, "contents")
1233 |
1234 | # clear using decompose()
1235 | em = a.em
1236 | a.clear(decompose=True)
1237 | assert 0 == len(em.contents)
1238 |
1239 |
1240 | def test_decompose(self):
1241 | # Test PageElement.decompose() and PageElement.decomposed
1242 | soup = self.soup("<p><a>String <em>Italicized</em></a></p><p>Another para</p>")
1243 | p1, p2 = soup.find_all('p')
1244 | a = p1.a
1245 | text = p1.em.string
1246 | for i in [p1, p2, a, text]:
1247 | assert False == i.decomposed
1248 |
1249 | # This sets p1 and everything beneath it to decomposed.
1250 | p1.decompose()
1251 | for i in [p1, a, text]:
1252 | assert True == i.decomposed
1253 | # p2 is unaffected.
1254 | assert False == p2.decomposed
1255 |
1256 | def test_string_set(self):
1257 | """Tag.string = 'string'"""
1258 | soup = self.soup("<a></a> <b><c></c></b>")
1259 | soup.a.string = "foo"
1260 | assert soup.a.contents == ["foo"]
1261 | soup.b.string = "bar"
1262 | assert soup.b.contents == ["bar"]
1263 |
1264 | def test_string_set_does_not_affect_original_string(self):
1265 | soup = self.soup("<a><b>foo</b><c>bar</c>")
1266 | soup.b.string = soup.c.string
1267 | assert soup.a.encode() == b"<a><b>bar</b><c>bar</c></a>"
1268 |
1269 | def test_set_string_preserves_class_of_string(self):
1270 | soup = self.soup("<a></a>")
1271 | cdata = CData("foo")
1272 | soup.a.string = cdata
1273 | assert isinstance(soup.a.string, CData)
1274 |
1275 |
1276 | class TestDeprecatedArguments(SoupTest):
1277 |
1278 | @pytest.mark.parametrize(
1279 | "method_name", [
1280 | "find", "find_all", "find_parent", "find_parents",
1281 | "find_next", "find_all_next", "find_previous",
1282 | "find_all_previous", "find_next_sibling", "find_next_siblings",
1283 | "find_previous_sibling", "find_previous_siblings",
1284 | ]
1285 | )
1286 | def test_find_type_method_string(self, method_name):
1287 | soup = self.soup("<a>some</a><b>markup</b>")
1288 | method = getattr(soup.b, method_name)
1289 | with warnings.catch_warnings(record=True) as w:
1290 | method(text='markup')
1291 | [warning] = w
1292 | assert warning.filename == __file__
1293 | msg = str(warning.message)
1294 | assert msg == "The 'text' argument to find()-type methods is deprecated. Use 'string' instead."
1295 |
1296 | def test_soupstrainer_constructor_string(self):
1297 | with warnings.catch_warnings(record=True) as w:
1298 | strainer = SoupStrainer(text="text")
1299 | assert strainer.text == 'text'
1300 | [warning] = w
1301 | msg = str(warning.message)
1302 | assert warning.filename == __file__
1303 | assert msg == "The 'text' argument to the SoupStrainer constructor is deprecated. Use 'string' instead."
1304 |
1305 |
```