This is page 71 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/_internal/network/auth.py:
--------------------------------------------------------------------------------
```python
1 | """Network Authentication Helpers
2 |
3 | Contains interface (MultiDomainBasicAuth) and associated glue code for
4 | providing credentials in the context of network requests.
5 | """
6 |
7 | import logging
8 | import os
9 | import shutil
10 | import subprocess
11 | import sysconfig
12 | import typing
13 | import urllib.parse
14 | from abc import ABC, abstractmethod
15 | from functools import lru_cache
16 | from os.path import commonprefix
17 | from pathlib import Path
18 | from typing import Any, Dict, List, NamedTuple, Optional, Tuple
19 |
20 | from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
21 | from pip._vendor.requests.models import Request, Response
22 | from pip._vendor.requests.utils import get_netrc_auth
23 |
24 | from pip._internal.utils.logging import getLogger
25 | from pip._internal.utils.misc import (
26 | ask,
27 | ask_input,
28 | ask_password,
29 | remove_auth_from_url,
30 | split_auth_netloc_from_url,
31 | )
32 | from pip._internal.vcs.versioncontrol import AuthInfo
33 |
34 | logger = getLogger(__name__)
35 |
36 | KEYRING_DISABLED = False
37 |
38 |
39 | class Credentials(NamedTuple):
40 | url: str
41 | username: str
42 | password: str
43 |
44 |
45 | class KeyRingBaseProvider(ABC):
46 | """Keyring base provider interface"""
47 |
48 | has_keyring: bool
49 |
50 | @abstractmethod
51 | def get_auth_info(
52 | self, url: str, username: Optional[str]
53 | ) -> Optional[AuthInfo]: ...
54 |
55 | @abstractmethod
56 | def save_auth_info(self, url: str, username: str, password: str) -> None: ...
57 |
58 |
59 | class KeyRingNullProvider(KeyRingBaseProvider):
60 | """Keyring null provider"""
61 |
62 | has_keyring = False
63 |
64 | def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
65 | return None
66 |
67 | def save_auth_info(self, url: str, username: str, password: str) -> None:
68 | return None
69 |
70 |
71 | class KeyRingPythonProvider(KeyRingBaseProvider):
72 | """Keyring interface which uses locally imported `keyring`"""
73 |
74 | has_keyring = True
75 |
76 | def __init__(self) -> None:
77 | import keyring
78 |
79 | self.keyring = keyring
80 |
81 | def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
82 | # Support keyring's get_credential interface which supports getting
83 | # credentials without a username. This is only available for
84 | # keyring>=15.2.0.
85 | if hasattr(self.keyring, "get_credential"):
86 | logger.debug("Getting credentials from keyring for %s", url)
87 | cred = self.keyring.get_credential(url, username)
88 | if cred is not None:
89 | return cred.username, cred.password
90 | return None
91 |
92 | if username is not None:
93 | logger.debug("Getting password from keyring for %s", url)
94 | password = self.keyring.get_password(url, username)
95 | if password:
96 | return username, password
97 | return None
98 |
99 | def save_auth_info(self, url: str, username: str, password: str) -> None:
100 | self.keyring.set_password(url, username, password)
101 |
102 |
103 | class KeyRingCliProvider(KeyRingBaseProvider):
104 | """Provider which uses `keyring` cli
105 |
106 | Instead of calling the keyring package installed alongside pip
107 | we call keyring on the command line which will enable pip to
108 | use which ever installation of keyring is available first in
109 | PATH.
110 | """
111 |
112 | has_keyring = True
113 |
114 | def __init__(self, cmd: str) -> None:
115 | self.keyring = cmd
116 |
117 | def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
118 | # This is the default implementation of keyring.get_credential
119 | # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
120 | if username is not None:
121 | password = self._get_password(url, username)
122 | if password is not None:
123 | return username, password
124 | return None
125 |
126 | def save_auth_info(self, url: str, username: str, password: str) -> None:
127 | return self._set_password(url, username, password)
128 |
129 | def _get_password(self, service_name: str, username: str) -> Optional[str]:
130 | """Mirror the implementation of keyring.get_password using cli"""
131 | if self.keyring is None:
132 | return None
133 |
134 | cmd = [self.keyring, "get", service_name, username]
135 | env = os.environ.copy()
136 | env["PYTHONIOENCODING"] = "utf-8"
137 | res = subprocess.run(
138 | cmd,
139 | stdin=subprocess.DEVNULL,
140 | stdout=subprocess.PIPE,
141 | env=env,
142 | )
143 | if res.returncode:
144 | return None
145 | return res.stdout.decode("utf-8").strip(os.linesep)
146 |
147 | def _set_password(self, service_name: str, username: str, password: str) -> None:
148 | """Mirror the implementation of keyring.set_password using cli"""
149 | if self.keyring is None:
150 | return None
151 | env = os.environ.copy()
152 | env["PYTHONIOENCODING"] = "utf-8"
153 | subprocess.run(
154 | [self.keyring, "set", service_name, username],
155 | input=f"{password}{os.linesep}".encode(),
156 | env=env,
157 | check=True,
158 | )
159 | return None
160 |
161 |
162 | @lru_cache(maxsize=None)
163 | def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
164 | logger.verbose("Keyring provider requested: %s", provider)
165 |
166 | # keyring has previously failed and been disabled
167 | if KEYRING_DISABLED:
168 | provider = "disabled"
169 | if provider in ["import", "auto"]:
170 | try:
171 | impl = KeyRingPythonProvider()
172 | logger.verbose("Keyring provider set: import")
173 | return impl
174 | except ImportError:
175 | pass
176 | except Exception as exc:
177 | # In the event of an unexpected exception
178 | # we should warn the user
179 | msg = "Installed copy of keyring fails with exception %s"
180 | if provider == "auto":
181 | msg = msg + ", trying to find a keyring executable as a fallback"
182 | logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
183 | if provider in ["subprocess", "auto"]:
184 | cli = shutil.which("keyring")
185 | if cli and cli.startswith(sysconfig.get_path("scripts")):
186 | # all code within this function is stolen from shutil.which implementation
187 | @typing.no_type_check
188 | def PATH_as_shutil_which_determines_it() -> str:
189 | path = os.environ.get("PATH", None)
190 | if path is None:
191 | try:
192 | path = os.confstr("CS_PATH")
193 | except (AttributeError, ValueError):
194 | # os.confstr() or CS_PATH is not available
195 | path = os.defpath
196 | # bpo-35755: Don't use os.defpath if the PATH environment variable is
197 | # set to an empty string
198 |
199 | return path
200 |
201 | scripts = Path(sysconfig.get_path("scripts"))
202 |
203 | paths = []
204 | for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
205 | p = Path(path)
206 | try:
207 | if not p.samefile(scripts):
208 | paths.append(path)
209 | except FileNotFoundError:
210 | pass
211 |
212 | path = os.pathsep.join(paths)
213 |
214 | cli = shutil.which("keyring", path=path)
215 |
216 | if cli:
217 | logger.verbose("Keyring provider set: subprocess with executable %s", cli)
218 | return KeyRingCliProvider(cli)
219 |
220 | logger.verbose("Keyring provider set: disabled")
221 | return KeyRingNullProvider()
222 |
223 |
224 | class MultiDomainBasicAuth(AuthBase):
225 | def __init__(
226 | self,
227 | prompting: bool = True,
228 | index_urls: Optional[List[str]] = None,
229 | keyring_provider: str = "auto",
230 | ) -> None:
231 | self.prompting = prompting
232 | self.index_urls = index_urls
233 | self.keyring_provider = keyring_provider # type: ignore[assignment]
234 | self.passwords: Dict[str, AuthInfo] = {}
235 | # When the user is prompted to enter credentials and keyring is
236 | # available, we will offer to save them. If the user accepts,
237 | # this value is set to the credentials they entered. After the
238 | # request authenticates, the caller should call
239 | # ``save_credentials`` to save these.
240 | self._credentials_to_save: Optional[Credentials] = None
241 |
242 | @property
243 | def keyring_provider(self) -> KeyRingBaseProvider:
244 | return get_keyring_provider(self._keyring_provider)
245 |
246 | @keyring_provider.setter
247 | def keyring_provider(self, provider: str) -> None:
248 | # The free function get_keyring_provider has been decorated with
249 | # functools.cache. If an exception occurs in get_keyring_auth that
250 | # cache will be cleared and keyring disabled, take that into account
251 | # if you want to remove this indirection.
252 | self._keyring_provider = provider
253 |
254 | @property
255 | def use_keyring(self) -> bool:
256 | # We won't use keyring when --no-input is passed unless
257 | # a specific provider is requested because it might require
258 | # user interaction
259 | return self.prompting or self._keyring_provider not in ["auto", "disabled"]
260 |
261 | def _get_keyring_auth(
262 | self,
263 | url: Optional[str],
264 | username: Optional[str],
265 | ) -> Optional[AuthInfo]:
266 | """Return the tuple auth for a given url from keyring."""
267 | # Do nothing if no url was provided
268 | if not url:
269 | return None
270 |
271 | try:
272 | return self.keyring_provider.get_auth_info(url, username)
273 | except Exception as exc:
274 | # Log the full exception (with stacktrace) at debug, so it'll only
275 | # show up when running in verbose mode.
276 | logger.debug("Keyring is skipped due to an exception", exc_info=True)
277 | # Always log a shortened version of the exception.
278 | logger.warning(
279 | "Keyring is skipped due to an exception: %s",
280 | str(exc),
281 | )
282 | global KEYRING_DISABLED
283 | KEYRING_DISABLED = True
284 | get_keyring_provider.cache_clear()
285 | return None
286 |
287 | def _get_index_url(self, url: str) -> Optional[str]:
288 | """Return the original index URL matching the requested URL.
289 |
290 | Cached or dynamically generated credentials may work against
291 | the original index URL rather than just the netloc.
292 |
293 | The provided url should have had its username and password
294 | removed already. If the original index url had credentials then
295 | they will be included in the return value.
296 |
297 | Returns None if no matching index was found, or if --no-index
298 | was specified by the user.
299 | """
300 | if not url or not self.index_urls:
301 | return None
302 |
303 | url = remove_auth_from_url(url).rstrip("/") + "/"
304 | parsed_url = urllib.parse.urlsplit(url)
305 |
306 | candidates = []
307 |
308 | for index in self.index_urls:
309 | index = index.rstrip("/") + "/"
310 | parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
311 | if parsed_url == parsed_index:
312 | return index
313 |
314 | if parsed_url.netloc != parsed_index.netloc:
315 | continue
316 |
317 | candidate = urllib.parse.urlsplit(index)
318 | candidates.append(candidate)
319 |
320 | if not candidates:
321 | return None
322 |
323 | candidates.sort(
324 | reverse=True,
325 | key=lambda candidate: commonprefix(
326 | [
327 | parsed_url.path,
328 | candidate.path,
329 | ]
330 | ).rfind("/"),
331 | )
332 |
333 | return urllib.parse.urlunsplit(candidates[0])
334 |
335 | def _get_new_credentials(
336 | self,
337 | original_url: str,
338 | *,
339 | allow_netrc: bool = True,
340 | allow_keyring: bool = False,
341 | ) -> AuthInfo:
342 | """Find and return credentials for the specified URL."""
343 | # Split the credentials and netloc from the url.
344 | url, netloc, url_user_password = split_auth_netloc_from_url(
345 | original_url,
346 | )
347 |
348 | # Start with the credentials embedded in the url
349 | username, password = url_user_password
350 | if username is not None and password is not None:
351 | logger.debug("Found credentials in url for %s", netloc)
352 | return url_user_password
353 |
354 | # Find a matching index url for this request
355 | index_url = self._get_index_url(url)
356 | if index_url:
357 | # Split the credentials from the url.
358 | index_info = split_auth_netloc_from_url(index_url)
359 | if index_info:
360 | index_url, _, index_url_user_password = index_info
361 | logger.debug("Found index url %s", index_url)
362 |
363 | # If an index URL was found, try its embedded credentials
364 | if index_url and index_url_user_password[0] is not None:
365 | username, password = index_url_user_password
366 | if username is not None and password is not None:
367 | logger.debug("Found credentials in index url for %s", netloc)
368 | return index_url_user_password
369 |
370 | # Get creds from netrc if we still don't have them
371 | if allow_netrc:
372 | netrc_auth = get_netrc_auth(original_url)
373 | if netrc_auth:
374 | logger.debug("Found credentials in netrc for %s", netloc)
375 | return netrc_auth
376 |
377 | # If we don't have a password and keyring is available, use it.
378 | if allow_keyring:
379 | # The index url is more specific than the netloc, so try it first
380 | # fmt: off
381 | kr_auth = (
382 | self._get_keyring_auth(index_url, username) or
383 | self._get_keyring_auth(netloc, username)
384 | )
385 | # fmt: on
386 | if kr_auth:
387 | logger.debug("Found credentials in keyring for %s", netloc)
388 | return kr_auth
389 |
390 | return username, password
391 |
392 | def _get_url_and_credentials(
393 | self, original_url: str
394 | ) -> Tuple[str, Optional[str], Optional[str]]:
395 | """Return the credentials to use for the provided URL.
396 |
397 | If allowed, netrc and keyring may be used to obtain the
398 | correct credentials.
399 |
400 | Returns (url_without_credentials, username, password). Note
401 | that even if the original URL contains credentials, this
402 | function may return a different username and password.
403 | """
404 | url, netloc, _ = split_auth_netloc_from_url(original_url)
405 |
406 | # Try to get credentials from original url
407 | username, password = self._get_new_credentials(original_url)
408 |
409 | # If credentials not found, use any stored credentials for this netloc.
410 | # Do this if either the username or the password is missing.
411 | # This accounts for the situation in which the user has specified
412 | # the username in the index url, but the password comes from keyring.
413 | if (username is None or password is None) and netloc in self.passwords:
414 | un, pw = self.passwords[netloc]
415 | # It is possible that the cached credentials are for a different username,
416 | # in which case the cache should be ignored.
417 | if username is None or username == un:
418 | username, password = un, pw
419 |
420 | if username is not None or password is not None:
421 | # Convert the username and password if they're None, so that
422 | # this netloc will show up as "cached" in the conditional above.
423 | # Further, HTTPBasicAuth doesn't accept None, so it makes sense to
424 | # cache the value that is going to be used.
425 | username = username or ""
426 | password = password or ""
427 |
428 | # Store any acquired credentials.
429 | self.passwords[netloc] = (username, password)
430 |
431 | assert (
432 | # Credentials were found
433 | (username is not None and password is not None)
434 | # Credentials were not found
435 | or (username is None and password is None)
436 | ), f"Could not load credentials from url: {original_url}"
437 |
438 | return url, username, password
439 |
440 | def __call__(self, req: Request) -> Request:
441 | # Get credentials for this request
442 | url, username, password = self._get_url_and_credentials(req.url)
443 |
444 | # Set the url of the request to the url without any credentials
445 | req.url = url
446 |
447 | if username is not None and password is not None:
448 | # Send the basic auth with this request
449 | req = HTTPBasicAuth(username, password)(req)
450 |
451 | # Attach a hook to handle 401 responses
452 | req.register_hook("response", self.handle_401)
453 |
454 | return req
455 |
456 | # Factored out to allow for easy patching in tests
457 | def _prompt_for_password(
458 | self, netloc: str
459 | ) -> Tuple[Optional[str], Optional[str], bool]:
460 | username = ask_input(f"User for {netloc}: ") if self.prompting else None
461 | if not username:
462 | return None, None, False
463 | if self.use_keyring:
464 | auth = self._get_keyring_auth(netloc, username)
465 | if auth and auth[0] is not None and auth[1] is not None:
466 | return auth[0], auth[1], False
467 | password = ask_password("Password: ")
468 | return username, password, True
469 |
470 | # Factored out to allow for easy patching in tests
471 | def _should_save_password_to_keyring(self) -> bool:
472 | if (
473 | not self.prompting
474 | or not self.use_keyring
475 | or not self.keyring_provider.has_keyring
476 | ):
477 | return False
478 | return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
479 |
480 | def handle_401(self, resp: Response, **kwargs: Any) -> Response:
481 | # We only care about 401 responses, anything else we want to just
482 | # pass through the actual response
483 | if resp.status_code != 401:
484 | return resp
485 |
486 | username, password = None, None
487 |
488 | # Query the keyring for credentials:
489 | if self.use_keyring:
490 | username, password = self._get_new_credentials(
491 | resp.url,
492 | allow_netrc=False,
493 | allow_keyring=True,
494 | )
495 |
496 | # We are not able to prompt the user so simply return the response
497 | if not self.prompting and not username and not password:
498 | return resp
499 |
500 | parsed = urllib.parse.urlparse(resp.url)
501 |
502 | # Prompt the user for a new username and password
503 | save = False
504 | if not username and not password:
505 | username, password, save = self._prompt_for_password(parsed.netloc)
506 |
507 | # Store the new username and password to use for future requests
508 | self._credentials_to_save = None
509 | if username is not None and password is not None:
510 | self.passwords[parsed.netloc] = (username, password)
511 |
512 | # Prompt to save the password to keyring
513 | if save and self._should_save_password_to_keyring():
514 | self._credentials_to_save = Credentials(
515 | url=parsed.netloc,
516 | username=username,
517 | password=password,
518 | )
519 |
520 | # Consume content and release the original connection to allow our new
521 | # request to reuse the same one.
522 | # The result of the assignment isn't used, it's just needed to consume
523 | # the content.
524 | _ = resp.content
525 | resp.raw.release_conn()
526 |
527 | # Add our new username and password to the request
528 | req = HTTPBasicAuth(username or "", password or "")(resp.request)
529 | req.register_hook("response", self.warn_on_401)
530 |
531 | # On successful request, save the credentials that were used to
532 | # keyring. (Note that if the user responded "no" above, this member
533 | # is not set and nothing will be saved.)
534 | if self._credentials_to_save:
535 | req.register_hook("response", self.save_credentials)
536 |
537 | # Send our new request
538 | new_resp = resp.connection.send(req, **kwargs)
539 | new_resp.history.append(resp)
540 |
541 | return new_resp
542 |
543 | def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
544 | """Response callback to warn about incorrect credentials."""
545 | if resp.status_code == 401:
546 | logger.warning(
547 | "401 Error, Credentials not correct for %s",
548 | resp.request.url,
549 | )
550 |
551 | def save_credentials(self, resp: Response, **kwargs: Any) -> None:
552 | """Response callback to save credentials on success."""
553 | assert (
554 | self.keyring_provider.has_keyring
555 | ), "should never reach here without keyring"
556 |
557 | creds = self._credentials_to_save
558 | self._credentials_to_save = None
559 | if creds and resp.status_code < 400:
560 | try:
561 | logger.info("Saving credentials to keyring")
562 | self.keyring_provider.save_auth_info(
563 | creds.url, creds.username, creds.password
564 | )
565 | except Exception:
566 | logger.exception("Failed to save credentials")
567 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/werkzeug/wsgi.py:
--------------------------------------------------------------------------------
```python
1 | from __future__ import annotations
2 |
3 | import io
4 | import typing as t
5 | from functools import partial
6 | from functools import update_wrapper
7 |
8 | from .exceptions import ClientDisconnected
9 | from .exceptions import RequestEntityTooLarge
10 | from .sansio import utils as _sansio_utils
11 | from .sansio.utils import host_is_trusted # noqa: F401 # Imported as part of API
12 |
13 | if t.TYPE_CHECKING:
14 | from _typeshed.wsgi import WSGIApplication
15 | from _typeshed.wsgi import WSGIEnvironment
16 |
17 |
18 | def responder(f: t.Callable[..., WSGIApplication]) -> WSGIApplication:
19 | """Marks a function as responder. Decorate a function with it and it
20 | will automatically call the return value as WSGI application.
21 |
22 | Example::
23 |
24 | @responder
25 | def application(environ, start_response):
26 | return Response('Hello World!')
27 | """
28 | return update_wrapper(lambda *a: f(*a)(*a[-2:]), f)
29 |
30 |
31 | def get_current_url(
32 | environ: WSGIEnvironment,
33 | root_only: bool = False,
34 | strip_querystring: bool = False,
35 | host_only: bool = False,
36 | trusted_hosts: t.Iterable[str] | None = None,
37 | ) -> str:
38 | """Recreate the URL for a request from the parts in a WSGI
39 | environment.
40 |
41 | The URL is an IRI, not a URI, so it may contain Unicode characters.
42 | Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII.
43 |
44 | :param environ: The WSGI environment to get the URL parts from.
45 | :param root_only: Only build the root path, don't include the
46 | remaining path or query string.
47 | :param strip_querystring: Don't include the query string.
48 | :param host_only: Only build the scheme and host.
49 | :param trusted_hosts: A list of trusted host names to validate the
50 | host against.
51 | """
52 | parts = {
53 | "scheme": environ["wsgi.url_scheme"],
54 | "host": get_host(environ, trusted_hosts),
55 | }
56 |
57 | if not host_only:
58 | parts["root_path"] = environ.get("SCRIPT_NAME", "")
59 |
60 | if not root_only:
61 | parts["path"] = environ.get("PATH_INFO", "")
62 |
63 | if not strip_querystring:
64 | parts["query_string"] = environ.get("QUERY_STRING", "").encode("latin1")
65 |
66 | return _sansio_utils.get_current_url(**parts)
67 |
68 |
69 | def _get_server(
70 | environ: WSGIEnvironment,
71 | ) -> tuple[str, int | None] | None:
72 | name = environ.get("SERVER_NAME")
73 |
74 | if name is None:
75 | return None
76 |
77 | try:
78 | port: int | None = int(environ.get("SERVER_PORT", None))
79 | except (TypeError, ValueError):
80 | # unix socket
81 | port = None
82 |
83 | return name, port
84 |
85 |
86 | def get_host(
87 | environ: WSGIEnvironment, trusted_hosts: t.Iterable[str] | None = None
88 | ) -> str:
89 | """Return the host for the given WSGI environment.
90 |
91 | The ``Host`` header is preferred, then ``SERVER_NAME`` if it's not
92 | set. The returned host will only contain the port if it is different
93 | than the standard port for the protocol.
94 |
95 | Optionally, verify that the host is trusted using
96 | :func:`host_is_trusted` and raise a
97 | :exc:`~werkzeug.exceptions.SecurityError` if it is not.
98 |
99 | :param environ: A WSGI environment dict.
100 | :param trusted_hosts: A list of trusted host names.
101 |
102 | :return: Host, with port if necessary.
103 | :raise ~werkzeug.exceptions.SecurityError: If the host is not
104 | trusted.
105 | """
106 | return _sansio_utils.get_host(
107 | environ["wsgi.url_scheme"],
108 | environ.get("HTTP_HOST"),
109 | _get_server(environ),
110 | trusted_hosts,
111 | )
112 |
113 |
114 | def get_content_length(environ: WSGIEnvironment) -> int | None:
115 | """Return the ``Content-Length`` header value as an int. If the header is not given
116 | or the ``Transfer-Encoding`` header is ``chunked``, ``None`` is returned to indicate
117 | a streaming request. If the value is not an integer, or negative, 0 is returned.
118 |
119 | :param environ: The WSGI environ to get the content length from.
120 |
121 | .. versionadded:: 0.9
122 | """
123 | return _sansio_utils.get_content_length(
124 | http_content_length=environ.get("CONTENT_LENGTH"),
125 | http_transfer_encoding=environ.get("HTTP_TRANSFER_ENCODING"),
126 | )
127 |
128 |
129 | def get_input_stream(
130 | environ: WSGIEnvironment,
131 | safe_fallback: bool = True,
132 | max_content_length: int | None = None,
133 | ) -> t.IO[bytes]:
134 | """Return the WSGI input stream, wrapped so that it may be read safely without going
135 | past the ``Content-Length`` header value or ``max_content_length``.
136 |
137 | If ``Content-Length`` exceeds ``max_content_length``, a
138 | :exc:`RequestEntityTooLarge`` ``413 Content Too Large`` error is raised.
139 |
140 | If the WSGI server sets ``environ["wsgi.input_terminated"]``, it indicates that the
141 | server handles terminating the stream, so it is safe to read directly. For example,
142 | a server that knows how to handle chunked requests safely would set this.
143 |
144 | If ``max_content_length`` is set, it can be enforced on streams if
145 | ``wsgi.input_terminated`` is set. Otherwise, an empty stream is returned unless the
146 | user explicitly disables this safe fallback.
147 |
148 | If the limit is reached before the underlying stream is exhausted (such as a file
149 | that is too large, or an infinite stream), the remaining contents of the stream
150 | cannot be read safely. Depending on how the server handles this, clients may show a
151 | "connection reset" failure instead of seeing the 413 response.
152 |
153 | :param environ: The WSGI environ containing the stream.
154 | :param safe_fallback: Return an empty stream when ``Content-Length`` is not set.
155 | Disabling this allows infinite streams, which can be a denial-of-service risk.
156 | :param max_content_length: The maximum length that content-length or streaming
157 | requests may not exceed.
158 |
159 | .. versionchanged:: 2.3.2
160 | ``max_content_length`` is only applied to streaming requests if the server sets
161 | ``wsgi.input_terminated``.
162 |
163 | .. versionchanged:: 2.3
164 | Check ``max_content_length`` and raise an error if it is exceeded.
165 |
166 | .. versionadded:: 0.9
167 | """
168 | stream = t.cast(t.IO[bytes], environ["wsgi.input"])
169 | content_length = get_content_length(environ)
170 |
171 | if content_length is not None and max_content_length is not None:
172 | if content_length > max_content_length:
173 | raise RequestEntityTooLarge()
174 |
175 | # A WSGI server can set this to indicate that it terminates the input stream. In
176 | # that case the stream is safe without wrapping, or can enforce a max length.
177 | if "wsgi.input_terminated" in environ:
178 | if max_content_length is not None:
179 | # If this is moved above, it can cause the stream to hang if a read attempt
180 | # is made when the client sends no data. For example, the development server
181 | # does not handle buffering except for chunked encoding.
182 | return t.cast(
183 | t.IO[bytes], LimitedStream(stream, max_content_length, is_max=True)
184 | )
185 |
186 | return stream
187 |
188 | # No limit given, return an empty stream unless the user explicitly allows the
189 | # potentially infinite stream. An infinite stream is dangerous if it's not expected,
190 | # as it can tie up a worker indefinitely.
191 | if content_length is None:
192 | return io.BytesIO() if safe_fallback else stream
193 |
194 | return t.cast(t.IO[bytes], LimitedStream(stream, content_length))
195 |
196 |
197 | def get_path_info(environ: WSGIEnvironment) -> str:
198 | """Return ``PATH_INFO`` from the WSGI environment.
199 |
200 | :param environ: WSGI environment to get the path from.
201 |
202 | .. versionchanged:: 3.0
203 | The ``charset`` and ``errors`` parameters were removed.
204 |
205 | .. versionadded:: 0.9
206 | """
207 | path: bytes = environ.get("PATH_INFO", "").encode("latin1")
208 | return path.decode(errors="replace")
209 |
210 |
211 | class ClosingIterator:
212 | """The WSGI specification requires that all middlewares and gateways
213 | respect the `close` callback of the iterable returned by the application.
214 | Because it is useful to add another close action to a returned iterable
215 | and adding a custom iterable is a boring task this class can be used for
216 | that::
217 |
218 | return ClosingIterator(app(environ, start_response), [cleanup_session,
219 | cleanup_locals])
220 |
221 | If there is just one close function it can be passed instead of the list.
222 |
223 | A closing iterator is not needed if the application uses response objects
224 | and finishes the processing if the response is started::
225 |
226 | try:
227 | return response(environ, start_response)
228 | finally:
229 | cleanup_session()
230 | cleanup_locals()
231 | """
232 |
233 | def __init__(
234 | self,
235 | iterable: t.Iterable[bytes],
236 | callbacks: None
237 | | (t.Callable[[], None] | t.Iterable[t.Callable[[], None]]) = None,
238 | ) -> None:
239 | iterator = iter(iterable)
240 | self._next = t.cast(t.Callable[[], bytes], partial(next, iterator))
241 | if callbacks is None:
242 | callbacks = []
243 | elif callable(callbacks):
244 | callbacks = [callbacks]
245 | else:
246 | callbacks = list(callbacks)
247 | iterable_close = getattr(iterable, "close", None)
248 | if iterable_close:
249 | callbacks.insert(0, iterable_close)
250 | self._callbacks = callbacks
251 |
252 | def __iter__(self) -> ClosingIterator:
253 | return self
254 |
255 | def __next__(self) -> bytes:
256 | return self._next()
257 |
258 | def close(self) -> None:
259 | for callback in self._callbacks:
260 | callback()
261 |
262 |
263 | def wrap_file(
264 | environ: WSGIEnvironment, file: t.IO[bytes], buffer_size: int = 8192
265 | ) -> t.Iterable[bytes]:
266 | """Wraps a file. This uses the WSGI server's file wrapper if available
267 | or otherwise the generic :class:`FileWrapper`.
268 |
269 | .. versionadded:: 0.5
270 |
271 | If the file wrapper from the WSGI server is used it's important to not
272 | iterate over it from inside the application but to pass it through
273 | unchanged. If you want to pass out a file wrapper inside a response
274 | object you have to set :attr:`Response.direct_passthrough` to `True`.
275 |
276 | More information about file wrappers are available in :pep:`333`.
277 |
278 | :param file: a :class:`file`-like object with a :meth:`~file.read` method.
279 | :param buffer_size: number of bytes for one iteration.
280 | """
281 | return environ.get("wsgi.file_wrapper", FileWrapper)( # type: ignore
282 | file, buffer_size
283 | )
284 |
285 |
286 | class FileWrapper:
287 | """This class can be used to convert a :class:`file`-like object into
288 | an iterable. It yields `buffer_size` blocks until the file is fully
289 | read.
290 |
291 | You should not use this class directly but rather use the
292 | :func:`wrap_file` function that uses the WSGI server's file wrapper
293 | support if it's available.
294 |
295 | .. versionadded:: 0.5
296 |
297 | If you're using this object together with a :class:`Response` you have
298 | to use the `direct_passthrough` mode.
299 |
300 | :param file: a :class:`file`-like object with a :meth:`~file.read` method.
301 | :param buffer_size: number of bytes for one iteration.
302 | """
303 |
304 | def __init__(self, file: t.IO[bytes], buffer_size: int = 8192) -> None:
305 | self.file = file
306 | self.buffer_size = buffer_size
307 |
308 | def close(self) -> None:
309 | if hasattr(self.file, "close"):
310 | self.file.close()
311 |
312 | def seekable(self) -> bool:
313 | if hasattr(self.file, "seekable"):
314 | return self.file.seekable()
315 | if hasattr(self.file, "seek"):
316 | return True
317 | return False
318 |
319 | def seek(self, *args: t.Any) -> None:
320 | if hasattr(self.file, "seek"):
321 | self.file.seek(*args)
322 |
323 | def tell(self) -> int | None:
324 | if hasattr(self.file, "tell"):
325 | return self.file.tell()
326 | return None
327 |
328 | def __iter__(self) -> FileWrapper:
329 | return self
330 |
331 | def __next__(self) -> bytes:
332 | data = self.file.read(self.buffer_size)
333 | if data:
334 | return data
335 | raise StopIteration()
336 |
337 |
338 | class _RangeWrapper:
339 | # private for now, but should we make it public in the future ?
340 |
341 | """This class can be used to convert an iterable object into
342 | an iterable that will only yield a piece of the underlying content.
343 | It yields blocks until the underlying stream range is fully read.
344 | The yielded blocks will have a size that can't exceed the original
345 | iterator defined block size, but that can be smaller.
346 |
347 | If you're using this object together with a :class:`Response` you have
348 | to use the `direct_passthrough` mode.
349 |
350 | :param iterable: an iterable object with a :meth:`__next__` method.
351 | :param start_byte: byte from which read will start.
352 | :param byte_range: how many bytes to read.
353 | """
354 |
355 | def __init__(
356 | self,
357 | iterable: t.Iterable[bytes] | t.IO[bytes],
358 | start_byte: int = 0,
359 | byte_range: int | None = None,
360 | ):
361 | self.iterable = iter(iterable)
362 | self.byte_range = byte_range
363 | self.start_byte = start_byte
364 | self.end_byte = None
365 |
366 | if byte_range is not None:
367 | self.end_byte = start_byte + byte_range
368 |
369 | self.read_length = 0
370 | self.seekable = hasattr(iterable, "seekable") and iterable.seekable()
371 | self.end_reached = False
372 |
373 | def __iter__(self) -> _RangeWrapper:
374 | return self
375 |
376 | def _next_chunk(self) -> bytes:
377 | try:
378 | chunk = next(self.iterable)
379 | self.read_length += len(chunk)
380 | return chunk
381 | except StopIteration:
382 | self.end_reached = True
383 | raise
384 |
385 | def _first_iteration(self) -> tuple[bytes | None, int]:
386 | chunk = None
387 | if self.seekable:
388 | self.iterable.seek(self.start_byte) # type: ignore
389 | self.read_length = self.iterable.tell() # type: ignore
390 | contextual_read_length = self.read_length
391 | else:
392 | while self.read_length <= self.start_byte:
393 | chunk = self._next_chunk()
394 | if chunk is not None:
395 | chunk = chunk[self.start_byte - self.read_length :]
396 | contextual_read_length = self.start_byte
397 | return chunk, contextual_read_length
398 |
399 | def _next(self) -> bytes:
400 | if self.end_reached:
401 | raise StopIteration()
402 | chunk = None
403 | contextual_read_length = self.read_length
404 | if self.read_length == 0:
405 | chunk, contextual_read_length = self._first_iteration()
406 | if chunk is None:
407 | chunk = self._next_chunk()
408 | if self.end_byte is not None and self.read_length >= self.end_byte:
409 | self.end_reached = True
410 | return chunk[: self.end_byte - contextual_read_length]
411 | return chunk
412 |
413 | def __next__(self) -> bytes:
414 | chunk = self._next()
415 | if chunk:
416 | return chunk
417 | self.end_reached = True
418 | raise StopIteration()
419 |
420 | def close(self) -> None:
421 | if hasattr(self.iterable, "close"):
422 | self.iterable.close()
423 |
424 |
425 | class LimitedStream(io.RawIOBase):
426 | """Wrap a stream so that it doesn't read more than a given limit. This is used to
427 | limit ``wsgi.input`` to the ``Content-Length`` header value or
428 | :attr:`.Request.max_content_length`.
429 |
430 | When attempting to read after the limit has been reached, :meth:`on_exhausted` is
431 | called. When the limit is a maximum, this raises :exc:`.RequestEntityTooLarge`.
432 |
433 | If reading from the stream returns zero bytes or raises an error,
434 | :meth:`on_disconnect` is called, which raises :exc:`.ClientDisconnected`. When the
435 | limit is a maximum and zero bytes were read, no error is raised, since it may be the
436 | end of the stream.
437 |
438 | If the limit is reached before the underlying stream is exhausted (such as a file
439 | that is too large, or an infinite stream), the remaining contents of the stream
440 | cannot be read safely. Depending on how the server handles this, clients may show a
441 | "connection reset" failure instead of seeing the 413 response.
442 |
443 | :param stream: The stream to read from. Must be a readable binary IO object.
444 | :param limit: The limit in bytes to not read past. Should be either the
445 | ``Content-Length`` header value or ``request.max_content_length``.
446 | :param is_max: Whether the given ``limit`` is ``request.max_content_length`` instead
447 | of the ``Content-Length`` header value. This changes how exhausted and
448 | disconnect events are handled.
449 |
450 | .. versionchanged:: 2.3
451 | Handle ``max_content_length`` differently than ``Content-Length``.
452 |
453 | .. versionchanged:: 2.3
454 | Implements ``io.RawIOBase`` rather than ``io.IOBase``.
455 | """
456 |
457 | def __init__(self, stream: t.IO[bytes], limit: int, is_max: bool = False) -> None:
458 | self._stream = stream
459 | self._pos = 0
460 | self.limit = limit
461 | self._limit_is_max = is_max
462 |
463 | @property
464 | def is_exhausted(self) -> bool:
465 | """Whether the current stream position has reached the limit."""
466 | return self._pos >= self.limit
467 |
468 | def on_exhausted(self) -> None:
469 | """Called when attempting to read after the limit has been reached.
470 |
471 | The default behavior is to do nothing, unless the limit is a maximum, in which
472 | case it raises :exc:`.RequestEntityTooLarge`.
473 |
474 | .. versionchanged:: 2.3
475 | Raises ``RequestEntityTooLarge`` if the limit is a maximum.
476 |
477 | .. versionchanged:: 2.3
478 | Any return value is ignored.
479 | """
480 | if self._limit_is_max:
481 | raise RequestEntityTooLarge()
482 |
483 | def on_disconnect(self, error: Exception | None = None) -> None:
484 | """Called when an attempted read receives zero bytes before the limit was
485 | reached. This indicates that the client disconnected before sending the full
486 | request body.
487 |
488 | The default behavior is to raise :exc:`.ClientDisconnected`, unless the limit is
489 | a maximum and no error was raised.
490 |
491 | .. versionchanged:: 2.3
492 | Added the ``error`` parameter. Do nothing if the limit is a maximum and no
493 | error was raised.
494 |
495 | .. versionchanged:: 2.3
496 | Any return value is ignored.
497 | """
498 | if not self._limit_is_max or error is not None:
499 | raise ClientDisconnected()
500 |
501 | # If the limit is a maximum, then we may have read zero bytes because the
502 | # streaming body is complete. There's no way to distinguish that from the
503 | # client disconnecting early.
504 |
505 | def exhaust(self) -> bytes:
506 | """Exhaust the stream by reading until the limit is reached or the client
507 | disconnects, returning the remaining data.
508 |
509 | .. versionchanged:: 2.3
510 | Return the remaining data.
511 |
512 | .. versionchanged:: 2.2.3
513 | Handle case where wrapped stream returns fewer bytes than requested.
514 | """
515 | if not self.is_exhausted:
516 | return self.readall()
517 |
518 | return b""
519 |
520 | def readinto(self, b: bytearray) -> int | None: # type: ignore[override]
521 | size = len(b)
522 | remaining = self.limit - self._pos
523 |
524 | if remaining <= 0:
525 | self.on_exhausted()
526 | return 0
527 |
528 | if hasattr(self._stream, "readinto"):
529 | # Use stream.readinto if it's available.
530 | if size <= remaining:
531 | # The size fits in the remaining limit, use the buffer directly.
532 | try:
533 | out_size: int | None = self._stream.readinto(b)
534 | except (OSError, ValueError) as e:
535 | self.on_disconnect(error=e)
536 | return 0
537 | else:
538 | # Use a temp buffer with the remaining limit as the size.
539 | temp_b = bytearray(remaining)
540 |
541 | try:
542 | out_size = self._stream.readinto(temp_b)
543 | except (OSError, ValueError) as e:
544 | self.on_disconnect(error=e)
545 | return 0
546 |
547 | if out_size:
548 | b[:out_size] = temp_b
549 | else:
550 | # WSGI requires that stream.read is available.
551 | try:
552 | data = self._stream.read(min(size, remaining))
553 | except (OSError, ValueError) as e:
554 | self.on_disconnect(error=e)
555 | return 0
556 |
557 | out_size = len(data)
558 | b[:out_size] = data
559 |
560 | if not out_size:
561 | # Read zero bytes from the stream.
562 | self.on_disconnect()
563 | return 0
564 |
565 | self._pos += out_size
566 | return out_size
567 |
568 | def readall(self) -> bytes:
569 | if self.is_exhausted:
570 | self.on_exhausted()
571 | return b""
572 |
573 | out = bytearray()
574 |
575 | # The parent implementation uses "while True", which results in an extra read.
576 | while not self.is_exhausted:
577 | data = self.read(1024 * 64)
578 |
579 | # Stream may return empty before a max limit is reached.
580 | if not data:
581 | break
582 |
583 | out.extend(data)
584 |
585 | return bytes(out)
586 |
587 | def tell(self) -> int:
588 | """Return the current stream position.
589 |
590 | .. versionadded:: 0.9
591 | """
592 | return self._pos
593 |
594 | def readable(self) -> bool:
595 | return True
596 |
```
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/pip/_internal/models/link.py:
--------------------------------------------------------------------------------
```python
1 | import functools
2 | import itertools
3 | import logging
4 | import os
5 | import posixpath
6 | import re
7 | import urllib.parse
8 | from dataclasses import dataclass
9 | from typing import (
10 | TYPE_CHECKING,
11 | Any,
12 | Dict,
13 | List,
14 | Mapping,
15 | NamedTuple,
16 | Optional,
17 | Tuple,
18 | Union,
19 | )
20 |
21 | from pip._internal.utils.deprecation import deprecated
22 | from pip._internal.utils.filetypes import WHEEL_EXTENSION
23 | from pip._internal.utils.hashes import Hashes
24 | from pip._internal.utils.misc import (
25 | pairwise,
26 | redact_auth_from_url,
27 | split_auth_from_netloc,
28 | splitext,
29 | )
30 | from pip._internal.utils.urls import path_to_url, url_to_path
31 |
32 | if TYPE_CHECKING:
33 | from pip._internal.index.collector import IndexContent
34 |
35 | logger = logging.getLogger(__name__)
36 |
37 |
38 | # Order matters, earlier hashes have a precedence over later hashes for what
39 | # we will pick to use.
40 | _SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5")
41 |
42 |
43 | @dataclass(frozen=True)
44 | class LinkHash:
45 | """Links to content may have embedded hash values. This class parses those.
46 |
47 | `name` must be any member of `_SUPPORTED_HASHES`.
48 |
49 | This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to
50 | be JSON-serializable to conform to PEP 610, this class contains the logic for
51 | parsing a hash name and value for correctness, and then checking whether that hash
52 | conforms to a schema with `.is_hash_allowed()`."""
53 |
54 | name: str
55 | value: str
56 |
57 | _hash_url_fragment_re = re.compile(
58 | # NB: we do not validate that the second group (.*) is a valid hex
59 | # digest. Instead, we simply keep that string in this class, and then check it
60 | # against Hashes when hash-checking is needed. This is easier to debug than
61 | # proactively discarding an invalid hex digest, as we handle incorrect hashes
62 | # and malformed hashes in the same place.
63 | r"[#&]({choices})=([^&]*)".format(
64 | choices="|".join(re.escape(hash_name) for hash_name in _SUPPORTED_HASHES)
65 | ),
66 | )
67 |
68 | def __post_init__(self) -> None:
69 | assert self.name in _SUPPORTED_HASHES
70 |
71 | @classmethod
72 | @functools.lru_cache(maxsize=None)
73 | def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
74 | """Search a string for a checksum algorithm name and encoded output value."""
75 | match = cls._hash_url_fragment_re.search(url)
76 | if match is None:
77 | return None
78 | name, value = match.groups()
79 | return cls(name=name, value=value)
80 |
81 | def as_dict(self) -> Dict[str, str]:
82 | return {self.name: self.value}
83 |
84 | def as_hashes(self) -> Hashes:
85 | """Return a Hashes instance which checks only for the current hash."""
86 | return Hashes({self.name: [self.value]})
87 |
88 | def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
89 | """
90 | Return True if the current hash is allowed by `hashes`.
91 | """
92 | if hashes is None:
93 | return False
94 | return hashes.is_hash_allowed(self.name, hex_digest=self.value)
95 |
96 |
97 | @dataclass(frozen=True)
98 | class MetadataFile:
99 | """Information about a core metadata file associated with a distribution."""
100 |
101 | hashes: Optional[Dict[str, str]]
102 |
103 | def __post_init__(self) -> None:
104 | if self.hashes is not None:
105 | assert all(name in _SUPPORTED_HASHES for name in self.hashes)
106 |
107 |
108 | def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
109 | # Remove any unsupported hash types from the mapping. If this leaves no
110 | # supported hashes, return None
111 | if hashes is None:
112 | return None
113 | hashes = {n: v for n, v in hashes.items() if n in _SUPPORTED_HASHES}
114 | if not hashes:
115 | return None
116 | return hashes
117 |
118 |
119 | def _clean_url_path_part(part: str) -> str:
120 | """
121 | Clean a "part" of a URL path (i.e. after splitting on "@" characters).
122 | """
123 | # We unquote prior to quoting to make sure nothing is double quoted.
124 | return urllib.parse.quote(urllib.parse.unquote(part))
125 |
126 |
127 | def _clean_file_url_path(part: str) -> str:
128 | """
129 | Clean the first part of a URL path that corresponds to a local
130 | filesystem path (i.e. the first part after splitting on "@" characters).
131 | """
132 | # We unquote prior to quoting to make sure nothing is double quoted.
133 | # Also, on Windows the path part might contain a drive letter which
134 | # should not be quoted. On Linux where drive letters do not
135 | # exist, the colon should be quoted. We rely on urllib.request
136 | # to do the right thing here.
137 | return urllib.request.pathname2url(urllib.request.url2pathname(part))
138 |
139 |
140 | # percent-encoded: /
141 | _reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
142 |
143 |
144 | def _clean_url_path(path: str, is_local_path: bool) -> str:
145 | """
146 | Clean the path portion of a URL.
147 | """
148 | if is_local_path:
149 | clean_func = _clean_file_url_path
150 | else:
151 | clean_func = _clean_url_path_part
152 |
153 | # Split on the reserved characters prior to cleaning so that
154 | # revision strings in VCS URLs are properly preserved.
155 | parts = _reserved_chars_re.split(path)
156 |
157 | cleaned_parts = []
158 | for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
159 | cleaned_parts.append(clean_func(to_clean))
160 | # Normalize %xx escapes (e.g. %2f -> %2F)
161 | cleaned_parts.append(reserved.upper())
162 |
163 | return "".join(cleaned_parts)
164 |
165 |
166 | def _ensure_quoted_url(url: str) -> str:
167 | """
168 | Make sure a link is fully quoted.
169 | For example, if ' ' occurs in the URL, it will be replaced with "%20",
170 | and without double-quoting other characters.
171 | """
172 | # Split the URL into parts according to the general structure
173 | # `scheme://netloc/path;parameters?query#fragment`.
174 | result = urllib.parse.urlparse(url)
175 | # If the netloc is empty, then the URL refers to a local filesystem path.
176 | is_local_path = not result.netloc
177 | path = _clean_url_path(result.path, is_local_path=is_local_path)
178 | return urllib.parse.urlunparse(result._replace(path=path))
179 |
180 |
181 | @functools.total_ordering
182 | class Link:
183 | """Represents a parsed link from a Package Index's simple URL"""
184 |
185 | __slots__ = [
186 | "_parsed_url",
187 | "_url",
188 | "_hashes",
189 | "comes_from",
190 | "requires_python",
191 | "yanked_reason",
192 | "metadata_file_data",
193 | "cache_link_parsing",
194 | "egg_fragment",
195 | ]
196 |
197 | def __init__(
198 | self,
199 | url: str,
200 | comes_from: Optional[Union[str, "IndexContent"]] = None,
201 | requires_python: Optional[str] = None,
202 | yanked_reason: Optional[str] = None,
203 | metadata_file_data: Optional[MetadataFile] = None,
204 | cache_link_parsing: bool = True,
205 | hashes: Optional[Mapping[str, str]] = None,
206 | ) -> None:
207 | """
208 | :param url: url of the resource pointed to (href of the link)
209 | :param comes_from: instance of IndexContent where the link was found,
210 | or string.
211 | :param requires_python: String containing the `Requires-Python`
212 | metadata field, specified in PEP 345. This may be specified by
213 | a data-requires-python attribute in the HTML link tag, as
214 | described in PEP 503.
215 | :param yanked_reason: the reason the file has been yanked, if the
216 | file has been yanked, or None if the file hasn't been yanked.
217 | This is the value of the "data-yanked" attribute, if present, in
218 | a simple repository HTML link. If the file has been yanked but
219 | no reason was provided, this should be the empty string. See
220 | PEP 592 for more information and the specification.
221 | :param metadata_file_data: the metadata attached to the file, or None if
222 | no such metadata is provided. This argument, if not None, indicates
223 | that a separate metadata file exists, and also optionally supplies
224 | hashes for that file.
225 | :param cache_link_parsing: A flag that is used elsewhere to determine
226 | whether resources retrieved from this link should be cached. PyPI
227 | URLs should generally have this set to False, for example.
228 | :param hashes: A mapping of hash names to digests to allow us to
229 | determine the validity of a download.
230 | """
231 |
232 | # The comes_from, requires_python, and metadata_file_data arguments are
233 | # only used by classmethods of this class, and are not used in client
234 | # code directly.
235 |
236 | # url can be a UNC windows share
237 | if url.startswith("\\\\"):
238 | url = path_to_url(url)
239 |
240 | self._parsed_url = urllib.parse.urlsplit(url)
241 | # Store the url as a private attribute to prevent accidentally
242 | # trying to set a new value.
243 | self._url = url
244 |
245 | link_hash = LinkHash.find_hash_url_fragment(url)
246 | hashes_from_link = {} if link_hash is None else link_hash.as_dict()
247 | if hashes is None:
248 | self._hashes = hashes_from_link
249 | else:
250 | self._hashes = {**hashes, **hashes_from_link}
251 |
252 | self.comes_from = comes_from
253 | self.requires_python = requires_python if requires_python else None
254 | self.yanked_reason = yanked_reason
255 | self.metadata_file_data = metadata_file_data
256 |
257 | self.cache_link_parsing = cache_link_parsing
258 | self.egg_fragment = self._egg_fragment()
259 |
260 | @classmethod
261 | def from_json(
262 | cls,
263 | file_data: Dict[str, Any],
264 | page_url: str,
265 | ) -> Optional["Link"]:
266 | """
267 | Convert an pypi json document from a simple repository page into a Link.
268 | """
269 | file_url = file_data.get("url")
270 | if file_url is None:
271 | return None
272 |
273 | url = _ensure_quoted_url(urllib.parse.urljoin(page_url, file_url))
274 | pyrequire = file_data.get("requires-python")
275 | yanked_reason = file_data.get("yanked")
276 | hashes = file_data.get("hashes", {})
277 |
278 | # PEP 714: Indexes must use the name core-metadata, but
279 | # clients should support the old name as a fallback for compatibility.
280 | metadata_info = file_data.get("core-metadata")
281 | if metadata_info is None:
282 | metadata_info = file_data.get("dist-info-metadata")
283 |
284 | # The metadata info value may be a boolean, or a dict of hashes.
285 | if isinstance(metadata_info, dict):
286 | # The file exists, and hashes have been supplied
287 | metadata_file_data = MetadataFile(supported_hashes(metadata_info))
288 | elif metadata_info:
289 | # The file exists, but there are no hashes
290 | metadata_file_data = MetadataFile(None)
291 | else:
292 | # False or not present: the file does not exist
293 | metadata_file_data = None
294 |
295 | # The Link.yanked_reason expects an empty string instead of a boolean.
296 | if yanked_reason and not isinstance(yanked_reason, str):
297 | yanked_reason = ""
298 | # The Link.yanked_reason expects None instead of False.
299 | elif not yanked_reason:
300 | yanked_reason = None
301 |
302 | return cls(
303 | url,
304 | comes_from=page_url,
305 | requires_python=pyrequire,
306 | yanked_reason=yanked_reason,
307 | hashes=hashes,
308 | metadata_file_data=metadata_file_data,
309 | )
310 |
311 | @classmethod
312 | def from_element(
313 | cls,
314 | anchor_attribs: Dict[str, Optional[str]],
315 | page_url: str,
316 | base_url: str,
317 | ) -> Optional["Link"]:
318 | """
319 | Convert an anchor element's attributes in a simple repository page to a Link.
320 | """
321 | href = anchor_attribs.get("href")
322 | if not href:
323 | return None
324 |
325 | url = _ensure_quoted_url(urllib.parse.urljoin(base_url, href))
326 | pyrequire = anchor_attribs.get("data-requires-python")
327 | yanked_reason = anchor_attribs.get("data-yanked")
328 |
329 | # PEP 714: Indexes must use the name data-core-metadata, but
330 | # clients should support the old name as a fallback for compatibility.
331 | metadata_info = anchor_attribs.get("data-core-metadata")
332 | if metadata_info is None:
333 | metadata_info = anchor_attribs.get("data-dist-info-metadata")
334 | # The metadata info value may be the string "true", or a string of
335 | # the form "hashname=hashval"
336 | if metadata_info == "true":
337 | # The file exists, but there are no hashes
338 | metadata_file_data = MetadataFile(None)
339 | elif metadata_info is None:
340 | # The file does not exist
341 | metadata_file_data = None
342 | else:
343 | # The file exists, and hashes have been supplied
344 | hashname, sep, hashval = metadata_info.partition("=")
345 | if sep == "=":
346 | metadata_file_data = MetadataFile(supported_hashes({hashname: hashval}))
347 | else:
348 | # Error - data is wrong. Treat as no hashes supplied.
349 | logger.debug(
350 | "Index returned invalid data-dist-info-metadata value: %s",
351 | metadata_info,
352 | )
353 | metadata_file_data = MetadataFile(None)
354 |
355 | return cls(
356 | url,
357 | comes_from=page_url,
358 | requires_python=pyrequire,
359 | yanked_reason=yanked_reason,
360 | metadata_file_data=metadata_file_data,
361 | )
362 |
363 | def __str__(self) -> str:
364 | if self.requires_python:
365 | rp = f" (requires-python:{self.requires_python})"
366 | else:
367 | rp = ""
368 | if self.comes_from:
369 | return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}"
370 | else:
371 | return redact_auth_from_url(str(self._url))
372 |
373 | def __repr__(self) -> str:
374 | return f"<Link {self}>"
375 |
376 | def __hash__(self) -> int:
377 | return hash(self.url)
378 |
379 | def __eq__(self, other: Any) -> bool:
380 | if not isinstance(other, Link):
381 | return NotImplemented
382 | return self.url == other.url
383 |
384 | def __lt__(self, other: Any) -> bool:
385 | if not isinstance(other, Link):
386 | return NotImplemented
387 | return self.url < other.url
388 |
389 | @property
390 | def url(self) -> str:
391 | return self._url
392 |
393 | @property
394 | def filename(self) -> str:
395 | path = self.path.rstrip("/")
396 | name = posixpath.basename(path)
397 | if not name:
398 | # Make sure we don't leak auth information if the netloc
399 | # includes a username and password.
400 | netloc, user_pass = split_auth_from_netloc(self.netloc)
401 | return netloc
402 |
403 | name = urllib.parse.unquote(name)
404 | assert name, f"URL {self._url!r} produced no filename"
405 | return name
406 |
407 | @property
408 | def file_path(self) -> str:
409 | return url_to_path(self.url)
410 |
411 | @property
412 | def scheme(self) -> str:
413 | return self._parsed_url.scheme
414 |
415 | @property
416 | def netloc(self) -> str:
417 | """
418 | This can contain auth information.
419 | """
420 | return self._parsed_url.netloc
421 |
422 | @property
423 | def path(self) -> str:
424 | return urllib.parse.unquote(self._parsed_url.path)
425 |
426 | def splitext(self) -> Tuple[str, str]:
427 | return splitext(posixpath.basename(self.path.rstrip("/")))
428 |
429 | @property
430 | def ext(self) -> str:
431 | return self.splitext()[1]
432 |
433 | @property
434 | def url_without_fragment(self) -> str:
435 | scheme, netloc, path, query, fragment = self._parsed_url
436 | return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
437 |
438 | _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
439 |
440 | # Per PEP 508.
441 | _project_name_re = re.compile(
442 | r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
443 | )
444 |
445 | def _egg_fragment(self) -> Optional[str]:
446 | match = self._egg_fragment_re.search(self._url)
447 | if not match:
448 | return None
449 |
450 | # An egg fragment looks like a PEP 508 project name, along with
451 | # an optional extras specifier. Anything else is invalid.
452 | project_name = match.group(1)
453 | if not self._project_name_re.match(project_name):
454 | deprecated(
455 | reason=f"{self} contains an egg fragment with a non-PEP 508 name",
456 | replacement="to use the req @ url syntax, and remove the egg fragment",
457 | gone_in="25.0",
458 | issue=11617,
459 | )
460 |
461 | return project_name
462 |
463 | _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
464 |
465 | @property
466 | def subdirectory_fragment(self) -> Optional[str]:
467 | match = self._subdirectory_fragment_re.search(self._url)
468 | if not match:
469 | return None
470 | return match.group(1)
471 |
472 | def metadata_link(self) -> Optional["Link"]:
473 | """Return a link to the associated core metadata file (if any)."""
474 | if self.metadata_file_data is None:
475 | return None
476 | metadata_url = f"{self.url_without_fragment}.metadata"
477 | if self.metadata_file_data.hashes is None:
478 | return Link(metadata_url)
479 | return Link(metadata_url, hashes=self.metadata_file_data.hashes)
480 |
481 | def as_hashes(self) -> Hashes:
482 | return Hashes({k: [v] for k, v in self._hashes.items()})
483 |
484 | @property
485 | def hash(self) -> Optional[str]:
486 | return next(iter(self._hashes.values()), None)
487 |
488 | @property
489 | def hash_name(self) -> Optional[str]:
490 | return next(iter(self._hashes), None)
491 |
492 | @property
493 | def show_url(self) -> str:
494 | return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0])
495 |
496 | @property
497 | def is_file(self) -> bool:
498 | return self.scheme == "file"
499 |
500 | def is_existing_dir(self) -> bool:
501 | return self.is_file and os.path.isdir(self.file_path)
502 |
503 | @property
504 | def is_wheel(self) -> bool:
505 | return self.ext == WHEEL_EXTENSION
506 |
507 | @property
508 | def is_vcs(self) -> bool:
509 | from pip._internal.vcs import vcs
510 |
511 | return self.scheme in vcs.all_schemes
512 |
513 | @property
514 | def is_yanked(self) -> bool:
515 | return self.yanked_reason is not None
516 |
517 | @property
518 | def has_hash(self) -> bool:
519 | return bool(self._hashes)
520 |
521 | def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
522 | """
523 | Return True if the link has a hash and it is allowed by `hashes`.
524 | """
525 | if hashes is None:
526 | return False
527 | return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
528 |
529 |
530 | class _CleanResult(NamedTuple):
531 | """Convert link for equivalency check.
532 |
533 | This is used in the resolver to check whether two URL-specified requirements
534 | likely point to the same distribution and can be considered equivalent. This
535 | equivalency logic avoids comparing URLs literally, which can be too strict
536 | (e.g. "a=1&b=2" vs "b=2&a=1") and produce conflicts unexpecting to users.
537 |
538 | Currently this does three things:
539 |
540 | 1. Drop the basic auth part. This is technically wrong since a server can
541 | serve different content based on auth, but if it does that, it is even
542 | impossible to guarantee two URLs without auth are equivalent, since
543 | the user can input different auth information when prompted. So the
544 | practical solution is to assume the auth doesn't affect the response.
545 | 2. Parse the query to avoid the ordering issue. Note that ordering under the
546 | same key in the query are NOT cleaned; i.e. "a=1&a=2" and "a=2&a=1" are
547 | still considered different.
548 | 3. Explicitly drop most of the fragment part, except ``subdirectory=`` and
549 | hash values, since it should have no impact the downloaded content. Note
550 | that this drops the "egg=" part historically used to denote the requested
551 | project (and extras), which is wrong in the strictest sense, but too many
552 | people are supplying it inconsistently to cause superfluous resolution
553 | conflicts, so we choose to also ignore them.
554 | """
555 |
556 | parsed: urllib.parse.SplitResult
557 | query: Dict[str, List[str]]
558 | subdirectory: str
559 | hashes: Dict[str, str]
560 |
561 |
562 | def _clean_link(link: Link) -> _CleanResult:
563 | parsed = link._parsed_url
564 | netloc = parsed.netloc.rsplit("@", 1)[-1]
565 | # According to RFC 8089, an empty host in file: means localhost.
566 | if parsed.scheme == "file" and not netloc:
567 | netloc = "localhost"
568 | fragment = urllib.parse.parse_qs(parsed.fragment)
569 | if "egg" in fragment:
570 | logger.debug("Ignoring egg= fragment in %s", link)
571 | try:
572 | # If there are multiple subdirectory values, use the first one.
573 | # This matches the behavior of Link.subdirectory_fragment.
574 | subdirectory = fragment["subdirectory"][0]
575 | except (IndexError, KeyError):
576 | subdirectory = ""
577 | # If there are multiple hash values under the same algorithm, use the
578 | # first one. This matches the behavior of Link.hash_value.
579 | hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
580 | return _CleanResult(
581 | parsed=parsed._replace(netloc=netloc, query="", fragment=""),
582 | query=urllib.parse.parse_qs(parsed.query),
583 | subdirectory=subdirectory,
584 | hashes=hashes,
585 | )
586 |
587 |
588 | @functools.lru_cache(maxsize=None)
589 | def links_equivalent(link1: Link, link2: Link) -> bool:
590 | return _clean_link(link1) == _clean_link(link2)
591 |
```