This is page 144 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/pip/_vendor/distlib/locators.py:
--------------------------------------------------------------------------------
```python
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Copyright (C) 2012-2023 Vinay Sajip.
4 | # Licensed to the Python Software Foundation under a contributor agreement.
5 | # See LICENSE.txt and CONTRIBUTORS.txt.
6 | #
7 |
8 | import gzip
9 | from io import BytesIO
10 | import json
11 | import logging
12 | import os
13 | import posixpath
14 | import re
15 | try:
16 | import threading
17 | except ImportError: # pragma: no cover
18 | import dummy_threading as threading
19 | import zlib
20 |
21 | from . import DistlibException
22 | from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url,
23 | queue, quote, unescape, build_opener,
24 | HTTPRedirectHandler as BaseRedirectHandler, text_type,
25 | Request, HTTPError, URLError)
26 | from .database import Distribution, DistributionPath, make_dist
27 | from .metadata import Metadata, MetadataInvalidError
28 | from .util import (cached_property, ensure_slash, split_filename, get_project_data,
29 | parse_requirement, parse_name_and_version, ServerProxy,
30 | normalize_name)
31 | from .version import get_scheme, UnsupportedVersionError
32 | from .wheel import Wheel, is_compatible
33 |
34 | logger = logging.getLogger(__name__)
35 |
36 | HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)')
37 | CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
38 | HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
39 | DEFAULT_INDEX = 'https://pypi.org/pypi'
40 |
41 |
42 | def get_all_distribution_names(url=None):
43 | """
44 | Return all distribution names known by an index.
45 | :param url: The URL of the index.
46 | :return: A list of all known distribution names.
47 | """
48 | if url is None:
49 | url = DEFAULT_INDEX
50 | client = ServerProxy(url, timeout=3.0)
51 | try:
52 | return client.list_packages()
53 | finally:
54 | client('close')()
55 |
56 |
57 | class RedirectHandler(BaseRedirectHandler):
58 | """
59 | A class to work around a bug in some Python 3.2.x releases.
60 | """
61 | # There's a bug in the base version for some 3.2.x
62 | # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header
63 | # returns e.g. /abc, it bails because it says the scheme ''
64 | # is bogus, when actually it should use the request's
65 | # URL for the scheme. See Python issue #13696.
66 | def http_error_302(self, req, fp, code, msg, headers):
67 | # Some servers (incorrectly) return multiple Location headers
68 | # (so probably same goes for URI). Use first header.
69 | newurl = None
70 | for key in ('location', 'uri'):
71 | if key in headers:
72 | newurl = headers[key]
73 | break
74 | if newurl is None: # pragma: no cover
75 | return
76 | urlparts = urlparse(newurl)
77 | if urlparts.scheme == '':
78 | newurl = urljoin(req.get_full_url(), newurl)
79 | if hasattr(headers, 'replace_header'):
80 | headers.replace_header(key, newurl)
81 | else:
82 | headers[key] = newurl
83 | return BaseRedirectHandler.http_error_302(self, req, fp, code, msg,
84 | headers)
85 |
86 | http_error_301 = http_error_303 = http_error_307 = http_error_302
87 |
88 |
89 | class Locator(object):
90 | """
91 | A base class for locators - things that locate distributions.
92 | """
93 | source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz')
94 | binary_extensions = ('.egg', '.exe', '.whl')
95 | excluded_extensions = ('.pdf',)
96 |
97 | # A list of tags indicating which wheels you want to match. The default
98 | # value of None matches against the tags compatible with the running
99 | # Python. If you want to match other values, set wheel_tags on a locator
100 | # instance to a list of tuples (pyver, abi, arch) which you want to match.
101 | wheel_tags = None
102 |
103 | downloadable_extensions = source_extensions + ('.whl',)
104 |
105 | def __init__(self, scheme='default'):
106 | """
107 | Initialise an instance.
108 | :param scheme: Because locators look for most recent versions, they
109 | need to know the version scheme to use. This specifies
110 | the current PEP-recommended scheme - use ``'legacy'``
111 | if you need to support existing distributions on PyPI.
112 | """
113 | self._cache = {}
114 | self.scheme = scheme
115 | # Because of bugs in some of the handlers on some of the platforms,
116 | # we use our own opener rather than just using urlopen.
117 | self.opener = build_opener(RedirectHandler())
118 | # If get_project() is called from locate(), the matcher instance
119 | # is set from the requirement passed to locate(). See issue #18 for
120 | # why this can be useful to know.
121 | self.matcher = None
122 | self.errors = queue.Queue()
123 |
124 | def get_errors(self):
125 | """
126 | Return any errors which have occurred.
127 | """
128 | result = []
129 | while not self.errors.empty(): # pragma: no cover
130 | try:
131 | e = self.errors.get(False)
132 | result.append(e)
133 | except self.errors.Empty:
134 | continue
135 | self.errors.task_done()
136 | return result
137 |
138 | def clear_errors(self):
139 | """
140 | Clear any errors which may have been logged.
141 | """
142 | # Just get the errors and throw them away
143 | self.get_errors()
144 |
145 | def clear_cache(self):
146 | self._cache.clear()
147 |
148 | def _get_scheme(self):
149 | return self._scheme
150 |
151 | def _set_scheme(self, value):
152 | self._scheme = value
153 |
154 | scheme = property(_get_scheme, _set_scheme)
155 |
156 | def _get_project(self, name):
157 | """
158 | For a given project, get a dictionary mapping available versions to Distribution
159 | instances.
160 |
161 | This should be implemented in subclasses.
162 |
163 | If called from a locate() request, self.matcher will be set to a
164 | matcher for the requirement to satisfy, otherwise it will be None.
165 | """
166 | raise NotImplementedError('Please implement in the subclass')
167 |
168 | def get_distribution_names(self):
169 | """
170 | Return all the distribution names known to this locator.
171 | """
172 | raise NotImplementedError('Please implement in the subclass')
173 |
174 | def get_project(self, name):
175 | """
176 | For a given project, get a dictionary mapping available versions to Distribution
177 | instances.
178 |
179 | This calls _get_project to do all the work, and just implements a caching layer on top.
180 | """
181 | if self._cache is None: # pragma: no cover
182 | result = self._get_project(name)
183 | elif name in self._cache:
184 | result = self._cache[name]
185 | else:
186 | self.clear_errors()
187 | result = self._get_project(name)
188 | self._cache[name] = result
189 | return result
190 |
191 | def score_url(self, url):
192 | """
193 | Give an url a score which can be used to choose preferred URLs
194 | for a given project release.
195 | """
196 | t = urlparse(url)
197 | basename = posixpath.basename(t.path)
198 | compatible = True
199 | is_wheel = basename.endswith('.whl')
200 | is_downloadable = basename.endswith(self.downloadable_extensions)
201 | if is_wheel:
202 | compatible = is_compatible(Wheel(basename), self.wheel_tags)
203 | return (t.scheme == 'https', 'pypi.org' in t.netloc,
204 | is_downloadable, is_wheel, compatible, basename)
205 |
206 | def prefer_url(self, url1, url2):
207 | """
208 | Choose one of two URLs where both are candidates for distribution
209 | archives for the same version of a distribution (for example,
210 | .tar.gz vs. zip).
211 |
212 | The current implementation favours https:// URLs over http://, archives
213 | from PyPI over those from other locations, wheel compatibility (if a
214 | wheel) and then the archive name.
215 | """
216 | result = url2
217 | if url1:
218 | s1 = self.score_url(url1)
219 | s2 = self.score_url(url2)
220 | if s1 > s2:
221 | result = url1
222 | if result != url2:
223 | logger.debug('Not replacing %r with %r', url1, url2)
224 | else:
225 | logger.debug('Replacing %r with %r', url1, url2)
226 | return result
227 |
228 | def split_filename(self, filename, project_name):
229 | """
230 | Attempt to split a filename in project name, version and Python version.
231 | """
232 | return split_filename(filename, project_name)
233 |
234 | def convert_url_to_download_info(self, url, project_name):
235 | """
236 | See if a URL is a candidate for a download URL for a project (the URL
237 | has typically been scraped from an HTML page).
238 |
239 | If it is, a dictionary is returned with keys "name", "version",
240 | "filename" and "url"; otherwise, None is returned.
241 | """
242 | def same_project(name1, name2):
243 | return normalize_name(name1) == normalize_name(name2)
244 |
245 | result = None
246 | scheme, netloc, path, params, query, frag = urlparse(url)
247 | if frag.lower().startswith('egg='): # pragma: no cover
248 | logger.debug('%s: version hint in fragment: %r',
249 | project_name, frag)
250 | m = HASHER_HASH.match(frag)
251 | if m:
252 | algo, digest = m.groups()
253 | else:
254 | algo, digest = None, None
255 | origpath = path
256 | if path and path[-1] == '/': # pragma: no cover
257 | path = path[:-1]
258 | if path.endswith('.whl'):
259 | try:
260 | wheel = Wheel(path)
261 | if not is_compatible(wheel, self.wheel_tags):
262 | logger.debug('Wheel not compatible: %s', path)
263 | else:
264 | if project_name is None:
265 | include = True
266 | else:
267 | include = same_project(wheel.name, project_name)
268 | if include:
269 | result = {
270 | 'name': wheel.name,
271 | 'version': wheel.version,
272 | 'filename': wheel.filename,
273 | 'url': urlunparse((scheme, netloc, origpath,
274 | params, query, '')),
275 | 'python-version': ', '.join(
276 | ['.'.join(list(v[2:])) for v in wheel.pyver]),
277 | }
278 | except Exception: # pragma: no cover
279 | logger.warning('invalid path for wheel: %s', path)
280 | elif not path.endswith(self.downloadable_extensions): # pragma: no cover
281 | logger.debug('Not downloadable: %s', path)
282 | else: # downloadable extension
283 | path = filename = posixpath.basename(path)
284 | for ext in self.downloadable_extensions:
285 | if path.endswith(ext):
286 | path = path[:-len(ext)]
287 | t = self.split_filename(path, project_name)
288 | if not t: # pragma: no cover
289 | logger.debug('No match for project/version: %s', path)
290 | else:
291 | name, version, pyver = t
292 | if not project_name or same_project(project_name, name):
293 | result = {
294 | 'name': name,
295 | 'version': version,
296 | 'filename': filename,
297 | 'url': urlunparse((scheme, netloc, origpath,
298 | params, query, '')),
299 | }
300 | if pyver: # pragma: no cover
301 | result['python-version'] = pyver
302 | break
303 | if result and algo:
304 | result['%s_digest' % algo] = digest
305 | return result
306 |
307 | def _get_digest(self, info):
308 | """
309 | Get a digest from a dictionary by looking at a "digests" dictionary
310 | or keys of the form 'algo_digest'.
311 |
312 | Returns a 2-tuple (algo, digest) if found, else None. Currently
313 | looks only for SHA256, then MD5.
314 | """
315 | result = None
316 | if 'digests' in info:
317 | digests = info['digests']
318 | for algo in ('sha256', 'md5'):
319 | if algo in digests:
320 | result = (algo, digests[algo])
321 | break
322 | if not result:
323 | for algo in ('sha256', 'md5'):
324 | key = '%s_digest' % algo
325 | if key in info:
326 | result = (algo, info[key])
327 | break
328 | return result
329 |
330 | def _update_version_data(self, result, info):
331 | """
332 | Update a result dictionary (the final result from _get_project) with a
333 | dictionary for a specific version, which typically holds information
334 | gleaned from a filename or URL for an archive for the distribution.
335 | """
336 | name = info.pop('name')
337 | version = info.pop('version')
338 | if version in result:
339 | dist = result[version]
340 | md = dist.metadata
341 | else:
342 | dist = make_dist(name, version, scheme=self.scheme)
343 | md = dist.metadata
344 | dist.digest = digest = self._get_digest(info)
345 | url = info['url']
346 | result['digests'][url] = digest
347 | if md.source_url != info['url']:
348 | md.source_url = self.prefer_url(md.source_url, url)
349 | result['urls'].setdefault(version, set()).add(url)
350 | dist.locator = self
351 | result[version] = dist
352 |
353 | def locate(self, requirement, prereleases=False):
354 | """
355 | Find the most recent distribution which matches the given
356 | requirement.
357 |
358 | :param requirement: A requirement of the form 'foo (1.0)' or perhaps
359 | 'foo (>= 1.0, < 2.0, != 1.3)'
360 | :param prereleases: If ``True``, allow pre-release versions
361 | to be located. Otherwise, pre-release versions
362 | are not returned.
363 | :return: A :class:`Distribution` instance, or ``None`` if no such
364 | distribution could be located.
365 | """
366 | result = None
367 | r = parse_requirement(requirement)
368 | if r is None: # pragma: no cover
369 | raise DistlibException('Not a valid requirement: %r' % requirement)
370 | scheme = get_scheme(self.scheme)
371 | self.matcher = matcher = scheme.matcher(r.requirement)
372 | logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__)
373 | versions = self.get_project(r.name)
374 | if len(versions) > 2: # urls and digests keys are present
375 | # sometimes, versions are invalid
376 | slist = []
377 | vcls = matcher.version_class
378 | for k in versions:
379 | if k in ('urls', 'digests'):
380 | continue
381 | try:
382 | if not matcher.match(k):
383 | pass # logger.debug('%s did not match %r', matcher, k)
384 | else:
385 | if prereleases or not vcls(k).is_prerelease:
386 | slist.append(k)
387 | except Exception: # pragma: no cover
388 | logger.warning('error matching %s with %r', matcher, k)
389 | pass # slist.append(k)
390 | if len(slist) > 1:
391 | slist = sorted(slist, key=scheme.key)
392 | if slist:
393 | logger.debug('sorted list: %s', slist)
394 | version = slist[-1]
395 | result = versions[version]
396 | if result:
397 | if r.extras:
398 | result.extras = r.extras
399 | result.download_urls = versions.get('urls', {}).get(version, set())
400 | d = {}
401 | sd = versions.get('digests', {})
402 | for url in result.download_urls:
403 | if url in sd: # pragma: no cover
404 | d[url] = sd[url]
405 | result.digests = d
406 | self.matcher = None
407 | return result
408 |
409 |
410 | class PyPIRPCLocator(Locator):
411 | """
412 | This locator uses XML-RPC to locate distributions. It therefore
413 | cannot be used with simple mirrors (that only mirror file content).
414 | """
415 | def __init__(self, url, **kwargs):
416 | """
417 | Initialise an instance.
418 |
419 | :param url: The URL to use for XML-RPC.
420 | :param kwargs: Passed to the superclass constructor.
421 | """
422 | super(PyPIRPCLocator, self).__init__(**kwargs)
423 | self.base_url = url
424 | self.client = ServerProxy(url, timeout=3.0)
425 |
426 | def get_distribution_names(self):
427 | """
428 | Return all the distribution names known to this locator.
429 | """
430 | return set(self.client.list_packages())
431 |
432 | def _get_project(self, name):
433 | result = {'urls': {}, 'digests': {}}
434 | versions = self.client.package_releases(name, True)
435 | for v in versions:
436 | urls = self.client.release_urls(name, v)
437 | data = self.client.release_data(name, v)
438 | metadata = Metadata(scheme=self.scheme)
439 | metadata.name = data['name']
440 | metadata.version = data['version']
441 | metadata.license = data.get('license')
442 | metadata.keywords = data.get('keywords', [])
443 | metadata.summary = data.get('summary')
444 | dist = Distribution(metadata)
445 | if urls:
446 | info = urls[0]
447 | metadata.source_url = info['url']
448 | dist.digest = self._get_digest(info)
449 | dist.locator = self
450 | result[v] = dist
451 | for info in urls:
452 | url = info['url']
453 | digest = self._get_digest(info)
454 | result['urls'].setdefault(v, set()).add(url)
455 | result['digests'][url] = digest
456 | return result
457 |
458 |
459 | class PyPIJSONLocator(Locator):
460 | """
461 | This locator uses PyPI's JSON interface. It's very limited in functionality
462 | and probably not worth using.
463 | """
464 | def __init__(self, url, **kwargs):
465 | super(PyPIJSONLocator, self).__init__(**kwargs)
466 | self.base_url = ensure_slash(url)
467 |
468 | def get_distribution_names(self):
469 | """
470 | Return all the distribution names known to this locator.
471 | """
472 | raise NotImplementedError('Not available from this locator')
473 |
474 | def _get_project(self, name):
475 | result = {'urls': {}, 'digests': {}}
476 | url = urljoin(self.base_url, '%s/json' % quote(name))
477 | try:
478 | resp = self.opener.open(url)
479 | data = resp.read().decode() # for now
480 | d = json.loads(data)
481 | md = Metadata(scheme=self.scheme)
482 | data = d['info']
483 | md.name = data['name']
484 | md.version = data['version']
485 | md.license = data.get('license')
486 | md.keywords = data.get('keywords', [])
487 | md.summary = data.get('summary')
488 | dist = Distribution(md)
489 | dist.locator = self
490 | # urls = d['urls']
491 | result[md.version] = dist
492 | for info in d['urls']:
493 | url = info['url']
494 | dist.download_urls.add(url)
495 | dist.digests[url] = self._get_digest(info)
496 | result['urls'].setdefault(md.version, set()).add(url)
497 | result['digests'][url] = self._get_digest(info)
498 | # Now get other releases
499 | for version, infos in d['releases'].items():
500 | if version == md.version:
501 | continue # already done
502 | omd = Metadata(scheme=self.scheme)
503 | omd.name = md.name
504 | omd.version = version
505 | odist = Distribution(omd)
506 | odist.locator = self
507 | result[version] = odist
508 | for info in infos:
509 | url = info['url']
510 | odist.download_urls.add(url)
511 | odist.digests[url] = self._get_digest(info)
512 | result['urls'].setdefault(version, set()).add(url)
513 | result['digests'][url] = self._get_digest(info)
514 | # for info in urls:
515 | # md.source_url = info['url']
516 | # dist.digest = self._get_digest(info)
517 | # dist.locator = self
518 | # for info in urls:
519 | # url = info['url']
520 | # result['urls'].setdefault(md.version, set()).add(url)
521 | # result['digests'][url] = self._get_digest(info)
522 | except Exception as e:
523 | self.errors.put(text_type(e))
524 | logger.exception('JSON fetch failed: %s', e)
525 | return result
526 |
527 |
528 | class Page(object):
529 | """
530 | This class represents a scraped HTML page.
531 | """
532 | # The following slightly hairy-looking regex just looks for the contents of
533 | # an anchor link, which has an attribute "href" either immediately preceded
534 | # or immediately followed by a "rel" attribute. The attribute values can be
535 | # declared with double quotes, single quotes or no quotes - which leads to
536 | # the length of the expression.
537 | _href = re.compile("""
538 | (rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
539 | href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
540 | (\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
541 | """, re.I | re.S | re.X)
542 | _base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I | re.S)
543 |
544 | def __init__(self, data, url):
545 | """
546 | Initialise an instance with the Unicode page contents and the URL they
547 | came from.
548 | """
549 | self.data = data
550 | self.base_url = self.url = url
551 | m = self._base.search(self.data)
552 | if m:
553 | self.base_url = m.group(1)
554 |
555 | _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
556 |
557 | @cached_property
558 | def links(self):
559 | """
560 | Return the URLs of all the links on a page together with information
561 | about their "rel" attribute, for determining which ones to treat as
562 | downloads and which ones to queue for further scraping.
563 | """
564 | def clean(url):
565 | "Tidy up an URL."
566 | scheme, netloc, path, params, query, frag = urlparse(url)
567 | return urlunparse((scheme, netloc, quote(path),
568 | params, query, frag))
569 |
570 | result = set()
571 | for match in self._href.finditer(self.data):
572 | d = match.groupdict('')
573 | rel = (d['rel1'] or d['rel2'] or d['rel3'] or
574 | d['rel4'] or d['rel5'] or d['rel6'])
575 | url = d['url1'] or d['url2'] or d['url3']
576 | url = urljoin(self.base_url, url)
577 | url = unescape(url)
578 | url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url)
579 | result.add((url, rel))
580 | # We sort the result, hoping to bring the most recent versions
581 | # to the front
582 | result = sorted(result, key=lambda t: t[0], reverse=True)
583 | return result
584 |
585 |
586 | class SimpleScrapingLocator(Locator):
587 | """
588 | A locator which scrapes HTML pages to locate downloads for a distribution.
589 | This runs multiple threads to do the I/O; performance is at least as good
590 | as pip's PackageFinder, which works in an analogous fashion.
591 | """
592 |
593 | # These are used to deal with various Content-Encoding schemes.
594 | decoders = {
595 | 'deflate': zlib.decompress,
596 | 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(),
597 | 'none': lambda b: b,
598 | }
599 |
600 | def __init__(self, url, timeout=None, num_workers=10, **kwargs):
601 | """
602 | Initialise an instance.
603 | :param url: The root URL to use for scraping.
604 | :param timeout: The timeout, in seconds, to be applied to requests.
605 | This defaults to ``None`` (no timeout specified).
606 | :param num_workers: The number of worker threads you want to do I/O,
607 | This defaults to 10.
608 | :param kwargs: Passed to the superclass.
609 | """
610 | super(SimpleScrapingLocator, self).__init__(**kwargs)
611 | self.base_url = ensure_slash(url)
612 | self.timeout = timeout
613 | self._page_cache = {}
614 | self._seen = set()
615 | self._to_fetch = queue.Queue()
616 | self._bad_hosts = set()
617 | self.skip_externals = False
618 | self.num_workers = num_workers
619 | self._lock = threading.RLock()
620 | # See issue #45: we need to be resilient when the locator is used
621 | # in a thread, e.g. with concurrent.futures. We can't use self._lock
622 | # as it is for coordinating our internal threads - the ones created
623 | # in _prepare_threads.
624 | self._gplock = threading.RLock()
625 | self.platform_check = False # See issue #112
626 |
627 | def _prepare_threads(self):
628 | """
629 | Threads are created only when get_project is called, and terminate
630 | before it returns. They are there primarily to parallelise I/O (i.e.
631 | fetching web pages).
632 | """
633 | self._threads = []
634 | for i in range(self.num_workers):
635 | t = threading.Thread(target=self._fetch)
636 | t.daemon = True
637 | t.start()
638 | self._threads.append(t)
639 |
640 | def _wait_threads(self):
641 | """
642 | Tell all the threads to terminate (by sending a sentinel value) and
643 | wait for them to do so.
644 | """
645 | # Note that you need two loops, since you can't say which
646 | # thread will get each sentinel
647 | for t in self._threads:
648 | self._to_fetch.put(None) # sentinel
649 | for t in self._threads:
650 | t.join()
651 | self._threads = []
652 |
653 | def _get_project(self, name):
654 | result = {'urls': {}, 'digests': {}}
655 | with self._gplock:
656 | self.result = result
657 | self.project_name = name
658 | url = urljoin(self.base_url, '%s/' % quote(name))
659 | self._seen.clear()
660 | self._page_cache.clear()
661 | self._prepare_threads()
662 | try:
663 | logger.debug('Queueing %s', url)
664 | self._to_fetch.put(url)
665 | self._to_fetch.join()
666 | finally:
667 | self._wait_threads()
668 | del self.result
669 | return result
670 |
671 | platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|'
672 | r'win(32|_amd64)|macosx_?\d+)\b', re.I)
673 |
674 | def _is_platform_dependent(self, url):
675 | """
676 | Does an URL refer to a platform-specific download?
677 | """
678 | return self.platform_dependent.search(url)
679 |
680 | def _process_download(self, url):
681 | """
682 | See if an URL is a suitable download for a project.
683 |
684 | If it is, register information in the result dictionary (for
685 | _get_project) about the specific version it's for.
686 |
687 | Note that the return value isn't actually used other than as a boolean
688 | value.
689 | """
690 | if self.platform_check and self._is_platform_dependent(url):
691 | info = None
692 | else:
693 | info = self.convert_url_to_download_info(url, self.project_name)
694 | logger.debug('process_download: %s -> %s', url, info)
695 | if info:
696 | with self._lock: # needed because self.result is shared
697 | self._update_version_data(self.result, info)
698 | return info
699 |
700 | def _should_queue(self, link, referrer, rel):
701 | """
702 | Determine whether a link URL from a referring page and with a
703 | particular "rel" attribute should be queued for scraping.
704 | """
705 | scheme, netloc, path, _, _, _ = urlparse(link)
706 | if path.endswith(self.source_extensions + self.binary_extensions +
707 | self.excluded_extensions):
708 | result = False
709 | elif self.skip_externals and not link.startswith(self.base_url):
710 | result = False
711 | elif not referrer.startswith(self.base_url):
712 | result = False
713 | elif rel not in ('homepage', 'download'):
714 | result = False
715 | elif scheme not in ('http', 'https', 'ftp'):
716 | result = False
717 | elif self._is_platform_dependent(link):
718 | result = False
719 | else:
720 | host = netloc.split(':', 1)[0]
721 | if host.lower() == 'localhost':
722 | result = False
723 | else:
724 | result = True
725 | logger.debug('should_queue: %s (%s) from %s -> %s', link, rel,
726 | referrer, result)
727 | return result
728 |
729 | def _fetch(self):
730 | """
731 | Get a URL to fetch from the work queue, get the HTML page, examine its
732 | links for download candidates and candidates for further scraping.
733 |
734 | This is a handy method to run in a thread.
735 | """
736 | while True:
737 | url = self._to_fetch.get()
738 | try:
739 | if url:
740 | page = self.get_page(url)
741 | if page is None: # e.g. after an error
742 | continue
743 | for link, rel in page.links:
744 | if link not in self._seen:
745 | try:
746 | self._seen.add(link)
747 | if (not self._process_download(link) and
748 | self._should_queue(link, url, rel)):
749 | logger.debug('Queueing %s from %s', link, url)
750 | self._to_fetch.put(link)
751 | except MetadataInvalidError: # e.g. invalid versions
752 | pass
753 | except Exception as e: # pragma: no cover
754 | self.errors.put(text_type(e))
755 | finally:
756 | # always do this, to avoid hangs :-)
757 | self._to_fetch.task_done()
758 | if not url:
759 | # logger.debug('Sentinel seen, quitting.')
760 | break
761 |
762 | def get_page(self, url):
763 | """
764 | Get the HTML for an URL, possibly from an in-memory cache.
765 |
766 | XXX TODO Note: this cache is never actually cleared. It's assumed that
767 | the data won't get stale over the lifetime of a locator instance (not
768 | necessarily true for the default_locator).
769 | """
770 | # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api
771 | scheme, netloc, path, _, _, _ = urlparse(url)
772 | if scheme == 'file' and os.path.isdir(url2pathname(path)):
773 | url = urljoin(ensure_slash(url), 'index.html')
774 |
775 | if url in self._page_cache:
776 | result = self._page_cache[url]
777 | logger.debug('Returning %s from cache: %s', url, result)
778 | else:
779 | host = netloc.split(':', 1)[0]
780 | result = None
781 | if host in self._bad_hosts:
782 | logger.debug('Skipping %s due to bad host %s', url, host)
783 | else:
784 | req = Request(url, headers={'Accept-encoding': 'identity'})
785 | try:
786 | logger.debug('Fetching %s', url)
787 | resp = self.opener.open(req, timeout=self.timeout)
788 | logger.debug('Fetched %s', url)
789 | headers = resp.info()
790 | content_type = headers.get('Content-Type', '')
791 | if HTML_CONTENT_TYPE.match(content_type):
792 | final_url = resp.geturl()
793 | data = resp.read()
794 | encoding = headers.get('Content-Encoding')
795 | if encoding:
796 | decoder = self.decoders[encoding] # fail if not found
797 | data = decoder(data)
798 | encoding = 'utf-8'
799 | m = CHARSET.search(content_type)
800 | if m:
801 | encoding = m.group(1)
802 | try:
803 | data = data.decode(encoding)
804 | except UnicodeError: # pragma: no cover
805 | data = data.decode('latin-1') # fallback
806 | result = Page(data, final_url)
807 | self._page_cache[final_url] = result
808 | except HTTPError as e:
809 | if e.code != 404:
810 | logger.exception('Fetch failed: %s: %s', url, e)
811 | except URLError as e: # pragma: no cover
812 | logger.exception('Fetch failed: %s: %s', url, e)
813 | with self._lock:
814 | self._bad_hosts.add(host)
815 | except Exception as e: # pragma: no cover
816 | logger.exception('Fetch failed: %s: %s', url, e)
817 | finally:
818 | self._page_cache[url] = result # even if None (failure)
819 | return result
820 |
821 | _distname_re = re.compile('<a href=[^>]*>([^<]+)<')
822 |
823 | def get_distribution_names(self):
824 | """
825 | Return all the distribution names known to this locator.
826 | """
827 | result = set()
828 | page = self.get_page(self.base_url)
829 | if not page:
830 | raise DistlibException('Unable to get %s' % self.base_url)
831 | for match in self._distname_re.finditer(page.data):
832 | result.add(match.group(1))
833 | return result
834 |
835 |
836 | class DirectoryLocator(Locator):
837 | """
838 | This class locates distributions in a directory tree.
839 | """
840 |
841 | def __init__(self, path, **kwargs):
842 | """
843 | Initialise an instance.
844 | :param path: The root of the directory tree to search.
845 | :param kwargs: Passed to the superclass constructor,
846 | except for:
847 | * recursive - if True (the default), subdirectories are
848 | recursed into. If False, only the top-level directory
849 | is searched,
850 | """
851 | self.recursive = kwargs.pop('recursive', True)
852 | super(DirectoryLocator, self).__init__(**kwargs)
853 | path = os.path.abspath(path)
854 | if not os.path.isdir(path): # pragma: no cover
855 | raise DistlibException('Not a directory: %r' % path)
856 | self.base_dir = path
857 |
858 | def should_include(self, filename, parent):
859 | """
860 | Should a filename be considered as a candidate for a distribution
861 | archive? As well as the filename, the directory which contains it
862 | is provided, though not used by the current implementation.
863 | """
864 | return filename.endswith(self.downloadable_extensions)
865 |
866 | def _get_project(self, name):
867 | result = {'urls': {}, 'digests': {}}
868 | for root, dirs, files in os.walk(self.base_dir):
869 | for fn in files:
870 | if self.should_include(fn, root):
871 | fn = os.path.join(root, fn)
872 | url = urlunparse(('file', '',
873 | pathname2url(os.path.abspath(fn)),
874 | '', '', ''))
875 | info = self.convert_url_to_download_info(url, name)
876 | if info:
877 | self._update_version_data(result, info)
878 | if not self.recursive:
879 | break
880 | return result
881 |
882 | def get_distribution_names(self):
883 | """
884 | Return all the distribution names known to this locator.
885 | """
886 | result = set()
887 | for root, dirs, files in os.walk(self.base_dir):
888 | for fn in files:
889 | if self.should_include(fn, root):
890 | fn = os.path.join(root, fn)
891 | url = urlunparse(('file', '',
892 | pathname2url(os.path.abspath(fn)),
893 | '', '', ''))
894 | info = self.convert_url_to_download_info(url, None)
895 | if info:
896 | result.add(info['name'])
897 | if not self.recursive:
898 | break
899 | return result
900 |
901 |
902 | class JSONLocator(Locator):
903 | """
904 | This locator uses special extended metadata (not available on PyPI) and is
905 | the basis of performant dependency resolution in distlib. Other locators
906 | require archive downloads before dependencies can be determined! As you
907 | might imagine, that can be slow.
908 | """
909 | def get_distribution_names(self):
910 | """
911 | Return all the distribution names known to this locator.
912 | """
913 | raise NotImplementedError('Not available from this locator')
914 |
915 | def _get_project(self, name):
916 | result = {'urls': {}, 'digests': {}}
917 | data = get_project_data(name)
918 | if data:
919 | for info in data.get('files', []):
920 | if info['ptype'] != 'sdist' or info['pyversion'] != 'source':
921 | continue
922 | # We don't store summary in project metadata as it makes
923 | # the data bigger for no benefit during dependency
924 | # resolution
925 | dist = make_dist(data['name'], info['version'],
926 | summary=data.get('summary',
927 | 'Placeholder for summary'),
928 | scheme=self.scheme)
929 | md = dist.metadata
930 | md.source_url = info['url']
931 | # TODO SHA256 digest
932 | if 'digest' in info and info['digest']:
933 | dist.digest = ('md5', info['digest'])
934 | md.dependencies = info.get('requirements', {})
935 | dist.exports = info.get('exports', {})
936 | result[dist.version] = dist
937 | result['urls'].setdefault(dist.version, set()).add(info['url'])
938 | return result
939 |
940 |
941 | class DistPathLocator(Locator):
942 | """
943 | This locator finds installed distributions in a path. It can be useful for
944 | adding to an :class:`AggregatingLocator`.
945 | """
946 | def __init__(self, distpath, **kwargs):
947 | """
948 | Initialise an instance.
949 |
950 | :param distpath: A :class:`DistributionPath` instance to search.
951 | """
952 | super(DistPathLocator, self).__init__(**kwargs)
953 | assert isinstance(distpath, DistributionPath)
954 | self.distpath = distpath
955 |
956 | def _get_project(self, name):
957 | dist = self.distpath.get_distribution(name)
958 | if dist is None:
959 | result = {'urls': {}, 'digests': {}}
960 | else:
961 | result = {
962 | dist.version: dist,
963 | 'urls': {dist.version: set([dist.source_url])},
964 | 'digests': {dist.version: set([None])}
965 | }
966 | return result
967 |
968 |
969 | class AggregatingLocator(Locator):
970 | """
971 | This class allows you to chain and/or merge a list of locators.
972 | """
973 | def __init__(self, *locators, **kwargs):
974 | """
975 | Initialise an instance.
976 |
977 | :param locators: The list of locators to search.
978 | :param kwargs: Passed to the superclass constructor,
979 | except for:
980 | * merge - if False (the default), the first successful
981 | search from any of the locators is returned. If True,
982 | the results from all locators are merged (this can be
983 | slow).
984 | """
985 | self.merge = kwargs.pop('merge', False)
986 | self.locators = locators
987 | super(AggregatingLocator, self).__init__(**kwargs)
988 |
989 | def clear_cache(self):
990 | super(AggregatingLocator, self).clear_cache()
991 | for locator in self.locators:
992 | locator.clear_cache()
993 |
994 | def _set_scheme(self, value):
995 | self._scheme = value
996 | for locator in self.locators:
997 | locator.scheme = value
998 |
999 | scheme = property(Locator.scheme.fget, _set_scheme)
1000 |
1001 | def _get_project(self, name):
1002 | result = {}
1003 | for locator in self.locators:
1004 | d = locator.get_project(name)
1005 | if d:
1006 | if self.merge:
1007 | files = result.get('urls', {})
1008 | digests = result.get('digests', {})
1009 | # next line could overwrite result['urls'], result['digests']
1010 | result.update(d)
1011 | df = result.get('urls')
1012 | if files and df:
1013 | for k, v in files.items():
1014 | if k in df:
1015 | df[k] |= v
1016 | else:
1017 | df[k] = v
1018 | dd = result.get('digests')
1019 | if digests and dd:
1020 | dd.update(digests)
1021 | else:
1022 | # See issue #18. If any dists are found and we're looking
1023 | # for specific constraints, we only return something if
1024 | # a match is found. For example, if a DirectoryLocator
1025 | # returns just foo (1.0) while we're looking for
1026 | # foo (>= 2.0), we'll pretend there was nothing there so
1027 | # that subsequent locators can be queried. Otherwise we
1028 | # would just return foo (1.0) which would then lead to a
1029 | # failure to find foo (>= 2.0), because other locators
1030 | # weren't searched. Note that this only matters when
1031 | # merge=False.
1032 | if self.matcher is None:
1033 | found = True
1034 | else:
1035 | found = False
1036 | for k in d:
1037 | if self.matcher.match(k):
1038 | found = True
1039 | break
1040 | if found:
1041 | result = d
1042 | break
1043 | return result
1044 |
1045 | def get_distribution_names(self):
1046 | """
1047 | Return all the distribution names known to this locator.
1048 | """
1049 | result = set()
1050 | for locator in self.locators:
1051 | try:
1052 | result |= locator.get_distribution_names()
1053 | except NotImplementedError:
1054 | pass
1055 | return result
1056 |
1057 |
1058 | # We use a legacy scheme simply because most of the dists on PyPI use legacy
1059 | # versions which don't conform to PEP 440.
1060 | default_locator = AggregatingLocator(
1061 | # JSONLocator(), # don't use as PEP 426 is withdrawn
1062 | SimpleScrapingLocator('https://pypi.org/simple/',
1063 | timeout=3.0),
1064 | scheme='legacy')
1065 |
1066 | locate = default_locator.locate
1067 |
1068 |
1069 | class DependencyFinder(object):
1070 | """
1071 | Locate dependencies for distributions.
1072 | """
1073 |
1074 | def __init__(self, locator=None):
1075 | """
1076 | Initialise an instance, using the specified locator
1077 | to locate distributions.
1078 | """
1079 | self.locator = locator or default_locator
1080 | self.scheme = get_scheme(self.locator.scheme)
1081 |
1082 | def add_distribution(self, dist):
1083 | """
1084 | Add a distribution to the finder. This will update internal information
1085 | about who provides what.
1086 | :param dist: The distribution to add.
1087 | """
1088 | logger.debug('adding distribution %s', dist)
1089 | name = dist.key
1090 | self.dists_by_name[name] = dist
1091 | self.dists[(name, dist.version)] = dist
1092 | for p in dist.provides:
1093 | name, version = parse_name_and_version(p)
1094 | logger.debug('Add to provided: %s, %s, %s', name, version, dist)
1095 | self.provided.setdefault(name, set()).add((version, dist))
1096 |
1097 | def remove_distribution(self, dist):
1098 | """
1099 | Remove a distribution from the finder. This will update internal
1100 | information about who provides what.
1101 | :param dist: The distribution to remove.
1102 | """
1103 | logger.debug('removing distribution %s', dist)
1104 | name = dist.key
1105 | del self.dists_by_name[name]
1106 | del self.dists[(name, dist.version)]
1107 | for p in dist.provides:
1108 | name, version = parse_name_and_version(p)
1109 | logger.debug('Remove from provided: %s, %s, %s', name, version, dist)
1110 | s = self.provided[name]
1111 | s.remove((version, dist))
1112 | if not s:
1113 | del self.provided[name]
1114 |
1115 | def get_matcher(self, reqt):
1116 | """
1117 | Get a version matcher for a requirement.
1118 | :param reqt: The requirement
1119 | :type reqt: str
1120 | :return: A version matcher (an instance of
1121 | :class:`distlib.version.Matcher`).
1122 | """
1123 | try:
1124 | matcher = self.scheme.matcher(reqt)
1125 | except UnsupportedVersionError: # pragma: no cover
1126 | # XXX compat-mode if cannot read the version
1127 | name = reqt.split()[0]
1128 | matcher = self.scheme.matcher(name)
1129 | return matcher
1130 |
1131 | def find_providers(self, reqt):
1132 | """
1133 | Find the distributions which can fulfill a requirement.
1134 |
1135 | :param reqt: The requirement.
1136 | :type reqt: str
1137 | :return: A set of distribution which can fulfill the requirement.
1138 | """
1139 | matcher = self.get_matcher(reqt)
1140 | name = matcher.key # case-insensitive
1141 | result = set()
1142 | provided = self.provided
1143 | if name in provided:
1144 | for version, provider in provided[name]:
1145 | try:
1146 | match = matcher.match(version)
1147 | except UnsupportedVersionError:
1148 | match = False
1149 |
1150 | if match:
1151 | result.add(provider)
1152 | break
1153 | return result
1154 |
1155 | def try_to_replace(self, provider, other, problems):
1156 | """
1157 | Attempt to replace one provider with another. This is typically used
1158 | when resolving dependencies from multiple sources, e.g. A requires
1159 | (B >= 1.0) while C requires (B >= 1.1).
1160 |
1161 | For successful replacement, ``provider`` must meet all the requirements
1162 | which ``other`` fulfills.
1163 |
1164 | :param provider: The provider we are trying to replace with.
1165 | :param other: The provider we're trying to replace.
1166 | :param problems: If False is returned, this will contain what
1167 | problems prevented replacement. This is currently
1168 | a tuple of the literal string 'cantreplace',
1169 | ``provider``, ``other`` and the set of requirements
1170 | that ``provider`` couldn't fulfill.
1171 | :return: True if we can replace ``other`` with ``provider``, else
1172 | False.
1173 | """
1174 | rlist = self.reqts[other]
1175 | unmatched = set()
1176 | for s in rlist:
1177 | matcher = self.get_matcher(s)
1178 | if not matcher.match(provider.version):
1179 | unmatched.add(s)
1180 | if unmatched:
1181 | # can't replace other with provider
1182 | problems.add(('cantreplace', provider, other,
1183 | frozenset(unmatched)))
1184 | result = False
1185 | else:
1186 | # can replace other with provider
1187 | self.remove_distribution(other)
1188 | del self.reqts[other]
1189 | for s in rlist:
1190 | self.reqts.setdefault(provider, set()).add(s)
1191 | self.add_distribution(provider)
1192 | result = True
1193 | return result
1194 |
1195 | def find(self, requirement, meta_extras=None, prereleases=False):
1196 | """
1197 | Find a distribution and all distributions it depends on.
1198 |
1199 | :param requirement: The requirement specifying the distribution to
1200 | find, or a Distribution instance.
1201 | :param meta_extras: A list of meta extras such as :test:, :build: and
1202 | so on.
1203 | :param prereleases: If ``True``, allow pre-release versions to be
1204 | returned - otherwise, don't return prereleases
1205 | unless they're all that's available.
1206 |
1207 | Return a set of :class:`Distribution` instances and a set of
1208 | problems.
1209 |
1210 | The distributions returned should be such that they have the
1211 | :attr:`required` attribute set to ``True`` if they were
1212 | from the ``requirement`` passed to ``find()``, and they have the
1213 | :attr:`build_time_dependency` attribute set to ``True`` unless they
1214 | are post-installation dependencies of the ``requirement``.
1215 |
1216 | The problems should be a tuple consisting of the string
1217 | ``'unsatisfied'`` and the requirement which couldn't be satisfied
1218 | by any distribution known to the locator.
1219 | """
1220 |
1221 | self.provided = {}
1222 | self.dists = {}
1223 | self.dists_by_name = {}
1224 | self.reqts = {}
1225 |
1226 | meta_extras = set(meta_extras or [])
1227 | if ':*:' in meta_extras:
1228 | meta_extras.remove(':*:')
1229 | # :meta: and :run: are implicitly included
1230 | meta_extras |= set([':test:', ':build:', ':dev:'])
1231 |
1232 | if isinstance(requirement, Distribution):
1233 | dist = odist = requirement
1234 | logger.debug('passed %s as requirement', odist)
1235 | else:
1236 | dist = odist = self.locator.locate(requirement,
1237 | prereleases=prereleases)
1238 | if dist is None:
1239 | raise DistlibException('Unable to locate %r' % requirement)
1240 | logger.debug('located %s', odist)
1241 | dist.requested = True
1242 | problems = set()
1243 | todo = set([dist])
1244 | install_dists = set([odist])
1245 | while todo:
1246 | dist = todo.pop()
1247 | name = dist.key # case-insensitive
1248 | if name not in self.dists_by_name:
1249 | self.add_distribution(dist)
1250 | else:
1251 | # import pdb; pdb.set_trace()
1252 | other = self.dists_by_name[name]
1253 | if other != dist:
1254 | self.try_to_replace(dist, other, problems)
1255 |
1256 | ireqts = dist.run_requires | dist.meta_requires
1257 | sreqts = dist.build_requires
1258 | ereqts = set()
1259 | if meta_extras and dist in install_dists:
1260 | for key in ('test', 'build', 'dev'):
1261 | e = ':%s:' % key
1262 | if e in meta_extras:
1263 | ereqts |= getattr(dist, '%s_requires' % key)
1264 | all_reqts = ireqts | sreqts | ereqts
1265 | for r in all_reqts:
1266 | providers = self.find_providers(r)
1267 | if not providers:
1268 | logger.debug('No providers found for %r', r)
1269 | provider = self.locator.locate(r, prereleases=prereleases)
1270 | # If no provider is found and we didn't consider
1271 | # prereleases, consider them now.
1272 | if provider is None and not prereleases:
1273 | provider = self.locator.locate(r, prereleases=True)
1274 | if provider is None:
1275 | logger.debug('Cannot satisfy %r', r)
1276 | problems.add(('unsatisfied', r))
1277 | else:
1278 | n, v = provider.key, provider.version
1279 | if (n, v) not in self.dists:
1280 | todo.add(provider)
1281 | providers.add(provider)
1282 | if r in ireqts and dist in install_dists:
1283 | install_dists.add(provider)
1284 | logger.debug('Adding %s to install_dists',
1285 | provider.name_and_version)
1286 | for p in providers:
1287 | name = p.key
1288 | if name not in self.dists_by_name:
1289 | self.reqts.setdefault(p, set()).add(r)
1290 | else:
1291 | other = self.dists_by_name[name]
1292 | if other != p:
1293 | # see if other can be replaced by p
1294 | self.try_to_replace(p, other, problems)
1295 |
1296 | dists = set(self.dists.values())
1297 | for dist in dists:
1298 | dist.build_time_dependency = dist not in install_dists
1299 | if dist.build_time_dependency:
1300 | logger.debug('%s is a build-time dependency only.',
1301 | dist.name_and_version)
1302 | logger.debug('find done for %s', odist)
1303 | return dists, problems
1304 |
```