+ In other language (unofficial port - by the community)
+
+
+
+
+
+> A library that helps you read text from an unknown charset encoding. Motivated by `chardet`,
+> I'm trying to resolve the issue by taking a new approach.
+> All IANA character set names for which the Python core library provides codecs are supported.
+
+
+
+
+---
+
+**Documentation**: https://hf.co/docs/huggingface_hub
+
+**Source Code**: https://github.com/huggingface/huggingface_hub
+
+---
+
+## Welcome to the huggingface_hub library
+
+The `huggingface_hub` library allows you to interact with the [Hugging Face Hub](https://huggingface.co/), a platform democratizing open-source Machine Learning for creators and collaborators. Discover pre-trained models and datasets for your projects or play with the thousands of machine learning apps hosted on the Hub. You can also create and share your own models, datasets and demos with the community. The `huggingface_hub` library provides a simple way to do all these things with Python.
+
+## Key features
+
+- [Download files](https://huggingface.co/docs/huggingface_hub/en/guides/download) from the Hub.
+- [Upload files](https://huggingface.co/docs/huggingface_hub/en/guides/upload) to the Hub.
+- [Manage your repositories](https://huggingface.co/docs/huggingface_hub/en/guides/repository).
+- [Run Inference](https://huggingface.co/docs/huggingface_hub/en/guides/inference) on deployed models.
+- [Search](https://huggingface.co/docs/huggingface_hub/en/guides/search) for models, datasets and Spaces.
+- [Share Model Cards](https://huggingface.co/docs/huggingface_hub/en/guides/model-cards) to document your models.
+- [Engage with the community](https://huggingface.co/docs/huggingface_hub/en/guides/community) through PRs and comments.
+
+## Installation
+
+Install the `huggingface_hub` package with [pip](https://pypi.org/project/huggingface-hub/):
+
+```bash
+pip install huggingface_hub
+```
+
+If you prefer, you can also install it with [conda](https://huggingface.co/docs/huggingface_hub/en/installation#install-with-conda).
+
+In order to keep the package minimal by default, `huggingface_hub` comes with optional dependencies useful for some use cases. For example, if you want have a complete experience for Inference, run:
+
+```bash
+pip install huggingface_hub[inference]
+```
+
+To learn more installation and optional dependencies, check out the [installation guide](https://huggingface.co/docs/huggingface_hub/en/installation).
+
+## Quick start
+
+### Download files
+
+Download a single file
+
+```py
+from huggingface_hub import hf_hub_download
+
+hf_hub_download(repo_id="tiiuae/falcon-7b-instruct", filename="config.json")
+```
+
+Or an entire repository
+
+```py
+from huggingface_hub import snapshot_download
+
+snapshot_download("stabilityai/stable-diffusion-2-1")
+```
+
+Files will be downloaded in a local cache folder. More details in [this guide](https://huggingface.co/docs/huggingface_hub/en/guides/manage-cache).
+
+### Login
+
+The Hugging Face Hub uses tokens to authenticate applications (see [docs](https://huggingface.co/docs/hub/security-tokens)). To log in your machine, run the following CLI:
+
+```bash
+huggingface-cli login
+# or using an environment variable
+huggingface-cli login --token $HUGGINGFACE_TOKEN
+```
+
+### Create a repository
+
+```py
+from huggingface_hub import create_repo
+
+create_repo(repo_id="super-cool-model")
+```
+
+### Upload files
+
+Upload a single file
+
+```py
+from huggingface_hub import upload_file
+
+upload_file(
+ path_or_fileobj="/home/lysandre/dummy-test/README.md",
+ path_in_repo="README.md",
+ repo_id="lysandre/test-model",
+)
+```
+
+Or an entire folder
+
+```py
+from huggingface_hub import upload_folder
+
+upload_folder(
+ folder_path="/path/to/local/space",
+ repo_id="username/my-cool-space",
+ repo_type="space",
+)
+```
+
+For details in the [upload guide](https://huggingface.co/docs/huggingface_hub/en/guides/upload).
+
+## Integrating to the Hub.
+
+We're partnering with cool open source ML libraries to provide free model hosting and versioning. You can find the existing integrations [here](https://huggingface.co/docs/hub/libraries).
+
+The advantages are:
+
+- Free model or dataset hosting for libraries and their users.
+- Built-in file versioning, even with very large files, thanks to a git-based approach.
+- Serverless inference API for all models publicly available.
+- In-browser widgets to play with the uploaded models.
+- Anyone can upload a new model for your library, they just need to add the corresponding tag for the model to be discoverable.
+- Fast downloads! We use Cloudfront (a CDN) to geo-replicate downloads so they're blazing fast from anywhere on the globe.
+- Usage stats and more features to come.
+
+If you would like to integrate your library, feel free to open an issue to begin the discussion. We wrote a [step-by-step guide](https://huggingface.co/docs/hub/adding-a-library) with ❤️ showing how to do this integration.
+
+## Contributions (feature requests, bugs, etc.) are super welcome 💙💚💛💜🧡❤️
+
+Everyone is welcome to contribute, and we value everybody's contribution. Code is not the only way to help the community.
+Answering questions, helping others, reaching out and improving the documentations are immensely valuable to the community.
+We wrote a [contribution guide](https://github.com/huggingface/huggingface_hub/blob/main/CONTRIBUTING.md) to summarize
+how to get started to contribute to this repository.
+
+
diff --git a/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/RECORD b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..2b0b66a38fbddb5e50db97954a4310d2649a061f
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/RECORD
@@ -0,0 +1,256 @@
+../../Scripts/huggingface-cli.exe,sha256=jPydxwYOgK7NV5SfNkpDQA-xD-aFEHknFsydS0cljyc,108424
+huggingface_hub-0.29.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+huggingface_hub-0.29.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
+huggingface_hub-0.29.1.dist-info/METADATA,sha256=B8dl2q55ILPp7jxGZ3Nx0zT0AlCd74Z0ipYxygbW3FI,13480
+huggingface_hub-0.29.1.dist-info/RECORD,,
+huggingface_hub-0.29.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+huggingface_hub-0.29.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
+huggingface_hub-0.29.1.dist-info/entry_points.txt,sha256=Y3Z2L02rBG7va_iE6RPXolIgwOdwUFONyRN3kXMxZ0g,131
+huggingface_hub-0.29.1.dist-info/top_level.txt,sha256=8KzlQJAY4miUvjAssOAJodqKOw3harNzuiwGQ9qLSSk,16
+huggingface_hub/__init__.py,sha256=T-o7tRMXCYjO5nPSgmN_PAVEpFlQTOp7gh-gh8ucXak,48761
+huggingface_hub/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/__pycache__/_commit_api.cpython-312.pyc,,
+huggingface_hub/__pycache__/_commit_scheduler.cpython-312.pyc,,
+huggingface_hub/__pycache__/_inference_endpoints.cpython-312.pyc,,
+huggingface_hub/__pycache__/_local_folder.cpython-312.pyc,,
+huggingface_hub/__pycache__/_login.cpython-312.pyc,,
+huggingface_hub/__pycache__/_snapshot_download.cpython-312.pyc,,
+huggingface_hub/__pycache__/_space_api.cpython-312.pyc,,
+huggingface_hub/__pycache__/_tensorboard_logger.cpython-312.pyc,,
+huggingface_hub/__pycache__/_upload_large_folder.cpython-312.pyc,,
+huggingface_hub/__pycache__/_webhooks_payload.cpython-312.pyc,,
+huggingface_hub/__pycache__/_webhooks_server.cpython-312.pyc,,
+huggingface_hub/__pycache__/community.cpython-312.pyc,,
+huggingface_hub/__pycache__/constants.cpython-312.pyc,,
+huggingface_hub/__pycache__/errors.cpython-312.pyc,,
+huggingface_hub/__pycache__/fastai_utils.cpython-312.pyc,,
+huggingface_hub/__pycache__/file_download.cpython-312.pyc,,
+huggingface_hub/__pycache__/hf_api.cpython-312.pyc,,
+huggingface_hub/__pycache__/hf_file_system.cpython-312.pyc,,
+huggingface_hub/__pycache__/hub_mixin.cpython-312.pyc,,
+huggingface_hub/__pycache__/inference_api.cpython-312.pyc,,
+huggingface_hub/__pycache__/keras_mixin.cpython-312.pyc,,
+huggingface_hub/__pycache__/lfs.cpython-312.pyc,,
+huggingface_hub/__pycache__/repocard.cpython-312.pyc,,
+huggingface_hub/__pycache__/repocard_data.cpython-312.pyc,,
+huggingface_hub/__pycache__/repository.cpython-312.pyc,,
+huggingface_hub/_commit_api.py,sha256=TqXmu5moVAhBa7iuyJdsqsfRTxTpGMnvsPkb4GgC3dc,32636
+huggingface_hub/_commit_scheduler.py,sha256=tfIoO1xWHjTJ6qy6VS6HIoymDycFPg0d6pBSZprrU2U,14679
+huggingface_hub/_inference_endpoints.py,sha256=SLoZOQtv_hNl0Xuafo34L--zuCZ3zSJja2tSkYkG5V4,17268
+huggingface_hub/_local_folder.py,sha256=ScpCJUITFC0LMkiebyaGiBhAU6fvQK8w7pVV6L8rhmc,16575
+huggingface_hub/_login.py,sha256=ssf4viT5BhHI2ZidnSuAZcrwSxzaLOrf8xgRVKuvu_A,20298
+huggingface_hub/_snapshot_download.py,sha256=zZDaPBb4CfMCU7DgxjbaFmdoISCY425RaH7wXwFijEM,14992
+huggingface_hub/_space_api.py,sha256=QVOUNty2T4RxPoxf9FzUjXmjHiGXP0mqXJzqQ7GmoJo,5363
+huggingface_hub/_tensorboard_logger.py,sha256=ZkYcAUiRC8RGL214QUYtp58O8G5tn-HF6DCWha9imcA,8358
+huggingface_hub/_upload_large_folder.py,sha256=eedUTowflZx1thFVLDv7hLd_LQqixa5NVsUco7R6F5c,23531
+huggingface_hub/_webhooks_payload.py,sha256=Xm3KaK7tCOGBlXkuZvbym6zjHXrT1XCrbUFWuXiBmNY,3617
+huggingface_hub/_webhooks_server.py,sha256=oCvpFrYjrhJjClAMw26SQfvN4DUItgK2IhFp1OVh2bU,15623
+huggingface_hub/commands/__init__.py,sha256=AkbM2a-iGh0Vq_xAWhK3mu3uZ44km8-X5uWjKcvcrUQ,928
+huggingface_hub/commands/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/_cli_utils.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/delete_cache.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/download.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/env.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/huggingface_cli.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/lfs.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/repo_files.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/scan_cache.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/tag.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/upload.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/upload_large_folder.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/user.cpython-312.pyc,,
+huggingface_hub/commands/__pycache__/version.cpython-312.pyc,,
+huggingface_hub/commands/_cli_utils.py,sha256=Nt6CjbkYqQQRuh70bUXVA6rZpbZt_Sa1WqBUxjQLu6g,2095
+huggingface_hub/commands/delete_cache.py,sha256=Rb1BtIltJPnQ-th7tcK_L4mFqfk785t3KXV77xXKBP4,16131
+huggingface_hub/commands/download.py,sha256=1YXKttB8YBX7SJ0Jxg0t1n8yp2BUZXtY0ck6DhCg-XE,8183
+huggingface_hub/commands/env.py,sha256=yYl4DSS14V8t244nAi0t77Izx5LIdgS_dy6xiV5VQME,1226
+huggingface_hub/commands/huggingface_cli.py,sha256=ZwW_nwgppyj-GA6iM3mgmbXMZ63bgtpGl_yIQDyWS4A,2414
+huggingface_hub/commands/lfs.py,sha256=xdbnNRO04UuQemEhUGT809jFgQn9Rj-SnyT_0Ph-VYg,7342
+huggingface_hub/commands/repo_files.py,sha256=Nfv8TjuaZVOrj7TZjrojtjdD8Wf54aZvYPDEOevh7tA,4923
+huggingface_hub/commands/scan_cache.py,sha256=xdD_zRKd49hRuATyptG-zaY08h1f9CAjB5zZBKe0YEo,8563
+huggingface_hub/commands/tag.py,sha256=0LNQZyK-WKi0VIL9i1xWzKxJ1ILw1jxMF_E6t2weJss,6288
+huggingface_hub/commands/upload.py,sha256=xMExm68YcR8R_dDRi3bcIC1qVCvRFRW7aP_AGxGZ1rc,13656
+huggingface_hub/commands/upload_large_folder.py,sha256=P-EO44JWVl39Ax4b0E0Z873d0a6S38Qas8P6DaL1EwI,6129
+huggingface_hub/commands/user.py,sha256=M6Ef045YcyV4mFCbLaTRPciQDC6xtV9MMheeen69D0E,11168
+huggingface_hub/commands/version.py,sha256=vfCJn7GO1m-DtDmbdsty8_RTVtnZ7lX6MJsx0Bf4e-s,1266
+huggingface_hub/community.py,sha256=4MtcoxEI9_0lmmilBEnvUEi8_O1Ivfa8p6eKxYU5-ts,12198
+huggingface_hub/constants.py,sha256=JOswJMnb45udoZibIcH5v71gILOKvVHBjpCqGZK5xDw,8560
+huggingface_hub/errors.py,sha256=zble0j94ai8zwyM0a2DovwcF372zQohwDsgajTsaxqI,9703
+huggingface_hub/fastai_utils.py,sha256=DpeH9d-6ut2k_nCAAwglM51XmRmgfbRe2SPifpVL5Yk,16745
+huggingface_hub/file_download.py,sha256=CU8ZANwJ4nf436jDCP9Ru8qEvdbZD4QznvAo6vbTO_4,70613
+huggingface_hub/hf_api.py,sha256=g81_Vs2n08Hm4kksO6QoNLDWkYaSnIjPdiu-qZOgMks,423772
+huggingface_hub/hf_file_system.py,sha256=m_g7uYLGxTdsBnhvR5835jvYMAuEBsUSFvEbzZKzzoo,47500
+huggingface_hub/hub_mixin.py,sha256=-oTnuB3b-0WeutZ1iBkAy1YuWrBKvHBVBpmd3-7oGB4,37419
+huggingface_hub/inference/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+huggingface_hub/inference/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/inference/__pycache__/_client.cpython-312.pyc,,
+huggingface_hub/inference/__pycache__/_common.cpython-312.pyc,,
+huggingface_hub/inference/_client.py,sha256=aiiVqLiYisaEZTOxkv90vGhsdIM-fvXdhuDwhoNbjSQ,162205
+huggingface_hub/inference/_common.py,sha256=iwCkq2fWE1MVoPTeeXN7UN5FZi7g5fZ3K8PHSOCi5dU,14591
+huggingface_hub/inference/_generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+huggingface_hub/inference/_generated/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-312.pyc,,
+huggingface_hub/inference/_generated/_async_client.py,sha256=barbsIBB5d76l3zO3Tj_2WV6Phmwfjtuq7277qHfOYg,168438
+huggingface_hub/inference/_generated/types/__init__.py,sha256=CJwdkaPbR-vzCWU1ITr4aHOHax87JaewaIs_7rKaRXE,6274
+huggingface_hub/inference/_generated/types/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/audio_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/audio_to_audio.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/automatic_speech_recognition.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/base.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/chat_completion.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/depth_estimation.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/document_question_answering.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/feature_extraction.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/fill_mask.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/image_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/image_segmentation.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/image_to_image.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/image_to_text.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/object_detection.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/question_answering.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/sentence_similarity.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/summarization.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/table_question_answering.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text2text_generation.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text_generation.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text_to_audio.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text_to_image.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text_to_speech.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/text_to_video.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/token_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/translation.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/video_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/visual_question_answering.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/zero_shot_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/zero_shot_image_classification.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/__pycache__/zero_shot_object_detection.cpython-312.pyc,,
+huggingface_hub/inference/_generated/types/audio_classification.py,sha256=Jg3mzfGhCSH6CfvVvgJSiFpkz6v4nNA0G4LJXacEgNc,1573
+huggingface_hub/inference/_generated/types/audio_to_audio.py,sha256=2Ep4WkePL7oJwcp5nRJqApwviumGHbft9HhXE9XLHj4,891
+huggingface_hub/inference/_generated/types/automatic_speech_recognition.py,sha256=lWD_BMDMS3hreIq0kcLwOa8e0pXRH-oWUK96VaVc5DM,5624
+huggingface_hub/inference/_generated/types/base.py,sha256=4XG49q0-2SOftYQ8HXQnWLxiJktou-a7IoG3kdOv-kg,6751
+huggingface_hub/inference/_generated/types/chat_completion.py,sha256=rJUsET-Lqgt3AlW2zPIxOHc7XmhAZmaolbV8TGu4MmE,9885
+huggingface_hub/inference/_generated/types/depth_estimation.py,sha256=rcpe9MhYMeLjflOwBs3KMZPr6WjOH3FYEThStG-FJ3M,929
+huggingface_hub/inference/_generated/types/document_question_answering.py,sha256=6BEYGwJcqGlah4RBJDAvWFTEXkO0mosBiMy82432nAM,3202
+huggingface_hub/inference/_generated/types/feature_extraction.py,sha256=NMWVL_TLSG5SS5bdt1-fflkZ75UMlMKeTMtmdnUTADc,1537
+huggingface_hub/inference/_generated/types/fill_mask.py,sha256=OrTgQ7Ndn0_dWK5thQhZwTOHbQni8j0iJcx9llyhRds,1708
+huggingface_hub/inference/_generated/types/image_classification.py,sha256=A-Y024o8723_n8mGVos4TwdAkVL62McGeL1iIo4VzNs,1585
+huggingface_hub/inference/_generated/types/image_segmentation.py,sha256=vrkI4SuP1Iq_iLXc-2pQhYY3SHN4gzvFBoZqbUHxU7o,1950
+huggingface_hub/inference/_generated/types/image_to_image.py,sha256=uhJO63Ny3qhsN7KY9Y2rj1rzFuYaPczz5dlgDNOx-5k,1954
+huggingface_hub/inference/_generated/types/image_to_text.py,sha256=3hN7lpJoVuwUJme5gDdxZmXftb6cQ_7SXVC1VM8rXh8,4919
+huggingface_hub/inference/_generated/types/object_detection.py,sha256=VuFlb1281qTXoSgJDmquGz-VNfEZLo2H0Rh_F6MF6ts,2000
+huggingface_hub/inference/_generated/types/question_answering.py,sha256=zw38a9_9l2k1ifYZefjkioqZ4asfSRM9M4nU3gSCmAQ,2898
+huggingface_hub/inference/_generated/types/sentence_similarity.py,sha256=w5Nj1g18eBzopZwxuDLI-fEsyaCK2KrHA5yf_XfSjgo,1052
+huggingface_hub/inference/_generated/types/summarization.py,sha256=WGGr8uDLrZg8JQgF9ZMUP9euw6uZo6zwkVZ-IfvCFI0,1487
+huggingface_hub/inference/_generated/types/table_question_answering.py,sha256=cJnIPA2fIbQP2Ejn7X_esY48qGWoXg30fnNOqCXiOVQ,2293
+huggingface_hub/inference/_generated/types/text2text_generation.py,sha256=v-418w1JNNSZ2tuW9DUl6a36TQQCADa438A3ufvcbOw,1609
+huggingface_hub/inference/_generated/types/text_classification.py,sha256=FarAjygLEfPofLfKeabzJ7PKEBItlHGoUNUOzyLRpL4,1445
+huggingface_hub/inference/_generated/types/text_generation.py,sha256=Rk6kAbyWn7tI-tDamkoCAg61sQj3glNPxWdovs6WrQM,5907
+huggingface_hub/inference/_generated/types/text_to_audio.py,sha256=aE6NLpQ9V3ENIXOCFFcMaMjdLxZzZpE7iU1V-XYPU0w,4850
+huggingface_hub/inference/_generated/types/text_to_image.py,sha256=sGGi1Fa0n5Pmd6G3I-F2SBJcJ1M7Gmqnng6sfi0AVzs,1903
+huggingface_hub/inference/_generated/types/text_to_speech.py,sha256=5Md6d1eRBfeVQ4A32s7YoxM2HFfSLMz5B5QovGKfWbs,4869
+huggingface_hub/inference/_generated/types/text_to_video.py,sha256=yHXVNs3t6aYO7visrBlB5cH7kjoysxF9510aofcf_18,1790
+huggingface_hub/inference/_generated/types/token_classification.py,sha256=iblAcgfxXeaLYJ14NdiiCMIQuBlarUknLkXUklhvcLI,1915
+huggingface_hub/inference/_generated/types/translation.py,sha256=xww4X5cfCYv_F0oINWLwqJRPCT6SV3VBAJuPjTs_j7o,1763
+huggingface_hub/inference/_generated/types/video_classification.py,sha256=TyydjQw2NRLK9sDGzJUVnkDeo848ebmCx588Ur8I9q0,1680
+huggingface_hub/inference/_generated/types/visual_question_answering.py,sha256=AWrQ6qo4gZa3PGedaNpzDFqx5yOYyjhnUB6iuZEj_uo,1673
+huggingface_hub/inference/_generated/types/zero_shot_classification.py,sha256=BAiebPjsqoNa8EU35Dx0pfIv8W2c4GSl-TJckV1MaxQ,1738
+huggingface_hub/inference/_generated/types/zero_shot_image_classification.py,sha256=8J9n6VqFARkWvPfAZNWEG70AlrMGldU95EGQQwn06zI,1487
+huggingface_hub/inference/_generated/types/zero_shot_object_detection.py,sha256=GUd81LIV7oEbRWayDlAVgyLmY596r1M3AW0jXDp1yTA,1630
+huggingface_hub/inference/_providers/__init__.py,sha256=Q1hPPQgN3gKTa3NWQSANUBOB3oeCLr4miVQAVaZK8DU,5352
+huggingface_hub/inference/_providers/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/_common.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/black_forest_labs.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/fal_ai.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/fireworks_ai.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/hf_inference.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/hyperbolic.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/nebius.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/novita.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/replicate.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/sambanova.cpython-312.pyc,,
+huggingface_hub/inference/_providers/__pycache__/together.cpython-312.pyc,,
+huggingface_hub/inference/_providers/_common.py,sha256=8mgu95x46aRhvuHOVijczBpRJK4LFHusC_FU3t4iXGw,9200
+huggingface_hub/inference/_providers/black_forest_labs.py,sha256=YacbRSMwTcWMCtNfLZGRnjAwyOLAM9sIj06ZUKDb7n0,2647
+huggingface_hub/inference/_providers/fal_ai.py,sha256=pjWeMfxatAXSVJsEQf142MQVvAz5x-jtZLYXapXJFlI,3455
+huggingface_hub/inference/_providers/fireworks_ai.py,sha256=NazpDeD4agtFW6ISaXEvq5XAPVNeoG9XWk3O4NCxBNI,228
+huggingface_hub/inference/_providers/hf_inference.py,sha256=5CUR4LzPHiHfd5JN3ooP3DbOAyRgEzbQb0ZoaaiiNPY,5183
+huggingface_hub/inference/_providers/hyperbolic.py,sha256=qccC_gcMstGnvjmRyslgnuFVa9VAKS9w6F1ohwysvMU,1739
+huggingface_hub/inference/_providers/nebius.py,sha256=P34BO2y8MdBWqYzzt4VlkPePkXAIbMlRxvV87UhZVdU,1508
+huggingface_hub/inference/_providers/novita.py,sha256=SLOgZuAP1-Zs9NB2JmLf6kgX8R4O1Yy_64Ok9CmEZNs,745
+huggingface_hub/inference/_providers/replicate.py,sha256=5XVbbokgIz431rkIMchxcZgSAMU4vFiJ3xPgF8xyhz8,2263
+huggingface_hub/inference/_providers/sambanova.py,sha256=pR2MajO3ffga9FxzruzrTfTm3eBQ3AC0TPeSIdiQeco,249
+huggingface_hub/inference/_providers/together.py,sha256=HPVx9_pVc-b8PUl_aB1SPCngbfA7QK-tRV7_AzgTD_g,2028
+huggingface_hub/inference_api.py,sha256=b4-NhPSn9b44nYKV8tDKXodmE4JVdEymMWL4CVGkzlE,8323
+huggingface_hub/keras_mixin.py,sha256=3d2oW35SALXHq-WHoLD_tbq0UrcabGKj3HidtPRx51U,19574
+huggingface_hub/lfs.py,sha256=n-TIjK7J7aXG3zi__0nkd6aNkE4djOf9CD6dYQOQ5P8,16649
+huggingface_hub/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+huggingface_hub/repocard.py,sha256=ihFBKYqPNaWw9rWMUvcaRKxrooL32NA4fAlrwzXk9LY,34733
+huggingface_hub/repocard_data.py,sha256=EqJ-54QF0qngitsZwCkPQjPwzrkLpxt_qU4lxekMWs8,33247
+huggingface_hub/repository.py,sha256=xVQR-MRKNDfJ_Z_99DwtXZB3xNO06eYG_GvRM4fLiTU,54557
+huggingface_hub/serialization/__init__.py,sha256=kn-Fa-m4FzMnN8lNsF-SwFcfzug4CucexybGKyvZ8S0,1041
+huggingface_hub/serialization/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/serialization/__pycache__/_base.cpython-312.pyc,,
+huggingface_hub/serialization/__pycache__/_dduf.cpython-312.pyc,,
+huggingface_hub/serialization/__pycache__/_tensorflow.cpython-312.pyc,,
+huggingface_hub/serialization/__pycache__/_torch.cpython-312.pyc,,
+huggingface_hub/serialization/_base.py,sha256=Df3GwGR9NzeK_SD75prXLucJAzPiNPgHbgXSw-_LTk8,8126
+huggingface_hub/serialization/_dduf.py,sha256=s42239rLiHwaJE36QDEmS5GH7DSmQ__BffiHJO5RjIg,15424
+huggingface_hub/serialization/_tensorflow.py,sha256=zHOvEMg-JHC55Fm4roDT3LUCDO5zB9qtXZffG065RAM,3625
+huggingface_hub/serialization/_torch.py,sha256=WoNV_17x99Agx68mNMbi2g8T5CAVIkSb3_OaZx9KrX4,44714
+huggingface_hub/templates/datasetcard_template.md,sha256=W-EMqR6wndbrnZorkVv56URWPG49l7MATGeI015kTvs,5503
+huggingface_hub/templates/modelcard_template.md,sha256=4AqArS3cqdtbit5Bo-DhjcnDFR-pza5hErLLTPM4Yuc,6870
+huggingface_hub/utils/__init__.py,sha256=aMEsiXGi93z-dXz1W7FFma71tAMeKw0SoKVZSQUeE_4,3525
+huggingface_hub/utils/__pycache__/__init__.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_auth.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_cache_assets.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_cache_manager.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_chunk_utils.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_datetime.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_deprecation.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_experimental.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_fixes.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_git_credential.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_headers.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_hf_folder.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_http.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_lfs.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_pagination.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_paths.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_runtime.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_safetensors.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_subprocess.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_telemetry.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_typing.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/_validators.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/endpoint_helpers.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/insecure_hashlib.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/logging.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/sha.cpython-312.pyc,,
+huggingface_hub/utils/__pycache__/tqdm.cpython-312.pyc,,
+huggingface_hub/utils/_auth.py,sha256=-9p3SSOtWKMMCDKlsM_-ebsIGX0sSgKTSnC-_O4kTxg,8294
+huggingface_hub/utils/_cache_assets.py,sha256=kai77HPQMfYpROouMBQCr_gdBCaeTm996Sqj0dExbNg,5728
+huggingface_hub/utils/_cache_manager.py,sha256=GhiuVQsEkWU55uYkkgiGJV1_naeciyk8u4qb4WTIVyw,34531
+huggingface_hub/utils/_chunk_utils.py,sha256=kRCaj5228_vKcyLWspd8Xq01f17Jz6ds5Sr9ed5d_RU,2130
+huggingface_hub/utils/_datetime.py,sha256=kCS5jaKV25kOncX1xujbXsz5iDLcjLcLw85semGNzxQ,2770
+huggingface_hub/utils/_deprecation.py,sha256=HZhRGGUX_QMKBBBwHHlffLtmCSK01TOpeXHefZbPfwI,4872
+huggingface_hub/utils/_experimental.py,sha256=crCPH6k6-11wwH2GZuZzZzZbjUotay49ywV1SSJhMHM,2395
+huggingface_hub/utils/_fixes.py,sha256=xQV1QkUn2WpLqLjtXNiyn9gh-454K6AF-Q3kwkYAQD8,4437
+huggingface_hub/utils/_git_credential.py,sha256=SDdsiREr1TcAR2Ze2TB0E5cYzVJgvDZrs60od9lAsMc,4596
+huggingface_hub/utils/_headers.py,sha256=3tKQN5ciAt1683nZXEpPyQOS7oWnfYI0t_N_aJU-bms,8876
+huggingface_hub/utils/_hf_folder.py,sha256=WNjTnu0Q7tqcSS9EsP4ssCJrrJMcCvAt8P_-LEtmOU8,2487
+huggingface_hub/utils/_http.py,sha256=Nf4_Rpo9iqgOdrwwxjkZPAecfEGxdcGZ4w8Zb_qeesw,25301
+huggingface_hub/utils/_lfs.py,sha256=EC0Oz6Wiwl8foRNkUOzrETXzAWlbgpnpxo5a410ovFY,3957
+huggingface_hub/utils/_pagination.py,sha256=hzLFLd8i_DKkPRVYzOx2CxLt5lcocEiAxDJriQUjAjY,1841
+huggingface_hub/utils/_paths.py,sha256=w1ZhFmmD5ykWjp_hAvhjtOoa2ZUcOXJrF4a6O3QpAWo,5042
+huggingface_hub/utils/_runtime.py,sha256=tUyWylDgqaOXnMg39rvyusiruVN5ulcqiSwUEkQ9jjg,11195
+huggingface_hub/utils/_safetensors.py,sha256=GW3nyv7xQcuwObKYeYoT9VhURVzG1DZTbKBKho8Bbos,4458
+huggingface_hub/utils/_subprocess.py,sha256=6GpGD4qE9-Z1-Ocs3JuCLjR4NcRlknA-hAuQlqiprYY,4595
+huggingface_hub/utils/_telemetry.py,sha256=54LXeIJU5pEGghPAh06gqNAR-UoxOjVLvKqAQscwqZs,4890
+huggingface_hub/utils/_typing.py,sha256=Dgp6TQUlpzStfVLoSvXHCBP4b3NzHZ8E0Gg9mYAoDS4,2903
+huggingface_hub/utils/_validators.py,sha256=dDsVG31iooTYrIyi5Vwr1DukL0fEmJwu3ceVNduhsuE,9204
+huggingface_hub/utils/endpoint_helpers.py,sha256=9VtIAlxQ5H_4y30sjCAgbu7XCqAtNLC7aRYxaNn0hLI,2366
+huggingface_hub/utils/insecure_hashlib.py,sha256=OjxlvtSQHpbLp9PWSrXBDJ0wHjxCBU-SQJgucEEXDbU,1058
+huggingface_hub/utils/logging.py,sha256=0A8fF1yh3L9Ka_bCDX2ml4U5Ht0tY8Dr3JcbRvWFuwo,4909
+huggingface_hub/utils/sha.py,sha256=OFnNGCba0sNcT2gUwaVCJnldxlltrHHe0DS_PCpV3C4,2134
+huggingface_hub/utils/tqdm.py,sha256=ZgdphuTnwAIaUKnnD2P7qVvNHpzHAyrYoItkiV0aEjQ,9835
diff --git a/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/REQUESTED b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/WHEEL b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..79d5c89a71989389294854aa34e329701325f8b0
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.45.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/entry_points.txt b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eb3dafd90f19de60b3e520aeaf8132402980214d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/entry_points.txt
@@ -0,0 +1,6 @@
+[console_scripts]
+huggingface-cli = huggingface_hub.commands.huggingface_cli:main
+
+[fsspec.specs]
+hf=huggingface_hub.HfFileSystem
+
diff --git a/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/top_level.txt b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6b964ccca3c1b6766042b3fe3b2707ba25372924
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub-0.29.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+huggingface_hub
diff --git a/env/Lib/site-packages/huggingface_hub/__init__.py b/env/Lib/site-packages/huggingface_hub/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b322d99eaaca686f61a1aef292f78fa52163c491
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/__init__.py
@@ -0,0 +1,1431 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ***********
+# `huggingface_hub` init has 2 modes:
+# - Normal usage:
+# If imported to use it, all modules and functions are lazy-loaded. This means
+# they exist at top level in module but are imported only the first time they are
+# used. This way, `from huggingface_hub import something` will import `something`
+# quickly without the hassle of importing all the features from `huggingface_hub`.
+# - Static check:
+# If statically analyzed, all modules and functions are loaded normally. This way
+# static typing check works properly as well as autocomplete in text editors and
+# IDEs.
+#
+# The static model imports are done inside the `if TYPE_CHECKING:` statement at
+# the bottom of this file. Since module/functions imports are duplicated, it is
+# mandatory to make sure to add them twice when adding one. This is checked in the
+# `make quality` command.
+#
+# To update the static imports, please run the following command and commit the changes.
+# ```
+# # Use script
+# python utils/check_static_imports.py --update-file
+#
+# # Or run style on codebase
+# make style
+# ```
+#
+# ***********
+# Lazy loader vendored from https://github.com/scientific-python/lazy_loader
+import importlib
+import os
+import sys
+from typing import TYPE_CHECKING
+
+
+__version__ = "0.29.1"
+
+# Alphabetical order of definitions is ensured in tests
+# WARNING: any comment added in this dictionary definition will be lost when
+# re-generating the file !
+_SUBMOD_ATTRS = {
+ "_commit_scheduler": [
+ "CommitScheduler",
+ ],
+ "_inference_endpoints": [
+ "InferenceEndpoint",
+ "InferenceEndpointError",
+ "InferenceEndpointStatus",
+ "InferenceEndpointTimeoutError",
+ "InferenceEndpointType",
+ ],
+ "_login": [
+ "auth_list",
+ "auth_switch",
+ "interpreter_login",
+ "login",
+ "logout",
+ "notebook_login",
+ ],
+ "_snapshot_download": [
+ "snapshot_download",
+ ],
+ "_space_api": [
+ "SpaceHardware",
+ "SpaceRuntime",
+ "SpaceStage",
+ "SpaceStorage",
+ "SpaceVariable",
+ ],
+ "_tensorboard_logger": [
+ "HFSummaryWriter",
+ ],
+ "_webhooks_payload": [
+ "WebhookPayload",
+ "WebhookPayloadComment",
+ "WebhookPayloadDiscussion",
+ "WebhookPayloadDiscussionChanges",
+ "WebhookPayloadEvent",
+ "WebhookPayloadMovedTo",
+ "WebhookPayloadRepo",
+ "WebhookPayloadUrl",
+ "WebhookPayloadWebhook",
+ ],
+ "_webhooks_server": [
+ "WebhooksServer",
+ "webhook_endpoint",
+ ],
+ "community": [
+ "Discussion",
+ "DiscussionComment",
+ "DiscussionCommit",
+ "DiscussionEvent",
+ "DiscussionStatusChange",
+ "DiscussionTitleChange",
+ "DiscussionWithDetails",
+ ],
+ "constants": [
+ "CONFIG_NAME",
+ "FLAX_WEIGHTS_NAME",
+ "HUGGINGFACE_CO_URL_HOME",
+ "HUGGINGFACE_CO_URL_TEMPLATE",
+ "PYTORCH_WEIGHTS_NAME",
+ "REPO_TYPE_DATASET",
+ "REPO_TYPE_MODEL",
+ "REPO_TYPE_SPACE",
+ "TF2_WEIGHTS_NAME",
+ "TF_WEIGHTS_NAME",
+ ],
+ "fastai_utils": [
+ "_save_pretrained_fastai",
+ "from_pretrained_fastai",
+ "push_to_hub_fastai",
+ ],
+ "file_download": [
+ "HfFileMetadata",
+ "_CACHED_NO_EXIST",
+ "get_hf_file_metadata",
+ "hf_hub_download",
+ "hf_hub_url",
+ "try_to_load_from_cache",
+ ],
+ "hf_api": [
+ "Collection",
+ "CollectionItem",
+ "CommitInfo",
+ "CommitOperation",
+ "CommitOperationAdd",
+ "CommitOperationCopy",
+ "CommitOperationDelete",
+ "DatasetInfo",
+ "GitCommitInfo",
+ "GitRefInfo",
+ "GitRefs",
+ "HfApi",
+ "ModelInfo",
+ "RepoUrl",
+ "SpaceInfo",
+ "User",
+ "UserLikes",
+ "WebhookInfo",
+ "WebhookWatchedItem",
+ "accept_access_request",
+ "add_collection_item",
+ "add_space_secret",
+ "add_space_variable",
+ "auth_check",
+ "cancel_access_request",
+ "change_discussion_status",
+ "comment_discussion",
+ "create_branch",
+ "create_collection",
+ "create_commit",
+ "create_discussion",
+ "create_inference_endpoint",
+ "create_pull_request",
+ "create_repo",
+ "create_tag",
+ "create_webhook",
+ "dataset_info",
+ "delete_branch",
+ "delete_collection",
+ "delete_collection_item",
+ "delete_file",
+ "delete_folder",
+ "delete_inference_endpoint",
+ "delete_repo",
+ "delete_space_secret",
+ "delete_space_storage",
+ "delete_space_variable",
+ "delete_tag",
+ "delete_webhook",
+ "disable_webhook",
+ "duplicate_space",
+ "edit_discussion_comment",
+ "enable_webhook",
+ "file_exists",
+ "get_collection",
+ "get_dataset_tags",
+ "get_discussion_details",
+ "get_full_repo_name",
+ "get_inference_endpoint",
+ "get_model_tags",
+ "get_paths_info",
+ "get_repo_discussions",
+ "get_safetensors_metadata",
+ "get_space_runtime",
+ "get_space_variables",
+ "get_token_permission",
+ "get_user_overview",
+ "get_webhook",
+ "grant_access",
+ "list_accepted_access_requests",
+ "list_collections",
+ "list_datasets",
+ "list_inference_endpoints",
+ "list_liked_repos",
+ "list_models",
+ "list_organization_members",
+ "list_papers",
+ "list_pending_access_requests",
+ "list_rejected_access_requests",
+ "list_repo_commits",
+ "list_repo_files",
+ "list_repo_likers",
+ "list_repo_refs",
+ "list_repo_tree",
+ "list_spaces",
+ "list_user_followers",
+ "list_user_following",
+ "list_webhooks",
+ "merge_pull_request",
+ "model_info",
+ "move_repo",
+ "paper_info",
+ "parse_safetensors_file_metadata",
+ "pause_inference_endpoint",
+ "pause_space",
+ "preupload_lfs_files",
+ "reject_access_request",
+ "rename_discussion",
+ "repo_exists",
+ "repo_info",
+ "repo_type_and_id_from_hf_id",
+ "request_space_hardware",
+ "request_space_storage",
+ "restart_space",
+ "resume_inference_endpoint",
+ "revision_exists",
+ "run_as_future",
+ "scale_to_zero_inference_endpoint",
+ "set_space_sleep_time",
+ "space_info",
+ "super_squash_history",
+ "unlike",
+ "update_collection_item",
+ "update_collection_metadata",
+ "update_inference_endpoint",
+ "update_repo_settings",
+ "update_repo_visibility",
+ "update_webhook",
+ "upload_file",
+ "upload_folder",
+ "upload_large_folder",
+ "whoami",
+ ],
+ "hf_file_system": [
+ "HfFileSystem",
+ "HfFileSystemFile",
+ "HfFileSystemResolvedPath",
+ "HfFileSystemStreamFile",
+ ],
+ "hub_mixin": [
+ "ModelHubMixin",
+ "PyTorchModelHubMixin",
+ ],
+ "inference._client": [
+ "InferenceClient",
+ "InferenceTimeoutError",
+ ],
+ "inference._generated._async_client": [
+ "AsyncInferenceClient",
+ ],
+ "inference._generated.types": [
+ "AudioClassificationInput",
+ "AudioClassificationOutputElement",
+ "AudioClassificationOutputTransform",
+ "AudioClassificationParameters",
+ "AudioToAudioInput",
+ "AudioToAudioOutputElement",
+ "AutomaticSpeechRecognitionEarlyStoppingEnum",
+ "AutomaticSpeechRecognitionGenerationParameters",
+ "AutomaticSpeechRecognitionInput",
+ "AutomaticSpeechRecognitionOutput",
+ "AutomaticSpeechRecognitionOutputChunk",
+ "AutomaticSpeechRecognitionParameters",
+ "ChatCompletionInput",
+ "ChatCompletionInputFunctionDefinition",
+ "ChatCompletionInputFunctionName",
+ "ChatCompletionInputGrammarType",
+ "ChatCompletionInputGrammarTypeType",
+ "ChatCompletionInputMessage",
+ "ChatCompletionInputMessageChunk",
+ "ChatCompletionInputMessageChunkType",
+ "ChatCompletionInputStreamOptions",
+ "ChatCompletionInputTool",
+ "ChatCompletionInputToolChoiceClass",
+ "ChatCompletionInputToolChoiceEnum",
+ "ChatCompletionInputURL",
+ "ChatCompletionOutput",
+ "ChatCompletionOutputComplete",
+ "ChatCompletionOutputFunctionDefinition",
+ "ChatCompletionOutputLogprob",
+ "ChatCompletionOutputLogprobs",
+ "ChatCompletionOutputMessage",
+ "ChatCompletionOutputToolCall",
+ "ChatCompletionOutputTopLogprob",
+ "ChatCompletionOutputUsage",
+ "ChatCompletionStreamOutput",
+ "ChatCompletionStreamOutputChoice",
+ "ChatCompletionStreamOutputDelta",
+ "ChatCompletionStreamOutputDeltaToolCall",
+ "ChatCompletionStreamOutputFunction",
+ "ChatCompletionStreamOutputLogprob",
+ "ChatCompletionStreamOutputLogprobs",
+ "ChatCompletionStreamOutputTopLogprob",
+ "ChatCompletionStreamOutputUsage",
+ "DepthEstimationInput",
+ "DepthEstimationOutput",
+ "DocumentQuestionAnsweringInput",
+ "DocumentQuestionAnsweringInputData",
+ "DocumentQuestionAnsweringOutputElement",
+ "DocumentQuestionAnsweringParameters",
+ "FeatureExtractionInput",
+ "FeatureExtractionInputTruncationDirection",
+ "FillMaskInput",
+ "FillMaskOutputElement",
+ "FillMaskParameters",
+ "ImageClassificationInput",
+ "ImageClassificationOutputElement",
+ "ImageClassificationOutputTransform",
+ "ImageClassificationParameters",
+ "ImageSegmentationInput",
+ "ImageSegmentationOutputElement",
+ "ImageSegmentationParameters",
+ "ImageSegmentationSubtask",
+ "ImageToImageInput",
+ "ImageToImageOutput",
+ "ImageToImageParameters",
+ "ImageToImageTargetSize",
+ "ImageToTextEarlyStoppingEnum",
+ "ImageToTextGenerationParameters",
+ "ImageToTextInput",
+ "ImageToTextOutput",
+ "ImageToTextParameters",
+ "ObjectDetectionBoundingBox",
+ "ObjectDetectionInput",
+ "ObjectDetectionOutputElement",
+ "ObjectDetectionParameters",
+ "Padding",
+ "QuestionAnsweringInput",
+ "QuestionAnsweringInputData",
+ "QuestionAnsweringOutputElement",
+ "QuestionAnsweringParameters",
+ "SentenceSimilarityInput",
+ "SentenceSimilarityInputData",
+ "SummarizationInput",
+ "SummarizationOutput",
+ "SummarizationParameters",
+ "SummarizationTruncationStrategy",
+ "TableQuestionAnsweringInput",
+ "TableQuestionAnsweringInputData",
+ "TableQuestionAnsweringOutputElement",
+ "TableQuestionAnsweringParameters",
+ "Text2TextGenerationInput",
+ "Text2TextGenerationOutput",
+ "Text2TextGenerationParameters",
+ "Text2TextGenerationTruncationStrategy",
+ "TextClassificationInput",
+ "TextClassificationOutputElement",
+ "TextClassificationOutputTransform",
+ "TextClassificationParameters",
+ "TextGenerationInput",
+ "TextGenerationInputGenerateParameters",
+ "TextGenerationInputGrammarType",
+ "TextGenerationOutput",
+ "TextGenerationOutputBestOfSequence",
+ "TextGenerationOutputDetails",
+ "TextGenerationOutputFinishReason",
+ "TextGenerationOutputPrefillToken",
+ "TextGenerationOutputToken",
+ "TextGenerationStreamOutput",
+ "TextGenerationStreamOutputStreamDetails",
+ "TextGenerationStreamOutputToken",
+ "TextToAudioEarlyStoppingEnum",
+ "TextToAudioGenerationParameters",
+ "TextToAudioInput",
+ "TextToAudioOutput",
+ "TextToAudioParameters",
+ "TextToImageInput",
+ "TextToImageOutput",
+ "TextToImageParameters",
+ "TextToSpeechEarlyStoppingEnum",
+ "TextToSpeechGenerationParameters",
+ "TextToSpeechInput",
+ "TextToSpeechOutput",
+ "TextToSpeechParameters",
+ "TextToVideoInput",
+ "TextToVideoOutput",
+ "TextToVideoParameters",
+ "TokenClassificationAggregationStrategy",
+ "TokenClassificationInput",
+ "TokenClassificationOutputElement",
+ "TokenClassificationParameters",
+ "TranslationInput",
+ "TranslationOutput",
+ "TranslationParameters",
+ "TranslationTruncationStrategy",
+ "TypeEnum",
+ "VideoClassificationInput",
+ "VideoClassificationOutputElement",
+ "VideoClassificationOutputTransform",
+ "VideoClassificationParameters",
+ "VisualQuestionAnsweringInput",
+ "VisualQuestionAnsweringInputData",
+ "VisualQuestionAnsweringOutputElement",
+ "VisualQuestionAnsweringParameters",
+ "ZeroShotClassificationInput",
+ "ZeroShotClassificationOutputElement",
+ "ZeroShotClassificationParameters",
+ "ZeroShotImageClassificationInput",
+ "ZeroShotImageClassificationOutputElement",
+ "ZeroShotImageClassificationParameters",
+ "ZeroShotObjectDetectionBoundingBox",
+ "ZeroShotObjectDetectionInput",
+ "ZeroShotObjectDetectionOutputElement",
+ "ZeroShotObjectDetectionParameters",
+ ],
+ "inference_api": [
+ "InferenceApi",
+ ],
+ "keras_mixin": [
+ "KerasModelHubMixin",
+ "from_pretrained_keras",
+ "push_to_hub_keras",
+ "save_pretrained_keras",
+ ],
+ "repocard": [
+ "DatasetCard",
+ "ModelCard",
+ "RepoCard",
+ "SpaceCard",
+ "metadata_eval_result",
+ "metadata_load",
+ "metadata_save",
+ "metadata_update",
+ ],
+ "repocard_data": [
+ "CardData",
+ "DatasetCardData",
+ "EvalResult",
+ "ModelCardData",
+ "SpaceCardData",
+ ],
+ "repository": [
+ "Repository",
+ ],
+ "serialization": [
+ "StateDictSplit",
+ "get_tf_storage_size",
+ "get_torch_storage_id",
+ "get_torch_storage_size",
+ "load_state_dict_from_file",
+ "load_torch_model",
+ "save_torch_model",
+ "save_torch_state_dict",
+ "split_state_dict_into_shards_factory",
+ "split_tf_state_dict_into_shards",
+ "split_torch_state_dict_into_shards",
+ ],
+ "serialization._dduf": [
+ "DDUFEntry",
+ "export_entries_as_dduf",
+ "export_folder_as_dduf",
+ "read_dduf_file",
+ ],
+ "utils": [
+ "CacheNotFound",
+ "CachedFileInfo",
+ "CachedRepoInfo",
+ "CachedRevisionInfo",
+ "CorruptedCacheException",
+ "DeleteCacheStrategy",
+ "HFCacheInfo",
+ "HfFolder",
+ "cached_assets_path",
+ "configure_http_backend",
+ "dump_environment_info",
+ "get_session",
+ "get_token",
+ "logging",
+ "scan_cache_dir",
+ ],
+}
+
+# WARNING: __all__ is generated automatically, Any manual edit will be lost when re-generating this file !
+#
+# To update the static imports, please run the following command and commit the changes.
+# ```
+# # Use script
+# python utils/check_all_variable.py --update
+#
+# # Or run style on codebase
+# make style
+# ```
+
+__all__ = [
+ "AsyncInferenceClient",
+ "AudioClassificationInput",
+ "AudioClassificationOutputElement",
+ "AudioClassificationOutputTransform",
+ "AudioClassificationParameters",
+ "AudioToAudioInput",
+ "AudioToAudioOutputElement",
+ "AutomaticSpeechRecognitionEarlyStoppingEnum",
+ "AutomaticSpeechRecognitionGenerationParameters",
+ "AutomaticSpeechRecognitionInput",
+ "AutomaticSpeechRecognitionOutput",
+ "AutomaticSpeechRecognitionOutputChunk",
+ "AutomaticSpeechRecognitionParameters",
+ "CONFIG_NAME",
+ "CacheNotFound",
+ "CachedFileInfo",
+ "CachedRepoInfo",
+ "CachedRevisionInfo",
+ "CardData",
+ "ChatCompletionInput",
+ "ChatCompletionInputFunctionDefinition",
+ "ChatCompletionInputFunctionName",
+ "ChatCompletionInputGrammarType",
+ "ChatCompletionInputGrammarTypeType",
+ "ChatCompletionInputMessage",
+ "ChatCompletionInputMessageChunk",
+ "ChatCompletionInputMessageChunkType",
+ "ChatCompletionInputStreamOptions",
+ "ChatCompletionInputTool",
+ "ChatCompletionInputToolChoiceClass",
+ "ChatCompletionInputToolChoiceEnum",
+ "ChatCompletionInputURL",
+ "ChatCompletionOutput",
+ "ChatCompletionOutputComplete",
+ "ChatCompletionOutputFunctionDefinition",
+ "ChatCompletionOutputLogprob",
+ "ChatCompletionOutputLogprobs",
+ "ChatCompletionOutputMessage",
+ "ChatCompletionOutputToolCall",
+ "ChatCompletionOutputTopLogprob",
+ "ChatCompletionOutputUsage",
+ "ChatCompletionStreamOutput",
+ "ChatCompletionStreamOutputChoice",
+ "ChatCompletionStreamOutputDelta",
+ "ChatCompletionStreamOutputDeltaToolCall",
+ "ChatCompletionStreamOutputFunction",
+ "ChatCompletionStreamOutputLogprob",
+ "ChatCompletionStreamOutputLogprobs",
+ "ChatCompletionStreamOutputTopLogprob",
+ "ChatCompletionStreamOutputUsage",
+ "Collection",
+ "CollectionItem",
+ "CommitInfo",
+ "CommitOperation",
+ "CommitOperationAdd",
+ "CommitOperationCopy",
+ "CommitOperationDelete",
+ "CommitScheduler",
+ "CorruptedCacheException",
+ "DDUFEntry",
+ "DatasetCard",
+ "DatasetCardData",
+ "DatasetInfo",
+ "DeleteCacheStrategy",
+ "DepthEstimationInput",
+ "DepthEstimationOutput",
+ "Discussion",
+ "DiscussionComment",
+ "DiscussionCommit",
+ "DiscussionEvent",
+ "DiscussionStatusChange",
+ "DiscussionTitleChange",
+ "DiscussionWithDetails",
+ "DocumentQuestionAnsweringInput",
+ "DocumentQuestionAnsweringInputData",
+ "DocumentQuestionAnsweringOutputElement",
+ "DocumentQuestionAnsweringParameters",
+ "EvalResult",
+ "FLAX_WEIGHTS_NAME",
+ "FeatureExtractionInput",
+ "FeatureExtractionInputTruncationDirection",
+ "FillMaskInput",
+ "FillMaskOutputElement",
+ "FillMaskParameters",
+ "GitCommitInfo",
+ "GitRefInfo",
+ "GitRefs",
+ "HFCacheInfo",
+ "HFSummaryWriter",
+ "HUGGINGFACE_CO_URL_HOME",
+ "HUGGINGFACE_CO_URL_TEMPLATE",
+ "HfApi",
+ "HfFileMetadata",
+ "HfFileSystem",
+ "HfFileSystemFile",
+ "HfFileSystemResolvedPath",
+ "HfFileSystemStreamFile",
+ "HfFolder",
+ "ImageClassificationInput",
+ "ImageClassificationOutputElement",
+ "ImageClassificationOutputTransform",
+ "ImageClassificationParameters",
+ "ImageSegmentationInput",
+ "ImageSegmentationOutputElement",
+ "ImageSegmentationParameters",
+ "ImageSegmentationSubtask",
+ "ImageToImageInput",
+ "ImageToImageOutput",
+ "ImageToImageParameters",
+ "ImageToImageTargetSize",
+ "ImageToTextEarlyStoppingEnum",
+ "ImageToTextGenerationParameters",
+ "ImageToTextInput",
+ "ImageToTextOutput",
+ "ImageToTextParameters",
+ "InferenceApi",
+ "InferenceClient",
+ "InferenceEndpoint",
+ "InferenceEndpointError",
+ "InferenceEndpointStatus",
+ "InferenceEndpointTimeoutError",
+ "InferenceEndpointType",
+ "InferenceTimeoutError",
+ "KerasModelHubMixin",
+ "ModelCard",
+ "ModelCardData",
+ "ModelHubMixin",
+ "ModelInfo",
+ "ObjectDetectionBoundingBox",
+ "ObjectDetectionInput",
+ "ObjectDetectionOutputElement",
+ "ObjectDetectionParameters",
+ "PYTORCH_WEIGHTS_NAME",
+ "Padding",
+ "PyTorchModelHubMixin",
+ "QuestionAnsweringInput",
+ "QuestionAnsweringInputData",
+ "QuestionAnsweringOutputElement",
+ "QuestionAnsweringParameters",
+ "REPO_TYPE_DATASET",
+ "REPO_TYPE_MODEL",
+ "REPO_TYPE_SPACE",
+ "RepoCard",
+ "RepoUrl",
+ "Repository",
+ "SentenceSimilarityInput",
+ "SentenceSimilarityInputData",
+ "SpaceCard",
+ "SpaceCardData",
+ "SpaceHardware",
+ "SpaceInfo",
+ "SpaceRuntime",
+ "SpaceStage",
+ "SpaceStorage",
+ "SpaceVariable",
+ "StateDictSplit",
+ "SummarizationInput",
+ "SummarizationOutput",
+ "SummarizationParameters",
+ "SummarizationTruncationStrategy",
+ "TF2_WEIGHTS_NAME",
+ "TF_WEIGHTS_NAME",
+ "TableQuestionAnsweringInput",
+ "TableQuestionAnsweringInputData",
+ "TableQuestionAnsweringOutputElement",
+ "TableQuestionAnsweringParameters",
+ "Text2TextGenerationInput",
+ "Text2TextGenerationOutput",
+ "Text2TextGenerationParameters",
+ "Text2TextGenerationTruncationStrategy",
+ "TextClassificationInput",
+ "TextClassificationOutputElement",
+ "TextClassificationOutputTransform",
+ "TextClassificationParameters",
+ "TextGenerationInput",
+ "TextGenerationInputGenerateParameters",
+ "TextGenerationInputGrammarType",
+ "TextGenerationOutput",
+ "TextGenerationOutputBestOfSequence",
+ "TextGenerationOutputDetails",
+ "TextGenerationOutputFinishReason",
+ "TextGenerationOutputPrefillToken",
+ "TextGenerationOutputToken",
+ "TextGenerationStreamOutput",
+ "TextGenerationStreamOutputStreamDetails",
+ "TextGenerationStreamOutputToken",
+ "TextToAudioEarlyStoppingEnum",
+ "TextToAudioGenerationParameters",
+ "TextToAudioInput",
+ "TextToAudioOutput",
+ "TextToAudioParameters",
+ "TextToImageInput",
+ "TextToImageOutput",
+ "TextToImageParameters",
+ "TextToSpeechEarlyStoppingEnum",
+ "TextToSpeechGenerationParameters",
+ "TextToSpeechInput",
+ "TextToSpeechOutput",
+ "TextToSpeechParameters",
+ "TextToVideoInput",
+ "TextToVideoOutput",
+ "TextToVideoParameters",
+ "TokenClassificationAggregationStrategy",
+ "TokenClassificationInput",
+ "TokenClassificationOutputElement",
+ "TokenClassificationParameters",
+ "TranslationInput",
+ "TranslationOutput",
+ "TranslationParameters",
+ "TranslationTruncationStrategy",
+ "TypeEnum",
+ "User",
+ "UserLikes",
+ "VideoClassificationInput",
+ "VideoClassificationOutputElement",
+ "VideoClassificationOutputTransform",
+ "VideoClassificationParameters",
+ "VisualQuestionAnsweringInput",
+ "VisualQuestionAnsweringInputData",
+ "VisualQuestionAnsweringOutputElement",
+ "VisualQuestionAnsweringParameters",
+ "WebhookInfo",
+ "WebhookPayload",
+ "WebhookPayloadComment",
+ "WebhookPayloadDiscussion",
+ "WebhookPayloadDiscussionChanges",
+ "WebhookPayloadEvent",
+ "WebhookPayloadMovedTo",
+ "WebhookPayloadRepo",
+ "WebhookPayloadUrl",
+ "WebhookPayloadWebhook",
+ "WebhookWatchedItem",
+ "WebhooksServer",
+ "ZeroShotClassificationInput",
+ "ZeroShotClassificationOutputElement",
+ "ZeroShotClassificationParameters",
+ "ZeroShotImageClassificationInput",
+ "ZeroShotImageClassificationOutputElement",
+ "ZeroShotImageClassificationParameters",
+ "ZeroShotObjectDetectionBoundingBox",
+ "ZeroShotObjectDetectionInput",
+ "ZeroShotObjectDetectionOutputElement",
+ "ZeroShotObjectDetectionParameters",
+ "_CACHED_NO_EXIST",
+ "_save_pretrained_fastai",
+ "accept_access_request",
+ "add_collection_item",
+ "add_space_secret",
+ "add_space_variable",
+ "auth_check",
+ "auth_list",
+ "auth_switch",
+ "cached_assets_path",
+ "cancel_access_request",
+ "change_discussion_status",
+ "comment_discussion",
+ "configure_http_backend",
+ "create_branch",
+ "create_collection",
+ "create_commit",
+ "create_discussion",
+ "create_inference_endpoint",
+ "create_pull_request",
+ "create_repo",
+ "create_tag",
+ "create_webhook",
+ "dataset_info",
+ "delete_branch",
+ "delete_collection",
+ "delete_collection_item",
+ "delete_file",
+ "delete_folder",
+ "delete_inference_endpoint",
+ "delete_repo",
+ "delete_space_secret",
+ "delete_space_storage",
+ "delete_space_variable",
+ "delete_tag",
+ "delete_webhook",
+ "disable_webhook",
+ "dump_environment_info",
+ "duplicate_space",
+ "edit_discussion_comment",
+ "enable_webhook",
+ "export_entries_as_dduf",
+ "export_folder_as_dduf",
+ "file_exists",
+ "from_pretrained_fastai",
+ "from_pretrained_keras",
+ "get_collection",
+ "get_dataset_tags",
+ "get_discussion_details",
+ "get_full_repo_name",
+ "get_hf_file_metadata",
+ "get_inference_endpoint",
+ "get_model_tags",
+ "get_paths_info",
+ "get_repo_discussions",
+ "get_safetensors_metadata",
+ "get_session",
+ "get_space_runtime",
+ "get_space_variables",
+ "get_tf_storage_size",
+ "get_token",
+ "get_token_permission",
+ "get_torch_storage_id",
+ "get_torch_storage_size",
+ "get_user_overview",
+ "get_webhook",
+ "grant_access",
+ "hf_hub_download",
+ "hf_hub_url",
+ "interpreter_login",
+ "list_accepted_access_requests",
+ "list_collections",
+ "list_datasets",
+ "list_inference_endpoints",
+ "list_liked_repos",
+ "list_models",
+ "list_organization_members",
+ "list_papers",
+ "list_pending_access_requests",
+ "list_rejected_access_requests",
+ "list_repo_commits",
+ "list_repo_files",
+ "list_repo_likers",
+ "list_repo_refs",
+ "list_repo_tree",
+ "list_spaces",
+ "list_user_followers",
+ "list_user_following",
+ "list_webhooks",
+ "load_state_dict_from_file",
+ "load_torch_model",
+ "logging",
+ "login",
+ "logout",
+ "merge_pull_request",
+ "metadata_eval_result",
+ "metadata_load",
+ "metadata_save",
+ "metadata_update",
+ "model_info",
+ "move_repo",
+ "notebook_login",
+ "paper_info",
+ "parse_safetensors_file_metadata",
+ "pause_inference_endpoint",
+ "pause_space",
+ "preupload_lfs_files",
+ "push_to_hub_fastai",
+ "push_to_hub_keras",
+ "read_dduf_file",
+ "reject_access_request",
+ "rename_discussion",
+ "repo_exists",
+ "repo_info",
+ "repo_type_and_id_from_hf_id",
+ "request_space_hardware",
+ "request_space_storage",
+ "restart_space",
+ "resume_inference_endpoint",
+ "revision_exists",
+ "run_as_future",
+ "save_pretrained_keras",
+ "save_torch_model",
+ "save_torch_state_dict",
+ "scale_to_zero_inference_endpoint",
+ "scan_cache_dir",
+ "set_space_sleep_time",
+ "snapshot_download",
+ "space_info",
+ "split_state_dict_into_shards_factory",
+ "split_tf_state_dict_into_shards",
+ "split_torch_state_dict_into_shards",
+ "super_squash_history",
+ "try_to_load_from_cache",
+ "unlike",
+ "update_collection_item",
+ "update_collection_metadata",
+ "update_inference_endpoint",
+ "update_repo_settings",
+ "update_repo_visibility",
+ "update_webhook",
+ "upload_file",
+ "upload_folder",
+ "upload_large_folder",
+ "webhook_endpoint",
+ "whoami",
+]
+
+
+def _attach(package_name, submodules=None, submod_attrs=None):
+ """Attach lazily loaded submodules, functions, or other attributes.
+
+ Typically, modules import submodules and attributes as follows:
+
+ ```py
+ import mysubmodule
+ import anothersubmodule
+
+ from .foo import someattr
+ ```
+
+ The idea is to replace a package's `__getattr__`, `__dir__`, such that all imports
+ work exactly the way they would with normal imports, except that the import occurs
+ upon first use.
+
+ The typical way to call this function, replacing the above imports, is:
+
+ ```python
+ __getattr__, __dir__ = lazy.attach(
+ __name__,
+ ['mysubmodule', 'anothersubmodule'],
+ {'foo': ['someattr']}
+ )
+ ```
+ This functionality requires Python 3.7 or higher.
+
+ Args:
+ package_name (`str`):
+ Typically use `__name__`.
+ submodules (`set`):
+ List of submodules to attach.
+ submod_attrs (`dict`):
+ Dictionary of submodule -> list of attributes / functions.
+ These attributes are imported as they are used.
+
+ Returns:
+ __getattr__, __dir__, __all__
+
+ """
+ if submod_attrs is None:
+ submod_attrs = {}
+
+ if submodules is None:
+ submodules = set()
+ else:
+ submodules = set(submodules)
+
+ attr_to_modules = {attr: mod for mod, attrs in submod_attrs.items() for attr in attrs}
+
+ def __getattr__(name):
+ if name in submodules:
+ try:
+ return importlib.import_module(f"{package_name}.{name}")
+ except Exception as e:
+ print(f"Error importing {package_name}.{name}: {e}")
+ raise
+ elif name in attr_to_modules:
+ submod_path = f"{package_name}.{attr_to_modules[name]}"
+ try:
+ submod = importlib.import_module(submod_path)
+ except Exception as e:
+ print(f"Error importing {submod_path}: {e}")
+ raise
+ attr = getattr(submod, name)
+
+ # If the attribute lives in a file (module) with the same
+ # name as the attribute, ensure that the attribute and *not*
+ # the module is accessible on the package.
+ if name == attr_to_modules[name]:
+ pkg = sys.modules[package_name]
+ pkg.__dict__[name] = attr
+
+ return attr
+ else:
+ raise AttributeError(f"No {package_name} attribute {name}")
+
+ def __dir__():
+ return __all__
+
+ return __getattr__, __dir__
+
+
+__getattr__, __dir__ = _attach(__name__, submodules=[], submod_attrs=_SUBMOD_ATTRS)
+
+if os.environ.get("EAGER_IMPORT", ""):
+ for attr in __all__:
+ __getattr__(attr)
+
+# WARNING: any content below this statement is generated automatically. Any manual edit
+# will be lost when re-generating this file !
+#
+# To update the static imports, please run the following command and commit the changes.
+# ```
+# # Use script
+# python utils/check_static_imports.py --update
+#
+# # Or run style on codebase
+# make style
+# ```
+if TYPE_CHECKING: # pragma: no cover
+ from ._commit_scheduler import CommitScheduler # noqa: F401
+ from ._inference_endpoints import (
+ InferenceEndpoint, # noqa: F401
+ InferenceEndpointError, # noqa: F401
+ InferenceEndpointStatus, # noqa: F401
+ InferenceEndpointTimeoutError, # noqa: F401
+ InferenceEndpointType, # noqa: F401
+ )
+ from ._login import (
+ auth_list, # noqa: F401
+ auth_switch, # noqa: F401
+ interpreter_login, # noqa: F401
+ login, # noqa: F401
+ logout, # noqa: F401
+ notebook_login, # noqa: F401
+ )
+ from ._snapshot_download import snapshot_download # noqa: F401
+ from ._space_api import (
+ SpaceHardware, # noqa: F401
+ SpaceRuntime, # noqa: F401
+ SpaceStage, # noqa: F401
+ SpaceStorage, # noqa: F401
+ SpaceVariable, # noqa: F401
+ )
+ from ._tensorboard_logger import HFSummaryWriter # noqa: F401
+ from ._webhooks_payload import (
+ WebhookPayload, # noqa: F401
+ WebhookPayloadComment, # noqa: F401
+ WebhookPayloadDiscussion, # noqa: F401
+ WebhookPayloadDiscussionChanges, # noqa: F401
+ WebhookPayloadEvent, # noqa: F401
+ WebhookPayloadMovedTo, # noqa: F401
+ WebhookPayloadRepo, # noqa: F401
+ WebhookPayloadUrl, # noqa: F401
+ WebhookPayloadWebhook, # noqa: F401
+ )
+ from ._webhooks_server import (
+ WebhooksServer, # noqa: F401
+ webhook_endpoint, # noqa: F401
+ )
+ from .community import (
+ Discussion, # noqa: F401
+ DiscussionComment, # noqa: F401
+ DiscussionCommit, # noqa: F401
+ DiscussionEvent, # noqa: F401
+ DiscussionStatusChange, # noqa: F401
+ DiscussionTitleChange, # noqa: F401
+ DiscussionWithDetails, # noqa: F401
+ )
+ from .constants import (
+ CONFIG_NAME, # noqa: F401
+ FLAX_WEIGHTS_NAME, # noqa: F401
+ HUGGINGFACE_CO_URL_HOME, # noqa: F401
+ HUGGINGFACE_CO_URL_TEMPLATE, # noqa: F401
+ PYTORCH_WEIGHTS_NAME, # noqa: F401
+ REPO_TYPE_DATASET, # noqa: F401
+ REPO_TYPE_MODEL, # noqa: F401
+ REPO_TYPE_SPACE, # noqa: F401
+ TF2_WEIGHTS_NAME, # noqa: F401
+ TF_WEIGHTS_NAME, # noqa: F401
+ )
+ from .fastai_utils import (
+ _save_pretrained_fastai, # noqa: F401
+ from_pretrained_fastai, # noqa: F401
+ push_to_hub_fastai, # noqa: F401
+ )
+ from .file_download import (
+ _CACHED_NO_EXIST, # noqa: F401
+ HfFileMetadata, # noqa: F401
+ get_hf_file_metadata, # noqa: F401
+ hf_hub_download, # noqa: F401
+ hf_hub_url, # noqa: F401
+ try_to_load_from_cache, # noqa: F401
+ )
+ from .hf_api import (
+ Collection, # noqa: F401
+ CollectionItem, # noqa: F401
+ CommitInfo, # noqa: F401
+ CommitOperation, # noqa: F401
+ CommitOperationAdd, # noqa: F401
+ CommitOperationCopy, # noqa: F401
+ CommitOperationDelete, # noqa: F401
+ DatasetInfo, # noqa: F401
+ GitCommitInfo, # noqa: F401
+ GitRefInfo, # noqa: F401
+ GitRefs, # noqa: F401
+ HfApi, # noqa: F401
+ ModelInfo, # noqa: F401
+ RepoUrl, # noqa: F401
+ SpaceInfo, # noqa: F401
+ User, # noqa: F401
+ UserLikes, # noqa: F401
+ WebhookInfo, # noqa: F401
+ WebhookWatchedItem, # noqa: F401
+ accept_access_request, # noqa: F401
+ add_collection_item, # noqa: F401
+ add_space_secret, # noqa: F401
+ add_space_variable, # noqa: F401
+ auth_check, # noqa: F401
+ cancel_access_request, # noqa: F401
+ change_discussion_status, # noqa: F401
+ comment_discussion, # noqa: F401
+ create_branch, # noqa: F401
+ create_collection, # noqa: F401
+ create_commit, # noqa: F401
+ create_discussion, # noqa: F401
+ create_inference_endpoint, # noqa: F401
+ create_pull_request, # noqa: F401
+ create_repo, # noqa: F401
+ create_tag, # noqa: F401
+ create_webhook, # noqa: F401
+ dataset_info, # noqa: F401
+ delete_branch, # noqa: F401
+ delete_collection, # noqa: F401
+ delete_collection_item, # noqa: F401
+ delete_file, # noqa: F401
+ delete_folder, # noqa: F401
+ delete_inference_endpoint, # noqa: F401
+ delete_repo, # noqa: F401
+ delete_space_secret, # noqa: F401
+ delete_space_storage, # noqa: F401
+ delete_space_variable, # noqa: F401
+ delete_tag, # noqa: F401
+ delete_webhook, # noqa: F401
+ disable_webhook, # noqa: F401
+ duplicate_space, # noqa: F401
+ edit_discussion_comment, # noqa: F401
+ enable_webhook, # noqa: F401
+ file_exists, # noqa: F401
+ get_collection, # noqa: F401
+ get_dataset_tags, # noqa: F401
+ get_discussion_details, # noqa: F401
+ get_full_repo_name, # noqa: F401
+ get_inference_endpoint, # noqa: F401
+ get_model_tags, # noqa: F401
+ get_paths_info, # noqa: F401
+ get_repo_discussions, # noqa: F401
+ get_safetensors_metadata, # noqa: F401
+ get_space_runtime, # noqa: F401
+ get_space_variables, # noqa: F401
+ get_token_permission, # noqa: F401
+ get_user_overview, # noqa: F401
+ get_webhook, # noqa: F401
+ grant_access, # noqa: F401
+ list_accepted_access_requests, # noqa: F401
+ list_collections, # noqa: F401
+ list_datasets, # noqa: F401
+ list_inference_endpoints, # noqa: F401
+ list_liked_repos, # noqa: F401
+ list_models, # noqa: F401
+ list_organization_members, # noqa: F401
+ list_papers, # noqa: F401
+ list_pending_access_requests, # noqa: F401
+ list_rejected_access_requests, # noqa: F401
+ list_repo_commits, # noqa: F401
+ list_repo_files, # noqa: F401
+ list_repo_likers, # noqa: F401
+ list_repo_refs, # noqa: F401
+ list_repo_tree, # noqa: F401
+ list_spaces, # noqa: F401
+ list_user_followers, # noqa: F401
+ list_user_following, # noqa: F401
+ list_webhooks, # noqa: F401
+ merge_pull_request, # noqa: F401
+ model_info, # noqa: F401
+ move_repo, # noqa: F401
+ paper_info, # noqa: F401
+ parse_safetensors_file_metadata, # noqa: F401
+ pause_inference_endpoint, # noqa: F401
+ pause_space, # noqa: F401
+ preupload_lfs_files, # noqa: F401
+ reject_access_request, # noqa: F401
+ rename_discussion, # noqa: F401
+ repo_exists, # noqa: F401
+ repo_info, # noqa: F401
+ repo_type_and_id_from_hf_id, # noqa: F401
+ request_space_hardware, # noqa: F401
+ request_space_storage, # noqa: F401
+ restart_space, # noqa: F401
+ resume_inference_endpoint, # noqa: F401
+ revision_exists, # noqa: F401
+ run_as_future, # noqa: F401
+ scale_to_zero_inference_endpoint, # noqa: F401
+ set_space_sleep_time, # noqa: F401
+ space_info, # noqa: F401
+ super_squash_history, # noqa: F401
+ unlike, # noqa: F401
+ update_collection_item, # noqa: F401
+ update_collection_metadata, # noqa: F401
+ update_inference_endpoint, # noqa: F401
+ update_repo_settings, # noqa: F401
+ update_repo_visibility, # noqa: F401
+ update_webhook, # noqa: F401
+ upload_file, # noqa: F401
+ upload_folder, # noqa: F401
+ upload_large_folder, # noqa: F401
+ whoami, # noqa: F401
+ )
+ from .hf_file_system import (
+ HfFileSystem, # noqa: F401
+ HfFileSystemFile, # noqa: F401
+ HfFileSystemResolvedPath, # noqa: F401
+ HfFileSystemStreamFile, # noqa: F401
+ )
+ from .hub_mixin import (
+ ModelHubMixin, # noqa: F401
+ PyTorchModelHubMixin, # noqa: F401
+ )
+ from .inference._client import (
+ InferenceClient, # noqa: F401
+ InferenceTimeoutError, # noqa: F401
+ )
+ from .inference._generated._async_client import AsyncInferenceClient # noqa: F401
+ from .inference._generated.types import (
+ AudioClassificationInput, # noqa: F401
+ AudioClassificationOutputElement, # noqa: F401
+ AudioClassificationOutputTransform, # noqa: F401
+ AudioClassificationParameters, # noqa: F401
+ AudioToAudioInput, # noqa: F401
+ AudioToAudioOutputElement, # noqa: F401
+ AutomaticSpeechRecognitionEarlyStoppingEnum, # noqa: F401
+ AutomaticSpeechRecognitionGenerationParameters, # noqa: F401
+ AutomaticSpeechRecognitionInput, # noqa: F401
+ AutomaticSpeechRecognitionOutput, # noqa: F401
+ AutomaticSpeechRecognitionOutputChunk, # noqa: F401
+ AutomaticSpeechRecognitionParameters, # noqa: F401
+ ChatCompletionInput, # noqa: F401
+ ChatCompletionInputFunctionDefinition, # noqa: F401
+ ChatCompletionInputFunctionName, # noqa: F401
+ ChatCompletionInputGrammarType, # noqa: F401
+ ChatCompletionInputGrammarTypeType, # noqa: F401
+ ChatCompletionInputMessage, # noqa: F401
+ ChatCompletionInputMessageChunk, # noqa: F401
+ ChatCompletionInputMessageChunkType, # noqa: F401
+ ChatCompletionInputStreamOptions, # noqa: F401
+ ChatCompletionInputTool, # noqa: F401
+ ChatCompletionInputToolChoiceClass, # noqa: F401
+ ChatCompletionInputToolChoiceEnum, # noqa: F401
+ ChatCompletionInputURL, # noqa: F401
+ ChatCompletionOutput, # noqa: F401
+ ChatCompletionOutputComplete, # noqa: F401
+ ChatCompletionOutputFunctionDefinition, # noqa: F401
+ ChatCompletionOutputLogprob, # noqa: F401
+ ChatCompletionOutputLogprobs, # noqa: F401
+ ChatCompletionOutputMessage, # noqa: F401
+ ChatCompletionOutputToolCall, # noqa: F401
+ ChatCompletionOutputTopLogprob, # noqa: F401
+ ChatCompletionOutputUsage, # noqa: F401
+ ChatCompletionStreamOutput, # noqa: F401
+ ChatCompletionStreamOutputChoice, # noqa: F401
+ ChatCompletionStreamOutputDelta, # noqa: F401
+ ChatCompletionStreamOutputDeltaToolCall, # noqa: F401
+ ChatCompletionStreamOutputFunction, # noqa: F401
+ ChatCompletionStreamOutputLogprob, # noqa: F401
+ ChatCompletionStreamOutputLogprobs, # noqa: F401
+ ChatCompletionStreamOutputTopLogprob, # noqa: F401
+ ChatCompletionStreamOutputUsage, # noqa: F401
+ DepthEstimationInput, # noqa: F401
+ DepthEstimationOutput, # noqa: F401
+ DocumentQuestionAnsweringInput, # noqa: F401
+ DocumentQuestionAnsweringInputData, # noqa: F401
+ DocumentQuestionAnsweringOutputElement, # noqa: F401
+ DocumentQuestionAnsweringParameters, # noqa: F401
+ FeatureExtractionInput, # noqa: F401
+ FeatureExtractionInputTruncationDirection, # noqa: F401
+ FillMaskInput, # noqa: F401
+ FillMaskOutputElement, # noqa: F401
+ FillMaskParameters, # noqa: F401
+ ImageClassificationInput, # noqa: F401
+ ImageClassificationOutputElement, # noqa: F401
+ ImageClassificationOutputTransform, # noqa: F401
+ ImageClassificationParameters, # noqa: F401
+ ImageSegmentationInput, # noqa: F401
+ ImageSegmentationOutputElement, # noqa: F401
+ ImageSegmentationParameters, # noqa: F401
+ ImageSegmentationSubtask, # noqa: F401
+ ImageToImageInput, # noqa: F401
+ ImageToImageOutput, # noqa: F401
+ ImageToImageParameters, # noqa: F401
+ ImageToImageTargetSize, # noqa: F401
+ ImageToTextEarlyStoppingEnum, # noqa: F401
+ ImageToTextGenerationParameters, # noqa: F401
+ ImageToTextInput, # noqa: F401
+ ImageToTextOutput, # noqa: F401
+ ImageToTextParameters, # noqa: F401
+ ObjectDetectionBoundingBox, # noqa: F401
+ ObjectDetectionInput, # noqa: F401
+ ObjectDetectionOutputElement, # noqa: F401
+ ObjectDetectionParameters, # noqa: F401
+ Padding, # noqa: F401
+ QuestionAnsweringInput, # noqa: F401
+ QuestionAnsweringInputData, # noqa: F401
+ QuestionAnsweringOutputElement, # noqa: F401
+ QuestionAnsweringParameters, # noqa: F401
+ SentenceSimilarityInput, # noqa: F401
+ SentenceSimilarityInputData, # noqa: F401
+ SummarizationInput, # noqa: F401
+ SummarizationOutput, # noqa: F401
+ SummarizationParameters, # noqa: F401
+ SummarizationTruncationStrategy, # noqa: F401
+ TableQuestionAnsweringInput, # noqa: F401
+ TableQuestionAnsweringInputData, # noqa: F401
+ TableQuestionAnsweringOutputElement, # noqa: F401
+ TableQuestionAnsweringParameters, # noqa: F401
+ Text2TextGenerationInput, # noqa: F401
+ Text2TextGenerationOutput, # noqa: F401
+ Text2TextGenerationParameters, # noqa: F401
+ Text2TextGenerationTruncationStrategy, # noqa: F401
+ TextClassificationInput, # noqa: F401
+ TextClassificationOutputElement, # noqa: F401
+ TextClassificationOutputTransform, # noqa: F401
+ TextClassificationParameters, # noqa: F401
+ TextGenerationInput, # noqa: F401
+ TextGenerationInputGenerateParameters, # noqa: F401
+ TextGenerationInputGrammarType, # noqa: F401
+ TextGenerationOutput, # noqa: F401
+ TextGenerationOutputBestOfSequence, # noqa: F401
+ TextGenerationOutputDetails, # noqa: F401
+ TextGenerationOutputFinishReason, # noqa: F401
+ TextGenerationOutputPrefillToken, # noqa: F401
+ TextGenerationOutputToken, # noqa: F401
+ TextGenerationStreamOutput, # noqa: F401
+ TextGenerationStreamOutputStreamDetails, # noqa: F401
+ TextGenerationStreamOutputToken, # noqa: F401
+ TextToAudioEarlyStoppingEnum, # noqa: F401
+ TextToAudioGenerationParameters, # noqa: F401
+ TextToAudioInput, # noqa: F401
+ TextToAudioOutput, # noqa: F401
+ TextToAudioParameters, # noqa: F401
+ TextToImageInput, # noqa: F401
+ TextToImageOutput, # noqa: F401
+ TextToImageParameters, # noqa: F401
+ TextToSpeechEarlyStoppingEnum, # noqa: F401
+ TextToSpeechGenerationParameters, # noqa: F401
+ TextToSpeechInput, # noqa: F401
+ TextToSpeechOutput, # noqa: F401
+ TextToSpeechParameters, # noqa: F401
+ TextToVideoInput, # noqa: F401
+ TextToVideoOutput, # noqa: F401
+ TextToVideoParameters, # noqa: F401
+ TokenClassificationAggregationStrategy, # noqa: F401
+ TokenClassificationInput, # noqa: F401
+ TokenClassificationOutputElement, # noqa: F401
+ TokenClassificationParameters, # noqa: F401
+ TranslationInput, # noqa: F401
+ TranslationOutput, # noqa: F401
+ TranslationParameters, # noqa: F401
+ TranslationTruncationStrategy, # noqa: F401
+ TypeEnum, # noqa: F401
+ VideoClassificationInput, # noqa: F401
+ VideoClassificationOutputElement, # noqa: F401
+ VideoClassificationOutputTransform, # noqa: F401
+ VideoClassificationParameters, # noqa: F401
+ VisualQuestionAnsweringInput, # noqa: F401
+ VisualQuestionAnsweringInputData, # noqa: F401
+ VisualQuestionAnsweringOutputElement, # noqa: F401
+ VisualQuestionAnsweringParameters, # noqa: F401
+ ZeroShotClassificationInput, # noqa: F401
+ ZeroShotClassificationOutputElement, # noqa: F401
+ ZeroShotClassificationParameters, # noqa: F401
+ ZeroShotImageClassificationInput, # noqa: F401
+ ZeroShotImageClassificationOutputElement, # noqa: F401
+ ZeroShotImageClassificationParameters, # noqa: F401
+ ZeroShotObjectDetectionBoundingBox, # noqa: F401
+ ZeroShotObjectDetectionInput, # noqa: F401
+ ZeroShotObjectDetectionOutputElement, # noqa: F401
+ ZeroShotObjectDetectionParameters, # noqa: F401
+ )
+ from .inference_api import InferenceApi # noqa: F401
+ from .keras_mixin import (
+ KerasModelHubMixin, # noqa: F401
+ from_pretrained_keras, # noqa: F401
+ push_to_hub_keras, # noqa: F401
+ save_pretrained_keras, # noqa: F401
+ )
+ from .repocard import (
+ DatasetCard, # noqa: F401
+ ModelCard, # noqa: F401
+ RepoCard, # noqa: F401
+ SpaceCard, # noqa: F401
+ metadata_eval_result, # noqa: F401
+ metadata_load, # noqa: F401
+ metadata_save, # noqa: F401
+ metadata_update, # noqa: F401
+ )
+ from .repocard_data import (
+ CardData, # noqa: F401
+ DatasetCardData, # noqa: F401
+ EvalResult, # noqa: F401
+ ModelCardData, # noqa: F401
+ SpaceCardData, # noqa: F401
+ )
+ from .repository import Repository # noqa: F401
+ from .serialization import (
+ StateDictSplit, # noqa: F401
+ get_tf_storage_size, # noqa: F401
+ get_torch_storage_id, # noqa: F401
+ get_torch_storage_size, # noqa: F401
+ load_state_dict_from_file, # noqa: F401
+ load_torch_model, # noqa: F401
+ save_torch_model, # noqa: F401
+ save_torch_state_dict, # noqa: F401
+ split_state_dict_into_shards_factory, # noqa: F401
+ split_tf_state_dict_into_shards, # noqa: F401
+ split_torch_state_dict_into_shards, # noqa: F401
+ )
+ from .serialization._dduf import (
+ DDUFEntry, # noqa: F401
+ export_entries_as_dduf, # noqa: F401
+ export_folder_as_dduf, # noqa: F401
+ read_dduf_file, # noqa: F401
+ )
+ from .utils import (
+ CachedFileInfo, # noqa: F401
+ CachedRepoInfo, # noqa: F401
+ CachedRevisionInfo, # noqa: F401
+ CacheNotFound, # noqa: F401
+ CorruptedCacheException, # noqa: F401
+ DeleteCacheStrategy, # noqa: F401
+ HFCacheInfo, # noqa: F401
+ HfFolder, # noqa: F401
+ cached_assets_path, # noqa: F401
+ configure_http_backend, # noqa: F401
+ dump_environment_info, # noqa: F401
+ get_session, # noqa: F401
+ get_token, # noqa: F401
+ logging, # noqa: F401
+ scan_cache_dir, # noqa: F401
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/_commit_api.py b/env/Lib/site-packages/huggingface_hub/_commit_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..783a3d2e3fdf2301000a6088e02ba74742a87454
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_commit_api.py
@@ -0,0 +1,758 @@
+"""
+Type definitions and utilities for the `create_commit` API
+"""
+
+import base64
+import io
+import os
+import warnings
+from collections import defaultdict
+from contextlib import contextmanager
+from dataclasses import dataclass, field
+from itertools import groupby
+from pathlib import Path, PurePosixPath
+from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Iterable, Iterator, List, Literal, Optional, Tuple, Union
+
+from tqdm.contrib.concurrent import thread_map
+
+from . import constants
+from .errors import EntryNotFoundError
+from .file_download import hf_hub_url
+from .lfs import UploadInfo, lfs_upload, post_lfs_batch_info
+from .utils import (
+ FORBIDDEN_FOLDERS,
+ chunk_iterable,
+ get_session,
+ hf_raise_for_status,
+ logging,
+ sha,
+ tqdm_stream_file,
+ validate_hf_hub_args,
+)
+from .utils import tqdm as hf_tqdm
+
+
+if TYPE_CHECKING:
+ from .hf_api import RepoFile
+
+
+logger = logging.get_logger(__name__)
+
+
+UploadMode = Literal["lfs", "regular"]
+
+# Max is 1,000 per request on the Hub for HfApi.get_paths_info
+# Otherwise we get:
+# HfHubHTTPError: 413 Client Error: Payload Too Large for url: https://huggingface.co/api/datasets/xxx (Request ID: xxx)\n\ntoo many parameters
+# See https://github.com/huggingface/huggingface_hub/issues/1503
+FETCH_LFS_BATCH_SIZE = 500
+
+
+@dataclass
+class CommitOperationDelete:
+ """
+ Data structure holding necessary info to delete a file or a folder from a repository
+ on the Hub.
+
+ Args:
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"`
+ for a file or `"checkpoints/1fec34a/"` for a folder.
+ is_folder (`bool` or `Literal["auto"]`, *optional*)
+ Whether the Delete Operation applies to a folder or not. If "auto", the path
+ type (file or folder) is guessed automatically by looking if path ends with
+ a "/" (folder) or not (file). To explicitly set the path type, you can set
+ `is_folder=True` or `is_folder=False`.
+ """
+
+ path_in_repo: str
+ is_folder: Union[bool, Literal["auto"]] = "auto"
+
+ def __post_init__(self):
+ self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
+
+ if self.is_folder == "auto":
+ self.is_folder = self.path_in_repo.endswith("/")
+ if not isinstance(self.is_folder, bool):
+ raise ValueError(
+ f"Wrong value for `is_folder`. Must be one of [`True`, `False`, `'auto'`]. Got '{self.is_folder}'."
+ )
+
+
+@dataclass
+class CommitOperationCopy:
+ """
+ Data structure holding necessary info to copy a file in a repository on the Hub.
+
+ Limitations:
+ - Only LFS files can be copied. To copy a regular file, you need to download it locally and re-upload it
+ - Cross-repository copies are not supported.
+
+ Note: you can combine a [`CommitOperationCopy`] and a [`CommitOperationDelete`] to rename an LFS file on the Hub.
+
+ Args:
+ src_path_in_repo (`str`):
+ Relative filepath in the repo of the file to be copied, e.g. `"checkpoints/1fec34a/weights.bin"`.
+ path_in_repo (`str`):
+ Relative filepath in the repo where to copy the file, e.g. `"checkpoints/1fec34a/weights_copy.bin"`.
+ src_revision (`str`, *optional*):
+ The git revision of the file to be copied. Can be any valid git revision.
+ Default to the target commit revision.
+ """
+
+ src_path_in_repo: str
+ path_in_repo: str
+ src_revision: Optional[str] = None
+ # set to the OID of the file to be copied if it has already been uploaded
+ # useful to determine if a commit will be empty or not.
+ _src_oid: Optional[str] = None
+ # set to the OID of the file to copy to if it has already been uploaded
+ # useful to determine if a commit will be empty or not.
+ _dest_oid: Optional[str] = None
+
+ def __post_init__(self):
+ self.src_path_in_repo = _validate_path_in_repo(self.src_path_in_repo)
+ self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
+
+
+@dataclass
+class CommitOperationAdd:
+ """
+ Data structure holding necessary info to upload a file to a repository on the Hub.
+
+ Args:
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"`
+ path_or_fileobj (`str`, `Path`, `bytes`, or `BinaryIO`):
+ Either:
+ - a path to a local file (as `str` or `pathlib.Path`) to upload
+ - a buffer of bytes (`bytes`) holding the content of the file to upload
+ - a "file object" (subclass of `io.BufferedIOBase`), typically obtained
+ with `open(path, "rb")`. It must support `seek()` and `tell()` methods.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `path_or_fileobj` is not one of `str`, `Path`, `bytes` or `io.BufferedIOBase`.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `path_or_fileobj` is a `str` or `Path` but not a path to an existing file.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `path_or_fileobj` is a `io.BufferedIOBase` but it doesn't support both
+ `seek()` and `tell()`.
+ """
+
+ path_in_repo: str
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO]
+ upload_info: UploadInfo = field(init=False, repr=False)
+
+ # Internal attributes
+
+ # set to "lfs" or "regular" once known
+ _upload_mode: Optional[UploadMode] = field(init=False, repr=False, default=None)
+
+ # set to True if .gitignore rules prevent the file from being uploaded as LFS
+ # (server-side check)
+ _should_ignore: Optional[bool] = field(init=False, repr=False, default=None)
+
+ # set to the remote OID of the file if it has already been uploaded
+ # useful to determine if a commit will be empty or not
+ _remote_oid: Optional[str] = field(init=False, repr=False, default=None)
+
+ # set to True once the file has been uploaded as LFS
+ _is_uploaded: bool = field(init=False, repr=False, default=False)
+
+ # set to True once the file has been committed
+ _is_committed: bool = field(init=False, repr=False, default=False)
+
+ def __post_init__(self) -> None:
+ """Validates `path_or_fileobj` and compute `upload_info`."""
+ self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
+
+ # Validate `path_or_fileobj` value
+ if isinstance(self.path_or_fileobj, Path):
+ self.path_or_fileobj = str(self.path_or_fileobj)
+ if isinstance(self.path_or_fileobj, str):
+ path_or_fileobj = os.path.normpath(os.path.expanduser(self.path_or_fileobj))
+ if not os.path.isfile(path_or_fileobj):
+ raise ValueError(f"Provided path: '{path_or_fileobj}' is not a file on the local file system")
+ elif not isinstance(self.path_or_fileobj, (io.BufferedIOBase, bytes)):
+ # ^^ Inspired from: https://stackoverflow.com/questions/44584829/how-to-determine-if-file-is-opened-in-binary-or-text-mode
+ raise ValueError(
+ "path_or_fileobj must be either an instance of str, bytes or"
+ " io.BufferedIOBase. If you passed a file-like object, make sure it is"
+ " in binary mode."
+ )
+ if isinstance(self.path_or_fileobj, io.BufferedIOBase):
+ try:
+ self.path_or_fileobj.tell()
+ self.path_or_fileobj.seek(0, os.SEEK_CUR)
+ except (OSError, AttributeError) as exc:
+ raise ValueError(
+ "path_or_fileobj is a file-like object but does not implement seek() and tell()"
+ ) from exc
+
+ # Compute "upload_info" attribute
+ if isinstance(self.path_or_fileobj, str):
+ self.upload_info = UploadInfo.from_path(self.path_or_fileobj)
+ elif isinstance(self.path_or_fileobj, bytes):
+ self.upload_info = UploadInfo.from_bytes(self.path_or_fileobj)
+ else:
+ self.upload_info = UploadInfo.from_fileobj(self.path_or_fileobj)
+
+ @contextmanager
+ def as_file(self, with_tqdm: bool = False) -> Iterator[BinaryIO]:
+ """
+ A context manager that yields a file-like object allowing to read the underlying
+ data behind `path_or_fileobj`.
+
+ Args:
+ with_tqdm (`bool`, *optional*, defaults to `False`):
+ If True, iterating over the file object will display a progress bar. Only
+ works if the file-like object is a path to a file. Pure bytes and buffers
+ are not supported.
+
+ Example:
+
+ ```python
+ >>> operation = CommitOperationAdd(
+ ... path_in_repo="remote/dir/weights.h5",
+ ... path_or_fileobj="./local/weights.h5",
+ ... )
+ CommitOperationAdd(path_in_repo='remote/dir/weights.h5', path_or_fileobj='./local/weights.h5')
+
+ >>> with operation.as_file() as file:
+ ... content = file.read()
+
+ >>> with operation.as_file(with_tqdm=True) as file:
+ ... while True:
+ ... data = file.read(1024)
+ ... if not data:
+ ... break
+ config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
+
+ >>> with operation.as_file(with_tqdm=True) as file:
+ ... requests.put(..., data=file)
+ config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
+ ```
+ """
+ if isinstance(self.path_or_fileobj, str) or isinstance(self.path_or_fileobj, Path):
+ if with_tqdm:
+ with tqdm_stream_file(self.path_or_fileobj) as file:
+ yield file
+ else:
+ with open(self.path_or_fileobj, "rb") as file:
+ yield file
+ elif isinstance(self.path_or_fileobj, bytes):
+ yield io.BytesIO(self.path_or_fileobj)
+ elif isinstance(self.path_or_fileobj, io.BufferedIOBase):
+ prev_pos = self.path_or_fileobj.tell()
+ yield self.path_or_fileobj
+ self.path_or_fileobj.seek(prev_pos, io.SEEK_SET)
+
+ def b64content(self) -> bytes:
+ """
+ The base64-encoded content of `path_or_fileobj`
+
+ Returns: `bytes`
+ """
+ with self.as_file() as file:
+ return base64.b64encode(file.read())
+
+ @property
+ def _local_oid(self) -> Optional[str]:
+ """Return the OID of the local file.
+
+ This OID is then compared to `self._remote_oid` to check if the file has changed compared to the remote one.
+ If the file did not change, we won't upload it again to prevent empty commits.
+
+ For LFS files, the OID corresponds to the SHA256 of the file content (used a LFS ref).
+ For regular files, the OID corresponds to the SHA1 of the file content.
+ Note: this is slightly different to git OID computation since the oid of an LFS file is usually the git-SHA1 of the
+ pointer file content (not the actual file content). However, using the SHA256 is enough to detect changes
+ and more convenient client-side.
+ """
+ if self._upload_mode is None:
+ return None
+ elif self._upload_mode == "lfs":
+ return self.upload_info.sha256.hex()
+ else:
+ # Regular file => compute sha1
+ # => no need to read by chunk since the file is guaranteed to be <=5MB.
+ with self.as_file() as file:
+ return sha.git_hash(file.read())
+
+
+def _validate_path_in_repo(path_in_repo: str) -> str:
+ # Validate `path_in_repo` value to prevent a server-side issue
+ if path_in_repo.startswith("/"):
+ path_in_repo = path_in_repo[1:]
+ if path_in_repo == "." or path_in_repo == ".." or path_in_repo.startswith("../"):
+ raise ValueError(f"Invalid `path_in_repo` in CommitOperation: '{path_in_repo}'")
+ if path_in_repo.startswith("./"):
+ path_in_repo = path_in_repo[2:]
+ for forbidden in FORBIDDEN_FOLDERS:
+ if any(part == forbidden for part in path_in_repo.split("/")):
+ raise ValueError(
+ f"Invalid `path_in_repo` in CommitOperation: cannot update files under a '{forbidden}/' folder (path:"
+ f" '{path_in_repo}')."
+ )
+ return path_in_repo
+
+
+CommitOperation = Union[CommitOperationAdd, CommitOperationCopy, CommitOperationDelete]
+
+
+def _warn_on_overwriting_operations(operations: List[CommitOperation]) -> None:
+ """
+ Warn user when a list of operations is expected to overwrite itself in a single
+ commit.
+
+ Rules:
+ - If a filepath is updated by multiple `CommitOperationAdd` operations, a warning
+ message is triggered.
+ - If a filepath is updated at least once by a `CommitOperationAdd` and then deleted
+ by a `CommitOperationDelete`, a warning is triggered.
+ - If a `CommitOperationDelete` deletes a filepath that is then updated by a
+ `CommitOperationAdd`, no warning is triggered. This is usually useless (no need to
+ delete before upload) but can happen if a user deletes an entire folder and then
+ add new files to it.
+ """
+ nb_additions_per_path: Dict[str, int] = defaultdict(int)
+ for operation in operations:
+ path_in_repo = operation.path_in_repo
+ if isinstance(operation, CommitOperationAdd):
+ if nb_additions_per_path[path_in_repo] > 0:
+ warnings.warn(
+ "About to update multiple times the same file in the same commit:"
+ f" '{path_in_repo}'. This can cause undesired inconsistencies in"
+ " your repo."
+ )
+ nb_additions_per_path[path_in_repo] += 1
+ for parent in PurePosixPath(path_in_repo).parents:
+ # Also keep track of number of updated files per folder
+ # => warns if deleting a folder overwrite some contained files
+ nb_additions_per_path[str(parent)] += 1
+ if isinstance(operation, CommitOperationDelete):
+ if nb_additions_per_path[str(PurePosixPath(path_in_repo))] > 0:
+ if operation.is_folder:
+ warnings.warn(
+ "About to delete a folder containing files that have just been"
+ f" updated within the same commit: '{path_in_repo}'. This can"
+ " cause undesired inconsistencies in your repo."
+ )
+ else:
+ warnings.warn(
+ "About to delete a file that have just been updated within the"
+ f" same commit: '{path_in_repo}'. This can cause undesired"
+ " inconsistencies in your repo."
+ )
+
+
+@validate_hf_hub_args
+def _upload_lfs_files(
+ *,
+ additions: List[CommitOperationAdd],
+ repo_type: str,
+ repo_id: str,
+ headers: Dict[str, str],
+ endpoint: Optional[str] = None,
+ num_threads: int = 5,
+ revision: Optional[str] = None,
+):
+ """
+ Uploads the content of `additions` to the Hub using the large file storage protocol.
+
+ Relevant external documentation:
+ - LFS Batch API: https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md
+
+ Args:
+ additions (`List` of `CommitOperationAdd`):
+ The files to be uploaded
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ headers (`Dict[str, str]`):
+ Headers to use for the request, including authorization headers and user agent.
+ num_threads (`int`, *optional*):
+ The number of concurrent threads to use when uploading. Defaults to 5.
+ revision (`str`, *optional*):
+ The git revision to upload to.
+
+ Raises:
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If an upload failed for any reason
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the server returns malformed responses
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ If the LFS batch endpoint returned an HTTP error.
+ """
+ # Step 1: retrieve upload instructions from the LFS batch endpoint.
+ # Upload instructions are retrieved by chunk of 256 files to avoid reaching
+ # the payload limit.
+ batch_actions: List[Dict] = []
+ for chunk in chunk_iterable(additions, chunk_size=256):
+ batch_actions_chunk, batch_errors_chunk = post_lfs_batch_info(
+ upload_infos=[op.upload_info for op in chunk],
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=endpoint,
+ headers=headers,
+ token=None, # already passed in 'headers'
+ )
+
+ # If at least 1 error, we do not retrieve information for other chunks
+ if batch_errors_chunk:
+ message = "\n".join(
+ [
+ f"Encountered error for file with OID {err.get('oid')}: `{err.get('error', {}).get('message')}"
+ for err in batch_errors_chunk
+ ]
+ )
+ raise ValueError(f"LFS batch endpoint returned errors:\n{message}")
+
+ batch_actions += batch_actions_chunk
+ oid2addop = {add_op.upload_info.sha256.hex(): add_op for add_op in additions}
+
+ # Step 2: ignore files that have already been uploaded
+ filtered_actions = []
+ for action in batch_actions:
+ if action.get("actions") is None:
+ logger.debug(
+ f"Content of file {oid2addop[action['oid']].path_in_repo} is already"
+ " present upstream - skipping upload."
+ )
+ else:
+ filtered_actions.append(action)
+
+ if len(filtered_actions) == 0:
+ logger.debug("No LFS files to upload.")
+ return
+
+ # Step 3: upload files concurrently according to these instructions
+ def _wrapped_lfs_upload(batch_action) -> None:
+ try:
+ operation = oid2addop[batch_action["oid"]]
+ lfs_upload(operation=operation, lfs_batch_action=batch_action, headers=headers, endpoint=endpoint)
+ except Exception as exc:
+ raise RuntimeError(f"Error while uploading '{operation.path_in_repo}' to the Hub.") from exc
+
+ if constants.HF_HUB_ENABLE_HF_TRANSFER:
+ logger.debug(f"Uploading {len(filtered_actions)} LFS files to the Hub using `hf_transfer`.")
+ for action in hf_tqdm(filtered_actions, name="huggingface_hub.lfs_upload"):
+ _wrapped_lfs_upload(action)
+ elif len(filtered_actions) == 1:
+ logger.debug("Uploading 1 LFS file to the Hub")
+ _wrapped_lfs_upload(filtered_actions[0])
+ else:
+ logger.debug(
+ f"Uploading {len(filtered_actions)} LFS files to the Hub using up to {num_threads} threads concurrently"
+ )
+ thread_map(
+ _wrapped_lfs_upload,
+ filtered_actions,
+ desc=f"Upload {len(filtered_actions)} LFS files",
+ max_workers=num_threads,
+ tqdm_class=hf_tqdm,
+ )
+
+
+def _validate_preupload_info(preupload_info: dict):
+ files = preupload_info.get("files")
+ if not isinstance(files, list):
+ raise ValueError("preupload_info is improperly formatted")
+ for file_info in files:
+ if not (
+ isinstance(file_info, dict)
+ and isinstance(file_info.get("path"), str)
+ and isinstance(file_info.get("uploadMode"), str)
+ and (file_info["uploadMode"] in ("lfs", "regular"))
+ ):
+ raise ValueError("preupload_info is improperly formatted:")
+ return preupload_info
+
+
+@validate_hf_hub_args
+def _fetch_upload_modes(
+ additions: Iterable[CommitOperationAdd],
+ repo_type: str,
+ repo_id: str,
+ headers: Dict[str, str],
+ revision: str,
+ endpoint: Optional[str] = None,
+ create_pr: bool = False,
+ gitignore_content: Optional[str] = None,
+) -> None:
+ """
+ Requests the Hub "preupload" endpoint to determine whether each input file should be uploaded as a regular git blob
+ or as git LFS blob. Input `additions` are mutated in-place with the upload mode.
+
+ Args:
+ additions (`Iterable` of :class:`CommitOperationAdd`):
+ Iterable of :class:`CommitOperationAdd` describing the files to
+ upload to the Hub.
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ headers (`Dict[str, str]`):
+ Headers to use for the request, including authorization headers and user agent.
+ revision (`str`):
+ The git revision to upload the files to. Can be any valid git revision.
+ gitignore_content (`str`, *optional*):
+ The content of the `.gitignore` file to know which files should be ignored. The order of priority
+ is to first check if `gitignore_content` is passed, then check if the `.gitignore` file is present
+ in the list of files to commit and finally default to the `.gitignore` file already hosted on the Hub
+ (if any).
+ Raises:
+ [`~utils.HfHubHTTPError`]
+ If the Hub API returned an error.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the Hub API response is improperly formatted.
+ """
+ endpoint = endpoint if endpoint is not None else constants.ENDPOINT
+
+ # Fetch upload mode (LFS or regular) chunk by chunk.
+ upload_modes: Dict[str, UploadMode] = {}
+ should_ignore_info: Dict[str, bool] = {}
+ oid_info: Dict[str, Optional[str]] = {}
+
+ for chunk in chunk_iterable(additions, 256):
+ payload: Dict = {
+ "files": [
+ {
+ "path": op.path_in_repo,
+ "sample": base64.b64encode(op.upload_info.sample).decode("ascii"),
+ "size": op.upload_info.size,
+ }
+ for op in chunk
+ ]
+ }
+ if gitignore_content is not None:
+ payload["gitIgnore"] = gitignore_content
+
+ resp = get_session().post(
+ f"{endpoint}/api/{repo_type}s/{repo_id}/preupload/{revision}",
+ json=payload,
+ headers=headers,
+ params={"create_pr": "1"} if create_pr else None,
+ )
+ hf_raise_for_status(resp)
+ preupload_info = _validate_preupload_info(resp.json())
+ upload_modes.update(**{file["path"]: file["uploadMode"] for file in preupload_info["files"]})
+ should_ignore_info.update(**{file["path"]: file["shouldIgnore"] for file in preupload_info["files"]})
+ oid_info.update(**{file["path"]: file.get("oid") for file in preupload_info["files"]})
+
+ # Set upload mode for each addition operation
+ for addition in additions:
+ addition._upload_mode = upload_modes[addition.path_in_repo]
+ addition._should_ignore = should_ignore_info[addition.path_in_repo]
+ addition._remote_oid = oid_info[addition.path_in_repo]
+
+ # Empty files cannot be uploaded as LFS (S3 would fail with a 501 Not Implemented)
+ # => empty files are uploaded as "regular" to still allow users to commit them.
+ for addition in additions:
+ if addition.upload_info.size == 0:
+ addition._upload_mode = "regular"
+
+
+@validate_hf_hub_args
+def _fetch_files_to_copy(
+ copies: Iterable[CommitOperationCopy],
+ repo_type: str,
+ repo_id: str,
+ headers: Dict[str, str],
+ revision: str,
+ endpoint: Optional[str] = None,
+) -> Dict[Tuple[str, Optional[str]], Union["RepoFile", bytes]]:
+ """
+ Fetch information about the files to copy.
+
+ For LFS files, we only need their metadata (file size and sha256) while for regular files
+ we need to download the raw content from the Hub.
+
+ Args:
+ copies (`Iterable` of :class:`CommitOperationCopy`):
+ Iterable of :class:`CommitOperationCopy` describing the files to
+ copy on the Hub.
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ headers (`Dict[str, str]`):
+ Headers to use for the request, including authorization headers and user agent.
+ revision (`str`):
+ The git revision to upload the files to. Can be any valid git revision.
+
+ Returns: `Dict[Tuple[str, Optional[str]], Union[RepoFile, bytes]]]`
+ Key is the file path and revision of the file to copy.
+ Value is the raw content as bytes (for regular files) or the file information as a RepoFile (for LFS files).
+
+ Raises:
+ [`~utils.HfHubHTTPError`]
+ If the Hub API returned an error.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the Hub API response is improperly formatted.
+ """
+ from .hf_api import HfApi, RepoFolder
+
+ hf_api = HfApi(endpoint=endpoint, headers=headers)
+ files_to_copy: Dict[Tuple[str, Optional[str]], Union["RepoFile", bytes]] = {}
+ # Store (path, revision) -> oid mapping
+ oid_info: Dict[Tuple[str, Optional[str]], Optional[str]] = {}
+ # 1. Fetch OIDs for destination paths in batches.
+ dest_paths = [op.path_in_repo for op in copies]
+ for offset in range(0, len(dest_paths), FETCH_LFS_BATCH_SIZE):
+ dest_repo_files = hf_api.get_paths_info(
+ repo_id=repo_id,
+ paths=dest_paths[offset : offset + FETCH_LFS_BATCH_SIZE],
+ revision=revision,
+ repo_type=repo_type,
+ )
+ for file in dest_repo_files:
+ if not isinstance(file, RepoFolder):
+ oid_info[(file.path, revision)] = file.blob_id
+
+ # 2. Group by source revision and fetch source file info in batches.
+ for src_revision, operations in groupby(copies, key=lambda op: op.src_revision):
+ operations = list(operations) # type: ignore
+ src_paths = [op.src_path_in_repo for op in operations]
+ for offset in range(0, len(src_paths), FETCH_LFS_BATCH_SIZE):
+ src_repo_files = hf_api.get_paths_info(
+ repo_id=repo_id,
+ paths=src_paths[offset : offset + FETCH_LFS_BATCH_SIZE],
+ revision=src_revision or revision,
+ repo_type=repo_type,
+ )
+
+ for src_repo_file in src_repo_files:
+ if isinstance(src_repo_file, RepoFolder):
+ raise NotImplementedError("Copying a folder is not implemented.")
+ oid_info[(src_repo_file.path, src_revision)] = src_repo_file.blob_id
+ # If it's an LFS file, store the RepoFile object. Otherwise, download raw bytes.
+ if src_repo_file.lfs:
+ files_to_copy[(src_repo_file.path, src_revision)] = src_repo_file
+ else:
+ # TODO: (optimization) download regular files to copy concurrently
+ url = hf_hub_url(
+ endpoint=endpoint,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ revision=src_revision or revision,
+ filename=src_repo_file.path,
+ )
+ response = get_session().get(url, headers=headers)
+ hf_raise_for_status(response)
+ files_to_copy[(src_repo_file.path, src_revision)] = response.content
+ # 3. Ensure all operations found a corresponding file in the Hub
+ # and track src/dest OIDs for each operation.
+ for operation in operations:
+ if (operation.src_path_in_repo, src_revision) not in files_to_copy:
+ raise EntryNotFoundError(
+ f"Cannot copy {operation.src_path_in_repo} at revision "
+ f"{src_revision or revision}: file is missing on repo."
+ )
+ operation._src_oid = oid_info.get((operation.src_path_in_repo, operation.src_revision))
+ operation._dest_oid = oid_info.get((operation.path_in_repo, revision))
+ return files_to_copy
+
+
+def _prepare_commit_payload(
+ operations: Iterable[CommitOperation],
+ files_to_copy: Dict[Tuple[str, Optional[str]], Union["RepoFile", bytes]],
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ parent_commit: Optional[str] = None,
+) -> Iterable[Dict[str, Any]]:
+ """
+ Builds the payload to POST to the `/commit` API of the Hub.
+
+ Payload is returned as an iterator so that it can be streamed as a ndjson in the
+ POST request.
+
+ For more information, see:
+ - https://github.com/huggingface/huggingface_hub/issues/1085#issuecomment-1265208073
+ - http://ndjson.org/
+ """
+ commit_description = commit_description if commit_description is not None else ""
+
+ # 1. Send a header item with the commit metadata
+ header_value = {"summary": commit_message, "description": commit_description}
+ if parent_commit is not None:
+ header_value["parentCommit"] = parent_commit
+ yield {"key": "header", "value": header_value}
+
+ nb_ignored_files = 0
+
+ # 2. Send operations, one per line
+ for operation in operations:
+ # Skip ignored files
+ if isinstance(operation, CommitOperationAdd) and operation._should_ignore:
+ logger.debug(f"Skipping file '{operation.path_in_repo}' in commit (ignored by gitignore file).")
+ nb_ignored_files += 1
+ continue
+
+ # 2.a. Case adding a regular file
+ if isinstance(operation, CommitOperationAdd) and operation._upload_mode == "regular":
+ yield {
+ "key": "file",
+ "value": {
+ "content": operation.b64content().decode(),
+ "path": operation.path_in_repo,
+ "encoding": "base64",
+ },
+ }
+ # 2.b. Case adding an LFS file
+ elif isinstance(operation, CommitOperationAdd) and operation._upload_mode == "lfs":
+ yield {
+ "key": "lfsFile",
+ "value": {
+ "path": operation.path_in_repo,
+ "algo": "sha256",
+ "oid": operation.upload_info.sha256.hex(),
+ "size": operation.upload_info.size,
+ },
+ }
+ # 2.c. Case deleting a file or folder
+ elif isinstance(operation, CommitOperationDelete):
+ yield {
+ "key": "deletedFolder" if operation.is_folder else "deletedFile",
+ "value": {"path": operation.path_in_repo},
+ }
+ # 2.d. Case copying a file or folder
+ elif isinstance(operation, CommitOperationCopy):
+ file_to_copy = files_to_copy[(operation.src_path_in_repo, operation.src_revision)]
+ if isinstance(file_to_copy, bytes):
+ yield {
+ "key": "file",
+ "value": {
+ "content": base64.b64encode(file_to_copy).decode(),
+ "path": operation.path_in_repo,
+ "encoding": "base64",
+ },
+ }
+ elif file_to_copy.lfs:
+ yield {
+ "key": "lfsFile",
+ "value": {
+ "path": operation.path_in_repo,
+ "algo": "sha256",
+ "oid": file_to_copy.lfs.sha256,
+ },
+ }
+ else:
+ raise ValueError(
+ "Malformed files_to_copy (should be raw file content as bytes or RepoFile objects with LFS info."
+ )
+ # 2.e. Never expected to happen
+ else:
+ raise ValueError(
+ f"Unknown operation to commit. Operation: {operation}. Upload mode:"
+ f" {getattr(operation, '_upload_mode', None)}"
+ )
+
+ if nb_ignored_files > 0:
+ logger.info(f"Skipped {nb_ignored_files} file(s) in commit (ignored by gitignore file).")
diff --git a/env/Lib/site-packages/huggingface_hub/_commit_scheduler.py b/env/Lib/site-packages/huggingface_hub/_commit_scheduler.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1f20339e7df2d17588623dc13bb3c6be6a46b53
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_commit_scheduler.py
@@ -0,0 +1,353 @@
+import atexit
+import logging
+import os
+import time
+from concurrent.futures import Future
+from dataclasses import dataclass
+from io import SEEK_END, SEEK_SET, BytesIO
+from pathlib import Path
+from threading import Lock, Thread
+from typing import Dict, List, Optional, Union
+
+from .hf_api import DEFAULT_IGNORE_PATTERNS, CommitInfo, CommitOperationAdd, HfApi
+from .utils import filter_repo_objects
+
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass(frozen=True)
+class _FileToUpload:
+ """Temporary dataclass to store info about files to upload. Not meant to be used directly."""
+
+ local_path: Path
+ path_in_repo: str
+ size_limit: int
+ last_modified: float
+
+
+class CommitScheduler:
+ """
+ Scheduler to upload a local folder to the Hub at regular intervals (e.g. push to hub every 5 minutes).
+
+ The recommended way to use the scheduler is to use it as a context manager. This ensures that the scheduler is
+ properly stopped and the last commit is triggered when the script ends. The scheduler can also be stopped manually
+ with the `stop` method. Checkout the [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#scheduled-uploads)
+ to learn more about how to use it.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to commit to.
+ folder_path (`str` or `Path`):
+ Path to the local folder to upload regularly.
+ every (`int` or `float`, *optional*):
+ The number of minutes between each commit. Defaults to 5 minutes.
+ path_in_repo (`str`, *optional*):
+ Relative path of the directory in the repo, for example: `"checkpoints/"`. Defaults to the root folder
+ of the repository.
+ repo_type (`str`, *optional*):
+ The type of the repo to commit to. Defaults to `model`.
+ revision (`str`, *optional*):
+ The revision of the repo to commit to. Defaults to `main`.
+ private (`bool`, *optional*):
+ Whether to make the repo private. If `None` (default), the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.
+ token (`str`, *optional*):
+ The token to use to commit to the repo. Defaults to the token saved on the machine.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are uploaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not uploaded.
+ squash_history (`bool`, *optional*):
+ Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
+ useful to avoid degraded performances on the repo when it grows too large.
+ hf_api (`HfApi`, *optional*):
+ The [`HfApi`] client to use to commit to the Hub. Can be set with custom settings (user agent, token,...).
+
+ Example:
+ ```py
+ >>> from pathlib import Path
+ >>> from huggingface_hub import CommitScheduler
+
+ # Scheduler uploads every 10 minutes
+ >>> csv_path = Path("watched_folder/data.csv")
+ >>> CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path=csv_path.parent, every=10)
+
+ >>> with csv_path.open("a") as f:
+ ... f.write("first line")
+
+ # Some time later (...)
+ >>> with csv_path.open("a") as f:
+ ... f.write("second line")
+ ```
+
+ Example using a context manager:
+ ```py
+ >>> from pathlib import Path
+ >>> from huggingface_hub import CommitScheduler
+
+ >>> with CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path="watched_folder", every=10) as scheduler:
+ ... csv_path = Path("watched_folder/data.csv")
+ ... with csv_path.open("a") as f:
+ ... f.write("first line")
+ ... (...)
+ ... with csv_path.open("a") as f:
+ ... f.write("second line")
+
+ # Scheduler is now stopped and last commit have been triggered
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ every: Union[int, float] = 5,
+ path_in_repo: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ private: Optional[bool] = None,
+ token: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ squash_history: bool = False,
+ hf_api: Optional["HfApi"] = None,
+ ) -> None:
+ self.api = hf_api or HfApi(token=token)
+
+ # Folder
+ self.folder_path = Path(folder_path).expanduser().resolve()
+ self.path_in_repo = path_in_repo or ""
+ self.allow_patterns = allow_patterns
+
+ if ignore_patterns is None:
+ ignore_patterns = []
+ elif isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+ self.ignore_patterns = ignore_patterns + DEFAULT_IGNORE_PATTERNS
+
+ if self.folder_path.is_file():
+ raise ValueError(f"'folder_path' must be a directory, not a file: '{self.folder_path}'.")
+ self.folder_path.mkdir(parents=True, exist_ok=True)
+
+ # Repository
+ repo_url = self.api.create_repo(repo_id=repo_id, private=private, repo_type=repo_type, exist_ok=True)
+ self.repo_id = repo_url.repo_id
+ self.repo_type = repo_type
+ self.revision = revision
+ self.token = token
+
+ # Keep track of already uploaded files
+ self.last_uploaded: Dict[Path, float] = {} # key is local path, value is timestamp
+
+ # Scheduler
+ if not every > 0:
+ raise ValueError(f"'every' must be a positive integer, not '{every}'.")
+ self.lock = Lock()
+ self.every = every
+ self.squash_history = squash_history
+
+ logger.info(f"Scheduled job to push '{self.folder_path}' to '{self.repo_id}' every {self.every} minutes.")
+ self._scheduler_thread = Thread(target=self._run_scheduler, daemon=True)
+ self._scheduler_thread.start()
+ atexit.register(self._push_to_hub)
+
+ self.__stopped = False
+
+ def stop(self) -> None:
+ """Stop the scheduler.
+
+ A stopped scheduler cannot be restarted. Mostly for tests purposes.
+ """
+ self.__stopped = True
+
+ def __enter__(self) -> "CommitScheduler":
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ # Upload last changes before exiting
+ self.trigger().result()
+ self.stop()
+ return
+
+ def _run_scheduler(self) -> None:
+ """Dumb thread waiting between each scheduled push to Hub."""
+ while True:
+ self.last_future = self.trigger()
+ time.sleep(self.every * 60)
+ if self.__stopped:
+ break
+
+ def trigger(self) -> Future:
+ """Trigger a `push_to_hub` and return a future.
+
+ This method is automatically called every `every` minutes. You can also call it manually to trigger a commit
+ immediately, without waiting for the next scheduled commit.
+ """
+ return self.api.run_as_future(self._push_to_hub)
+
+ def _push_to_hub(self) -> Optional[CommitInfo]:
+ if self.__stopped: # If stopped, already scheduled commits are ignored
+ return None
+
+ logger.info("(Background) scheduled commit triggered.")
+ try:
+ value = self.push_to_hub()
+ if self.squash_history:
+ logger.info("(Background) squashing repo history.")
+ self.api.super_squash_history(repo_id=self.repo_id, repo_type=self.repo_type, branch=self.revision)
+ return value
+ except Exception as e:
+ logger.error(f"Error while pushing to Hub: {e}") # Depending on the setup, error might be silenced
+ raise
+
+ def push_to_hub(self) -> Optional[CommitInfo]:
+ """
+ Push folder to the Hub and return the commit info.
+
+
+
+ This method is not meant to be called directly. It is run in the background by the scheduler, respecting a
+ queue mechanism to avoid concurrent commits. Making a direct call to the method might lead to concurrency
+ issues.
+
+
+
+ The default behavior of `push_to_hub` is to assume an append-only folder. It lists all files in the folder and
+ uploads only changed files. If no changes are found, the method returns without committing anything. If you want
+ to change this behavior, you can inherit from [`CommitScheduler`] and override this method. This can be useful
+ for example to compress data together in a single file before committing. For more details and examples, check
+ out our [integration guide](https://huggingface.co/docs/huggingface_hub/main/en/guides/upload#scheduled-uploads).
+ """
+ # Check files to upload (with lock)
+ with self.lock:
+ logger.debug("Listing files to upload for scheduled commit.")
+
+ # List files from folder (taken from `_prepare_upload_folder_additions`)
+ relpath_to_abspath = {
+ path.relative_to(self.folder_path).as_posix(): path
+ for path in sorted(self.folder_path.glob("**/*")) # sorted to be deterministic
+ if path.is_file()
+ }
+ prefix = f"{self.path_in_repo.strip('/')}/" if self.path_in_repo else ""
+
+ # Filter with pattern + filter out unchanged files + retrieve current file size
+ files_to_upload: List[_FileToUpload] = []
+ for relpath in filter_repo_objects(
+ relpath_to_abspath.keys(), allow_patterns=self.allow_patterns, ignore_patterns=self.ignore_patterns
+ ):
+ local_path = relpath_to_abspath[relpath]
+ stat = local_path.stat()
+ if self.last_uploaded.get(local_path) is None or self.last_uploaded[local_path] != stat.st_mtime:
+ files_to_upload.append(
+ _FileToUpload(
+ local_path=local_path,
+ path_in_repo=prefix + relpath,
+ size_limit=stat.st_size,
+ last_modified=stat.st_mtime,
+ )
+ )
+
+ # Return if nothing to upload
+ if len(files_to_upload) == 0:
+ logger.debug("Dropping schedule commit: no changed file to upload.")
+ return None
+
+ # Convert `_FileToUpload` as `CommitOperationAdd` (=> compute file shas + limit to file size)
+ logger.debug("Removing unchanged files since previous scheduled commit.")
+ add_operations = [
+ CommitOperationAdd(
+ # Cap the file to its current size, even if the user append data to it while a scheduled commit is happening
+ path_or_fileobj=PartialFileIO(file_to_upload.local_path, size_limit=file_to_upload.size_limit),
+ path_in_repo=file_to_upload.path_in_repo,
+ )
+ for file_to_upload in files_to_upload
+ ]
+
+ # Upload files (append mode expected - no need for lock)
+ logger.debug("Uploading files for scheduled commit.")
+ commit_info = self.api.create_commit(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ operations=add_operations,
+ commit_message="Scheduled Commit",
+ revision=self.revision,
+ )
+
+ # Successful commit: keep track of the latest "last_modified" for each file
+ for file in files_to_upload:
+ self.last_uploaded[file.local_path] = file.last_modified
+ return commit_info
+
+
+class PartialFileIO(BytesIO):
+ """A file-like object that reads only the first part of a file.
+
+ Useful to upload a file to the Hub when the user might still be appending data to it. Only the first part of the
+ file is uploaded (i.e. the part that was available when the filesystem was first scanned).
+
+ In practice, only used internally by the CommitScheduler to regularly push a folder to the Hub with minimal
+ disturbance for the user. The object is passed to `CommitOperationAdd`.
+
+ Only supports `read`, `tell` and `seek` methods.
+
+ Args:
+ file_path (`str` or `Path`):
+ Path to the file to read.
+ size_limit (`int`):
+ The maximum number of bytes to read from the file. If the file is larger than this, only the first part
+ will be read (and uploaded).
+ """
+
+ def __init__(self, file_path: Union[str, Path], size_limit: int) -> None:
+ self._file_path = Path(file_path)
+ self._file = self._file_path.open("rb")
+ self._size_limit = min(size_limit, os.fstat(self._file.fileno()).st_size)
+
+ def __del__(self) -> None:
+ self._file.close()
+ return super().__del__()
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __len__(self) -> int:
+ return self._size_limit
+
+ def __getattribute__(self, name: str):
+ if name.startswith("_") or name in ("read", "tell", "seek"): # only 3 public methods supported
+ return super().__getattribute__(name)
+ raise NotImplementedError(f"PartialFileIO does not support '{name}'.")
+
+ def tell(self) -> int:
+ """Return the current file position."""
+ return self._file.tell()
+
+ def seek(self, __offset: int, __whence: int = SEEK_SET) -> int:
+ """Change the stream position to the given offset.
+
+ Behavior is the same as a regular file, except that the position is capped to the size limit.
+ """
+ if __whence == SEEK_END:
+ # SEEK_END => set from the truncated end
+ __offset = len(self) + __offset
+ __whence = SEEK_SET
+
+ pos = self._file.seek(__offset, __whence)
+ if pos > self._size_limit:
+ return self._file.seek(self._size_limit)
+ return pos
+
+ def read(self, __size: Optional[int] = -1) -> bytes:
+ """Read at most `__size` bytes from the file.
+
+ Behavior is the same as a regular file, except that it is capped to the size limit.
+ """
+ current = self._file.tell()
+ if __size is None or __size < 0:
+ # Read until file limit
+ truncated_size = self._size_limit - current
+ else:
+ # Read until file limit or __size
+ truncated_size = min(__size, self._size_limit - current)
+ return self._file.read(truncated_size)
diff --git a/env/Lib/site-packages/huggingface_hub/_inference_endpoints.py b/env/Lib/site-packages/huggingface_hub/_inference_endpoints.py
new file mode 100644
index 0000000000000000000000000000000000000000..37733fef1b28872137916189072aa3814a4c2df9
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_inference_endpoints.py
@@ -0,0 +1,407 @@
+import time
+from dataclasses import dataclass, field
+from datetime import datetime
+from enum import Enum
+from typing import TYPE_CHECKING, Dict, Optional, Union
+
+from huggingface_hub.errors import InferenceEndpointError, InferenceEndpointTimeoutError
+
+from .inference._client import InferenceClient
+from .inference._generated._async_client import AsyncInferenceClient
+from .utils import get_session, logging, parse_datetime
+
+
+if TYPE_CHECKING:
+ from .hf_api import HfApi
+
+
+logger = logging.get_logger(__name__)
+
+
+class InferenceEndpointStatus(str, Enum):
+ PENDING = "pending"
+ INITIALIZING = "initializing"
+ UPDATING = "updating"
+ UPDATE_FAILED = "updateFailed"
+ RUNNING = "running"
+ PAUSED = "paused"
+ FAILED = "failed"
+ SCALED_TO_ZERO = "scaledToZero"
+
+
+class InferenceEndpointType(str, Enum):
+ PUBlIC = "public"
+ PROTECTED = "protected"
+ PRIVATE = "private"
+
+
+@dataclass
+class InferenceEndpoint:
+ """
+ Contains information about a deployed Inference Endpoint.
+
+ Args:
+ name (`str`):
+ The unique name of the Inference Endpoint.
+ namespace (`str`):
+ The namespace where the Inference Endpoint is located.
+ repository (`str`):
+ The name of the model repository deployed on this Inference Endpoint.
+ status ([`InferenceEndpointStatus`]):
+ The current status of the Inference Endpoint.
+ url (`str`, *optional*):
+ The URL of the Inference Endpoint, if available. Only a deployed Inference Endpoint will have a URL.
+ framework (`str`):
+ The machine learning framework used for the model.
+ revision (`str`):
+ The specific model revision deployed on the Inference Endpoint.
+ task (`str`):
+ The task associated with the deployed model.
+ created_at (`datetime.datetime`):
+ The timestamp when the Inference Endpoint was created.
+ updated_at (`datetime.datetime`):
+ The timestamp of the last update of the Inference Endpoint.
+ type ([`InferenceEndpointType`]):
+ The type of the Inference Endpoint (public, protected, private).
+ raw (`Dict`):
+ The raw dictionary data returned from the API.
+ token (`str` or `bool`, *optional*):
+ Authentication token for the Inference Endpoint, if set when requesting the API. Will default to the
+ locally saved token if not provided. Pass `token=False` if you don't want to send your token to the server.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import get_inference_endpoint
+ >>> endpoint = get_inference_endpoint("my-text-to-image")
+ >>> endpoint
+ InferenceEndpoint(name='my-text-to-image', ...)
+
+ # Get status
+ >>> endpoint.status
+ 'running'
+ >>> endpoint.url
+ 'https://my-text-to-image.region.vendor.endpoints.huggingface.cloud'
+
+ # Run inference
+ >>> endpoint.client.text_to_image(...)
+
+ # Pause endpoint to save $$$
+ >>> endpoint.pause()
+
+ # ...
+ # Resume and wait for deployment
+ >>> endpoint.resume()
+ >>> endpoint.wait()
+ >>> endpoint.client.text_to_image(...)
+ ```
+ """
+
+ # Field in __repr__
+ name: str = field(init=False)
+ namespace: str
+ repository: str = field(init=False)
+ status: InferenceEndpointStatus = field(init=False)
+ url: Optional[str] = field(init=False)
+
+ # Other fields
+ framework: str = field(repr=False, init=False)
+ revision: str = field(repr=False, init=False)
+ task: str = field(repr=False, init=False)
+ created_at: datetime = field(repr=False, init=False)
+ updated_at: datetime = field(repr=False, init=False)
+ type: InferenceEndpointType = field(repr=False, init=False)
+
+ # Raw dict from the API
+ raw: Dict = field(repr=False)
+
+ # Internal fields
+ _token: Union[str, bool, None] = field(repr=False, compare=False)
+ _api: "HfApi" = field(repr=False, compare=False)
+
+ @classmethod
+ def from_raw(
+ cls, raw: Dict, namespace: str, token: Union[str, bool, None] = None, api: Optional["HfApi"] = None
+ ) -> "InferenceEndpoint":
+ """Initialize object from raw dictionary."""
+ if api is None:
+ from .hf_api import HfApi
+
+ api = HfApi()
+ if token is None:
+ token = api.token
+
+ # All other fields are populated in __post_init__
+ return cls(raw=raw, namespace=namespace, _token=token, _api=api)
+
+ def __post_init__(self) -> None:
+ """Populate fields from raw dictionary."""
+ self._populate_from_raw()
+
+ @property
+ def client(self) -> InferenceClient:
+ """Returns a client to make predictions on this Inference Endpoint.
+
+ Returns:
+ [`InferenceClient`]: an inference client pointing to the deployed endpoint.
+
+ Raises:
+ [`InferenceEndpointError`]: If the Inference Endpoint is not yet deployed.
+ """
+ if self.url is None:
+ raise InferenceEndpointError(
+ "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
+ "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
+ )
+ return InferenceClient(
+ model=self.url,
+ token=self._token, # type: ignore[arg-type] # boolean token shouldn't be possible. In practice it's ok.
+ )
+
+ @property
+ def async_client(self) -> AsyncInferenceClient:
+ """Returns a client to make predictions on this Inference Endpoint.
+
+ Returns:
+ [`AsyncInferenceClient`]: an asyncio-compatible inference client pointing to the deployed endpoint.
+
+ Raises:
+ [`InferenceEndpointError`]: If the Inference Endpoint is not yet deployed.
+ """
+ if self.url is None:
+ raise InferenceEndpointError(
+ "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
+ "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
+ )
+ return AsyncInferenceClient(
+ model=self.url,
+ token=self._token, # type: ignore[arg-type] # boolean token shouldn't be possible. In practice it's ok.
+ )
+
+ def wait(self, timeout: Optional[int] = None, refresh_every: int = 5) -> "InferenceEndpoint":
+ """Wait for the Inference Endpoint to be deployed.
+
+ Information from the server will be fetched every 1s. If the Inference Endpoint is not deployed after `timeout`
+ seconds, a [`InferenceEndpointTimeoutError`] will be raised. The [`InferenceEndpoint`] will be mutated in place with the latest
+ data.
+
+ Args:
+ timeout (`int`, *optional*):
+ The maximum time to wait for the Inference Endpoint to be deployed, in seconds. If `None`, will wait
+ indefinitely.
+ refresh_every (`int`, *optional*):
+ The time to wait between each fetch of the Inference Endpoint status, in seconds. Defaults to 5s.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+
+ Raises:
+ [`InferenceEndpointError`]
+ If the Inference Endpoint ended up in a failed state.
+ [`InferenceEndpointTimeoutError`]
+ If the Inference Endpoint is not deployed after `timeout` seconds.
+ """
+ if timeout is not None and timeout < 0:
+ raise ValueError("`timeout` cannot be negative.")
+ if refresh_every <= 0:
+ raise ValueError("`refresh_every` must be positive.")
+
+ start = time.time()
+ while True:
+ if self.status == InferenceEndpointStatus.FAILED:
+ raise InferenceEndpointError(
+ f"Inference Endpoint {self.name} failed to deploy. Please check the logs for more information."
+ )
+ if self.status == InferenceEndpointStatus.UPDATE_FAILED:
+ raise InferenceEndpointError(
+ f"Inference Endpoint {self.name} failed to update. Please check the logs for more information."
+ )
+ if self.status == InferenceEndpointStatus.RUNNING and self.url is not None:
+ # Verify the endpoint is actually reachable
+ response = get_session().get(self.url, headers=self._api._build_hf_headers(token=self._token))
+ if response.status_code == 200:
+ logger.info("Inference Endpoint is ready to be used.")
+ return self
+
+ if timeout is not None:
+ if time.time() - start > timeout:
+ raise InferenceEndpointTimeoutError("Timeout while waiting for Inference Endpoint to be deployed.")
+ logger.info(f"Inference Endpoint is not deployed yet ({self.status}). Waiting {refresh_every}s...")
+ time.sleep(refresh_every)
+ self.fetch()
+
+ def fetch(self) -> "InferenceEndpoint":
+ """Fetch latest information about the Inference Endpoint.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.get_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def update(
+ self,
+ *,
+ # Compute update
+ accelerator: Optional[str] = None,
+ instance_size: Optional[str] = None,
+ instance_type: Optional[str] = None,
+ min_replica: Optional[int] = None,
+ max_replica: Optional[int] = None,
+ scale_to_zero_timeout: Optional[int] = None,
+ # Model update
+ repository: Optional[str] = None,
+ framework: Optional[str] = None,
+ revision: Optional[str] = None,
+ task: Optional[str] = None,
+ custom_image: Optional[Dict] = None,
+ secrets: Optional[Dict[str, str]] = None,
+ ) -> "InferenceEndpoint":
+ """Update the Inference Endpoint.
+
+ This method allows the update of either the compute configuration, the deployed model, or both. All arguments are
+ optional but at least one must be provided.
+
+ This is an alias for [`HfApi.update_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Args:
+ accelerator (`str`, *optional*):
+ The hardware accelerator to be used for inference (e.g. `"cpu"`).
+ instance_size (`str`, *optional*):
+ The size or type of the instance to be used for hosting the model (e.g. `"x4"`).
+ instance_type (`str`, *optional*):
+ The cloud instance type where the Inference Endpoint will be deployed (e.g. `"intel-icl"`).
+ min_replica (`int`, *optional*):
+ The minimum number of replicas (instances) to keep running for the Inference Endpoint.
+ max_replica (`int`, *optional*):
+ The maximum number of replicas (instances) to scale to for the Inference Endpoint.
+ scale_to_zero_timeout (`int`, *optional*):
+ The duration in minutes before an inactive endpoint is scaled to zero.
+
+ repository (`str`, *optional*):
+ The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
+ framework (`str`, *optional*):
+ The machine learning framework used for the model (e.g. `"custom"`).
+ revision (`str`, *optional*):
+ The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
+ task (`str`, *optional*):
+ The task on which to deploy the model (e.g. `"text-classification"`).
+ custom_image (`Dict`, *optional*):
+ A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an
+ Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples).
+ secrets (`Dict[str, str]`, *optional*):
+ Secret values to inject in the container environment.
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ # Make API call
+ obj = self._api.update_inference_endpoint(
+ name=self.name,
+ namespace=self.namespace,
+ accelerator=accelerator,
+ instance_size=instance_size,
+ instance_type=instance_type,
+ min_replica=min_replica,
+ max_replica=max_replica,
+ scale_to_zero_timeout=scale_to_zero_timeout,
+ repository=repository,
+ framework=framework,
+ revision=revision,
+ task=task,
+ custom_image=custom_image,
+ secrets=secrets,
+ token=self._token, # type: ignore [arg-type]
+ )
+
+ # Mutate current object
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def pause(self) -> "InferenceEndpoint":
+ """Pause the Inference Endpoint.
+
+ A paused Inference Endpoint will not be charged. It can be resumed at any time using [`InferenceEndpoint.resume`].
+ This is different than scaling the Inference Endpoint to zero with [`InferenceEndpoint.scale_to_zero`], which
+ would be automatically restarted when a request is made to it.
+
+ This is an alias for [`HfApi.pause_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.pause_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def resume(self, running_ok: bool = True) -> "InferenceEndpoint":
+ """Resume the Inference Endpoint.
+
+ This is an alias for [`HfApi.resume_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Args:
+ running_ok (`bool`, *optional*):
+ If `True`, the method will not raise an error if the Inference Endpoint is already running. Defaults to
+ `True`.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.resume_inference_endpoint(
+ name=self.name, namespace=self.namespace, running_ok=running_ok, token=self._token
+ ) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def scale_to_zero(self) -> "InferenceEndpoint":
+ """Scale Inference Endpoint to zero.
+
+ An Inference Endpoint scaled to zero will not be charged. It will be resume on the next request to it, with a
+ cold start delay. This is different than pausing the Inference Endpoint with [`InferenceEndpoint.pause`], which
+ would require a manual resume with [`InferenceEndpoint.resume`].
+
+ This is an alias for [`HfApi.scale_to_zero_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.scale_to_zero_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def delete(self) -> None:
+ """Delete the Inference Endpoint.
+
+ This operation is not reversible. If you don't want to be charged for an Inference Endpoint, it is preferable
+ to pause it with [`InferenceEndpoint.pause`] or scale it to zero with [`InferenceEndpoint.scale_to_zero`].
+
+ This is an alias for [`HfApi.delete_inference_endpoint`].
+ """
+ self._api.delete_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+
+ def _populate_from_raw(self) -> None:
+ """Populate fields from raw dictionary.
+
+ Called in __post_init__ + each time the Inference Endpoint is updated.
+ """
+ # Repr fields
+ self.name = self.raw["name"]
+ self.repository = self.raw["model"]["repository"]
+ self.status = self.raw["status"]["state"]
+ self.url = self.raw["status"].get("url")
+
+ # Other fields
+ self.framework = self.raw["model"]["framework"]
+ self.revision = self.raw["model"]["revision"]
+ self.task = self.raw["model"]["task"]
+ self.created_at = parse_datetime(self.raw["status"]["createdAt"])
+ self.updated_at = parse_datetime(self.raw["status"]["updatedAt"])
+ self.type = self.raw["type"]
diff --git a/env/Lib/site-packages/huggingface_hub/_local_folder.py b/env/Lib/site-packages/huggingface_hub/_local_folder.py
new file mode 100644
index 0000000000000000000000000000000000000000..264d51c58e890bcfda1903ebb5fb22cc68f9516d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_local_folder.py
@@ -0,0 +1,432 @@
+# coding=utf-8
+# Copyright 2024-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle the `../.cache/huggingface` folder in local directories.
+
+First discussed in https://github.com/huggingface/huggingface_hub/issues/1738 to store
+download metadata when downloading files from the hub to a local directory (without
+using the cache).
+
+./.cache/huggingface folder structure:
+[4.0K] data
+├── [4.0K] .cache
+│ └── [4.0K] huggingface
+│ └── [4.0K] download
+│ ├── [ 16] file.parquet.metadata
+│ ├── [ 16] file.txt.metadata
+│ └── [4.0K] folder
+│ └── [ 16] file.parquet.metadata
+│
+├── [6.5G] file.parquet
+├── [1.5K] file.txt
+└── [4.0K] folder
+ └── [ 16] file.parquet
+
+
+Download metadata file structure:
+```
+# file.txt.metadata
+11c5a3d5811f50298f278a704980280950aedb10
+a16a55fda99d2f2e7b69cce5cf93ff4ad3049930
+1712656091.123
+
+# file.parquet.metadata
+11c5a3d5811f50298f278a704980280950aedb10
+7c5d3f4b8b76583b422fcb9189ad6c89d5d97a094541ce8932dce3ecabde1421
+1712656091.123
+}
+```
+"""
+
+import base64
+import hashlib
+import logging
+import os
+import time
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Optional
+
+from .utils import WeakFileLock
+
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class LocalDownloadFilePaths:
+ """
+ Paths to the files related to a download process in a local dir.
+
+ Returned by [`get_local_download_paths`].
+
+ Attributes:
+ file_path (`Path`):
+ Path where the file will be saved.
+ lock_path (`Path`):
+ Path to the lock file used to ensure atomicity when reading/writing metadata.
+ metadata_path (`Path`):
+ Path to the metadata file.
+ """
+
+ file_path: Path
+ lock_path: Path
+ metadata_path: Path
+
+ def incomplete_path(self, etag: str) -> Path:
+ """Return the path where a file will be temporarily downloaded before being moved to `file_path`."""
+ return self.metadata_path.parent / f"{_short_hash(self.metadata_path.name)}.{etag}.incomplete"
+
+
+@dataclass(frozen=True)
+class LocalUploadFilePaths:
+ """
+ Paths to the files related to an upload process in a local dir.
+
+ Returned by [`get_local_upload_paths`].
+
+ Attributes:
+ path_in_repo (`str`):
+ Path of the file in the repo.
+ file_path (`Path`):
+ Path where the file will be saved.
+ lock_path (`Path`):
+ Path to the lock file used to ensure atomicity when reading/writing metadata.
+ metadata_path (`Path`):
+ Path to the metadata file.
+ """
+
+ path_in_repo: str
+ file_path: Path
+ lock_path: Path
+ metadata_path: Path
+
+
+@dataclass
+class LocalDownloadFileMetadata:
+ """
+ Metadata about a file in the local directory related to a download process.
+
+ Attributes:
+ filename (`str`):
+ Path of the file in the repo.
+ commit_hash (`str`):
+ Commit hash of the file in the repo.
+ etag (`str`):
+ ETag of the file in the repo. Used to check if the file has changed.
+ For LFS files, this is the sha256 of the file. For regular files, it corresponds to the git hash.
+ timestamp (`int`):
+ Unix timestamp of when the metadata was saved i.e. when the metadata was accurate.
+ """
+
+ filename: str
+ commit_hash: str
+ etag: str
+ timestamp: float
+
+
+@dataclass
+class LocalUploadFileMetadata:
+ """
+ Metadata about a file in the local directory related to an upload process.
+ """
+
+ size: int
+
+ # Default values correspond to "we don't know yet"
+ timestamp: Optional[float] = None
+ should_ignore: Optional[bool] = None
+ sha256: Optional[str] = None
+ upload_mode: Optional[str] = None
+ is_uploaded: bool = False
+ is_committed: bool = False
+
+ def save(self, paths: LocalUploadFilePaths) -> None:
+ """Save the metadata to disk."""
+ with WeakFileLock(paths.lock_path):
+ with paths.metadata_path.open("w") as f:
+ new_timestamp = time.time()
+ f.write(str(new_timestamp) + "\n")
+
+ f.write(str(self.size)) # never None
+ f.write("\n")
+
+ if self.should_ignore is not None:
+ f.write(str(int(self.should_ignore)))
+ f.write("\n")
+
+ if self.sha256 is not None:
+ f.write(self.sha256)
+ f.write("\n")
+
+ if self.upload_mode is not None:
+ f.write(self.upload_mode)
+ f.write("\n")
+
+ f.write(str(int(self.is_uploaded)) + "\n")
+ f.write(str(int(self.is_committed)) + "\n")
+
+ self.timestamp = new_timestamp
+
+
+def get_local_download_paths(local_dir: Path, filename: str) -> LocalDownloadFilePaths:
+ """Compute paths to the files related to a download process.
+
+ Folders containing the paths are all guaranteed to exist.
+
+ Args:
+ local_dir (`Path`):
+ Path to the local directory in which files are downloaded.
+ filename (`str`):
+ Path of the file in the repo.
+
+ Return:
+ [`LocalDownloadFilePaths`]: the paths to the files (file_path, lock_path, metadata_path, incomplete_path).
+ """
+ # filename is the path in the Hub repository (separated by '/')
+ # make sure to have a cross platform transcription
+ sanitized_filename = os.path.join(*filename.split("/"))
+ if os.name == "nt":
+ if sanitized_filename.startswith("..\\") or "\\..\\" in sanitized_filename:
+ raise ValueError(
+ f"Invalid filename: cannot handle filename '{sanitized_filename}' on Windows. Please ask the repository"
+ " owner to rename this file."
+ )
+ file_path = local_dir / sanitized_filename
+ metadata_path = _huggingface_dir(local_dir) / "download" / f"{sanitized_filename}.metadata"
+ lock_path = metadata_path.with_suffix(".lock")
+
+ # Some Windows versions do not allow for paths longer than 255 characters.
+ # In this case, we must specify it as an extended path by using the "\\?\" prefix
+ if os.name == "nt":
+ if not str(local_dir).startswith("\\\\?\\") and len(os.path.abspath(lock_path)) > 255:
+ file_path = Path("\\\\?\\" + os.path.abspath(file_path))
+ lock_path = Path("\\\\?\\" + os.path.abspath(lock_path))
+ metadata_path = Path("\\\\?\\" + os.path.abspath(metadata_path))
+
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+ metadata_path.parent.mkdir(parents=True, exist_ok=True)
+ return LocalDownloadFilePaths(file_path=file_path, lock_path=lock_path, metadata_path=metadata_path)
+
+
+def get_local_upload_paths(local_dir: Path, filename: str) -> LocalUploadFilePaths:
+ """Compute paths to the files related to an upload process.
+
+ Folders containing the paths are all guaranteed to exist.
+
+ Args:
+ local_dir (`Path`):
+ Path to the local directory that is uploaded.
+ filename (`str`):
+ Path of the file in the repo.
+
+ Return:
+ [`LocalUploadFilePaths`]: the paths to the files (file_path, lock_path, metadata_path).
+ """
+ # filename is the path in the Hub repository (separated by '/')
+ # make sure to have a cross platform transcription
+ sanitized_filename = os.path.join(*filename.split("/"))
+ if os.name == "nt":
+ if sanitized_filename.startswith("..\\") or "\\..\\" in sanitized_filename:
+ raise ValueError(
+ f"Invalid filename: cannot handle filename '{sanitized_filename}' on Windows. Please ask the repository"
+ " owner to rename this file."
+ )
+ file_path = local_dir / sanitized_filename
+ metadata_path = _huggingface_dir(local_dir) / "upload" / f"{sanitized_filename}.metadata"
+ lock_path = metadata_path.with_suffix(".lock")
+
+ # Some Windows versions do not allow for paths longer than 255 characters.
+ # In this case, we must specify it as an extended path by using the "\\?\" prefix
+ if os.name == "nt":
+ if not str(local_dir).startswith("\\\\?\\") and len(os.path.abspath(lock_path)) > 255:
+ file_path = Path("\\\\?\\" + os.path.abspath(file_path))
+ lock_path = Path("\\\\?\\" + os.path.abspath(lock_path))
+ metadata_path = Path("\\\\?\\" + os.path.abspath(metadata_path))
+
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+ metadata_path.parent.mkdir(parents=True, exist_ok=True)
+ return LocalUploadFilePaths(
+ path_in_repo=filename, file_path=file_path, lock_path=lock_path, metadata_path=metadata_path
+ )
+
+
+def read_download_metadata(local_dir: Path, filename: str) -> Optional[LocalDownloadFileMetadata]:
+ """Read metadata about a file in the local directory related to a download process.
+
+ Args:
+ local_dir (`Path`):
+ Path to the local directory in which files are downloaded.
+ filename (`str`):
+ Path of the file in the repo.
+
+ Return:
+ `[LocalDownloadFileMetadata]` or `None`: the metadata if it exists, `None` otherwise.
+ """
+ paths = get_local_download_paths(local_dir, filename)
+ with WeakFileLock(paths.lock_path):
+ if paths.metadata_path.exists():
+ try:
+ with paths.metadata_path.open() as f:
+ commit_hash = f.readline().strip()
+ etag = f.readline().strip()
+ timestamp = float(f.readline().strip())
+ metadata = LocalDownloadFileMetadata(
+ filename=filename,
+ commit_hash=commit_hash,
+ etag=etag,
+ timestamp=timestamp,
+ )
+ except Exception as e:
+ # remove the metadata file if it is corrupted / not the right format
+ logger.warning(
+ f"Invalid metadata file {paths.metadata_path}: {e}. Removing it from disk and continue."
+ )
+ try:
+ paths.metadata_path.unlink()
+ except Exception as e:
+ logger.warning(f"Could not remove corrupted metadata file {paths.metadata_path}: {e}")
+
+ try:
+ # check if the file exists and hasn't been modified since the metadata was saved
+ stat = paths.file_path.stat()
+ if (
+ stat.st_mtime - 1 <= metadata.timestamp
+ ): # allow 1s difference as stat.st_mtime might not be precise
+ return metadata
+ logger.info(f"Ignored metadata for '{filename}' (outdated). Will re-compute hash.")
+ except FileNotFoundError:
+ # file does not exist => metadata is outdated
+ return None
+ return None
+
+
+def read_upload_metadata(local_dir: Path, filename: str) -> LocalUploadFileMetadata:
+ """Read metadata about a file in the local directory related to an upload process.
+
+ TODO: factorize logic with `read_download_metadata`.
+
+ Args:
+ local_dir (`Path`):
+ Path to the local directory in which files are downloaded.
+ filename (`str`):
+ Path of the file in the repo.
+
+ Return:
+ `[LocalUploadFileMetadata]` or `None`: the metadata if it exists, `None` otherwise.
+ """
+ paths = get_local_upload_paths(local_dir, filename)
+ with WeakFileLock(paths.lock_path):
+ if paths.metadata_path.exists():
+ try:
+ with paths.metadata_path.open() as f:
+ timestamp = float(f.readline().strip())
+
+ size = int(f.readline().strip()) # never None
+
+ _should_ignore = f.readline().strip()
+ should_ignore = None if _should_ignore == "" else bool(int(_should_ignore))
+
+ _sha256 = f.readline().strip()
+ sha256 = None if _sha256 == "" else _sha256
+
+ _upload_mode = f.readline().strip()
+ upload_mode = None if _upload_mode == "" else _upload_mode
+ if upload_mode not in (None, "regular", "lfs"):
+ raise ValueError(f"Invalid upload mode in metadata {paths.path_in_repo}: {upload_mode}")
+
+ is_uploaded = bool(int(f.readline().strip()))
+ is_committed = bool(int(f.readline().strip()))
+
+ metadata = LocalUploadFileMetadata(
+ timestamp=timestamp,
+ size=size,
+ should_ignore=should_ignore,
+ sha256=sha256,
+ upload_mode=upload_mode,
+ is_uploaded=is_uploaded,
+ is_committed=is_committed,
+ )
+ except Exception as e:
+ # remove the metadata file if it is corrupted / not the right format
+ logger.warning(
+ f"Invalid metadata file {paths.metadata_path}: {e}. Removing it from disk and continue."
+ )
+ try:
+ paths.metadata_path.unlink()
+ except Exception as e:
+ logger.warning(f"Could not remove corrupted metadata file {paths.metadata_path}: {e}")
+
+ # TODO: can we do better?
+ if (
+ metadata.timestamp is not None
+ and metadata.is_uploaded # file was uploaded
+ and not metadata.is_committed # but not committed
+ and time.time() - metadata.timestamp > 20 * 3600 # and it's been more than 20 hours
+ ): # => we consider it as garbage-collected by S3
+ metadata.is_uploaded = False
+
+ # check if the file exists and hasn't been modified since the metadata was saved
+ try:
+ if metadata.timestamp is not None and paths.file_path.stat().st_mtime <= metadata.timestamp:
+ return metadata
+ logger.info(f"Ignored metadata for '{filename}' (outdated). Will re-compute hash.")
+ except FileNotFoundError:
+ # file does not exist => metadata is outdated
+ pass
+
+ # empty metadata => we don't know anything expect its size
+ return LocalUploadFileMetadata(size=paths.file_path.stat().st_size)
+
+
+def write_download_metadata(local_dir: Path, filename: str, commit_hash: str, etag: str) -> None:
+ """Write metadata about a file in the local directory related to a download process.
+
+ Args:
+ local_dir (`Path`):
+ Path to the local directory in which files are downloaded.
+ """
+ paths = get_local_download_paths(local_dir, filename)
+ with WeakFileLock(paths.lock_path):
+ with paths.metadata_path.open("w") as f:
+ f.write(f"{commit_hash}\n{etag}\n{time.time()}\n")
+
+
+def _huggingface_dir(local_dir: Path) -> Path:
+ """Return the path to the `.cache/huggingface` directory in a local directory."""
+ # Wrap in lru_cache to avoid overwriting the .gitignore file if called multiple times
+ path = local_dir / ".cache" / "huggingface"
+ path.mkdir(exist_ok=True, parents=True)
+
+ # Create a .gitignore file in the .cache/huggingface directory if it doesn't exist
+ # Should be thread-safe enough like this.
+ gitignore = path / ".gitignore"
+ gitignore_lock = path / ".gitignore.lock"
+ if not gitignore.exists():
+ try:
+ with WeakFileLock(gitignore_lock, timeout=0.1):
+ gitignore.write_text("*")
+ except IndexError:
+ pass
+ except OSError: # TimeoutError, FileNotFoundError, PermissionError, etc.
+ pass
+ try:
+ gitignore_lock.unlink()
+ except OSError:
+ pass
+ return path
+
+
+def _short_hash(filename: str) -> str:
+ return base64.urlsafe_b64encode(hashlib.sha1(filename.encode()).digest()).decode()
diff --git a/env/Lib/site-packages/huggingface_hub/_login.py b/env/Lib/site-packages/huggingface_hub/_login.py
new file mode 100644
index 0000000000000000000000000000000000000000..b14702201d45bebde47f95a5bc7fc85c9e93c84b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_login.py
@@ -0,0 +1,520 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains methods to log in to the Hub."""
+
+import os
+import subprocess
+from getpass import getpass
+from pathlib import Path
+from typing import Optional
+
+from . import constants
+from .commands._cli_utils import ANSI
+from .utils import (
+ capture_output,
+ get_token,
+ is_google_colab,
+ is_notebook,
+ list_credential_helpers,
+ logging,
+ run_subprocess,
+ set_git_credential,
+ unset_git_credential,
+)
+from .utils._auth import (
+ _get_token_by_name,
+ _get_token_from_environment,
+ _get_token_from_file,
+ _get_token_from_google_colab,
+ _save_stored_tokens,
+ _save_token,
+ get_stored_tokens,
+)
+from .utils._deprecation import _deprecate_arguments, _deprecate_positional_args
+
+
+logger = logging.get_logger(__name__)
+
+_HF_LOGO_ASCII = """
+ _| _| _| _| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _|_|_|_| _|_| _|_|_| _|_|_|_|
+ _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
+ _|_|_|_| _| _| _| _|_| _| _|_| _| _| _| _| _| _|_| _|_|_| _|_|_|_| _| _|_|_|
+ _| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
+ _| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|
+"""
+
+
+@_deprecate_arguments(
+ version="1.0",
+ deprecated_args="write_permission",
+ custom_message="Fine-grained tokens added complexity to the permissions, making it irrelevant to check if a token has 'write' access.",
+)
+@_deprecate_positional_args(version="1.0")
+def login(
+ token: Optional[str] = None,
+ *,
+ add_to_git_credential: bool = False,
+ new_session: bool = True,
+ write_permission: bool = False,
+) -> None:
+ """Login the machine to access the Hub.
+
+ The `token` is persisted in cache and set as a git credential. Once done, the machine
+ is logged in and the access token will be available across all `huggingface_hub`
+ components. If `token` is not provided, it will be prompted to the user either with
+ a widget (in a notebook) or via the terminal.
+
+ To log in from outside of a script, one can also use `huggingface-cli login` which is
+ a cli command that wraps [`login`].
+
+
+
+ [`login`] is a drop-in replacement method for [`notebook_login`] as it wraps and
+ extends its capabilities.
+
+
+
+
+
+ When the token is not passed, [`login`] will automatically detect if the script runs
+ in a notebook or not. However, this detection might not be accurate due to the
+ variety of notebooks that exists nowadays. If that is the case, you can always force
+ the UI by using [`notebook_login`] or [`interpreter_login`].
+
+
+
+ Args:
+ token (`str`, *optional*):
+ User access token to generate from https://huggingface.co/settings/token.
+ add_to_git_credential (`bool`, defaults to `False`):
+ If `True`, token will be set as git credential. If no git credential helper
+ is configured, a warning will be displayed to the user. If `token` is `None`,
+ the value of `add_to_git_credential` is ignored and will be prompted again
+ to the end user.
+ new_session (`bool`, defaults to `True`):
+ If `True`, will request a token even if one is already saved on the machine.
+ write_permission (`bool`):
+ Ignored and deprecated argument.
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If an organization token is passed. Only personal account tokens are valid
+ to log in.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If token is invalid.
+ [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ If running in a notebook but `ipywidgets` is not installed.
+ """
+ if token is not None:
+ if not add_to_git_credential:
+ logger.info(
+ "The token has not been saved to the git credentials helper. Pass "
+ "`add_to_git_credential=True` in this function directly or "
+ "`--add-to-git-credential` if using via `huggingface-cli` if "
+ "you want to set the git credential as well."
+ )
+ _login(token, add_to_git_credential=add_to_git_credential)
+ elif is_notebook():
+ notebook_login(new_session=new_session)
+ else:
+ interpreter_login(new_session=new_session)
+
+
+def logout(token_name: Optional[str] = None) -> None:
+ """Logout the machine from the Hub.
+
+ Token is deleted from the machine and removed from git credential.
+
+ Args:
+ token_name (`str`, *optional*):
+ Name of the access token to logout from. If `None`, will logout from all saved access tokens.
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):
+ If the access token name is not found.
+ """
+ if get_token() is None and not get_stored_tokens(): # No active token and no saved access tokens
+ logger.warning("Not logged in!")
+ return
+ if not token_name:
+ # Delete all saved access tokens and token
+ for file_path in (constants.HF_TOKEN_PATH, constants.HF_STORED_TOKENS_PATH):
+ try:
+ Path(file_path).unlink()
+ except FileNotFoundError:
+ pass
+ logger.info("Successfully logged out from all access tokens.")
+ else:
+ _logout_from_token(token_name)
+ logger.info(f"Successfully logged out from access token: {token_name}.")
+
+ unset_git_credential()
+
+ # Check if still logged in
+ if _get_token_from_google_colab() is not None:
+ raise EnvironmentError(
+ "You are automatically logged in using a Google Colab secret.\n"
+ "To log out, you must unset the `HF_TOKEN` secret in your Colab settings."
+ )
+ if _get_token_from_environment() is not None:
+ raise EnvironmentError(
+ "Token has been deleted from your machine but you are still logged in.\n"
+ "To log out, you must clear out both `HF_TOKEN` and `HUGGING_FACE_HUB_TOKEN` environment variables."
+ )
+
+
+def auth_switch(token_name: str, add_to_git_credential: bool = False) -> None:
+ """Switch to a different access token.
+
+ Args:
+ token_name (`str`):
+ Name of the access token to switch to.
+ add_to_git_credential (`bool`, defaults to `False`):
+ If `True`, token will be set as git credential. If no git credential helper
+ is configured, a warning will be displayed to the user. If `token` is `None`,
+ the value of `add_to_git_credential` is ignored and will be prompted again
+ to the end user.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):
+ If the access token name is not found.
+ """
+ token = _get_token_by_name(token_name)
+ if not token:
+ raise ValueError(f"Access token {token_name} not found in {constants.HF_STORED_TOKENS_PATH}")
+ # Write token to HF_TOKEN_PATH
+ _set_active_token(token_name, add_to_git_credential)
+ logger.info(f"The current active token is: {token_name}")
+ token_from_environment = _get_token_from_environment()
+ if token_from_environment is not None and token_from_environment != token:
+ logger.warning(
+ "The environment variable `HF_TOKEN` is set and will override the access token you've just switched to."
+ )
+
+
+def auth_list() -> None:
+ """List all stored access tokens."""
+ tokens = get_stored_tokens()
+
+ if not tokens:
+ logger.info("No access tokens found.")
+ return
+ # Find current token
+ current_token = get_token()
+ current_token_name = None
+ for token_name in tokens:
+ if tokens.get(token_name) == current_token:
+ current_token_name = token_name
+ # Print header
+ max_offset = max(len("token"), max(len(token) for token in tokens)) + 2
+ print(f" {{:<{max_offset}}}| {{:<15}}".format("name", "token"))
+ print("-" * (max_offset + 2) + "|" + "-" * 15)
+
+ # Print saved access tokens
+ for token_name in tokens:
+ token = tokens.get(token_name, "")
+ masked_token = f"{token[:3]}****{token[-4:]}" if token != "" else token
+ is_current = "*" if token == current_token else " "
+
+ print(f"{is_current} {{:<{max_offset}}}| {{:<15}}".format(token_name, masked_token))
+
+ if _get_token_from_environment():
+ logger.warning(
+ "\nNote: Environment variable `HF_TOKEN` is set and is the current active token independently from the stored tokens listed above."
+ )
+ elif current_token_name is None:
+ logger.warning(
+ "\nNote: No active token is set and no environment variable `HF_TOKEN` is found. Use `huggingface-cli login` to log in."
+ )
+
+
+###
+# Interpreter-based login (text)
+###
+
+
+@_deprecate_arguments(
+ version="1.0",
+ deprecated_args="write_permission",
+ custom_message="Fine-grained tokens added complexity to the permissions, making it irrelevant to check if a token has 'write' access.",
+)
+@_deprecate_positional_args(version="1.0")
+def interpreter_login(*, new_session: bool = True, write_permission: bool = False) -> None:
+ """
+ Displays a prompt to log in to the HF website and store the token.
+
+ This is equivalent to [`login`] without passing a token when not run in a notebook.
+ [`interpreter_login`] is useful if you want to force the use of the terminal prompt
+ instead of a notebook widget.
+
+ For more details, see [`login`].
+
+ Args:
+ new_session (`bool`, defaults to `True`):
+ If `True`, will request a token even if one is already saved on the machine.
+ write_permission (`bool`):
+ Ignored and deprecated argument.
+ """
+ if not new_session and get_token() is not None:
+ logger.info("User is already logged in.")
+ return
+
+ from .commands.delete_cache import _ask_for_confirmation_no_tui
+
+ print(_HF_LOGO_ASCII)
+ if get_token() is not None:
+ logger.info(
+ " A token is already saved on your machine. Run `huggingface-cli"
+ " whoami` to get more information or `huggingface-cli logout` if you want"
+ " to log out."
+ )
+ logger.info(" Setting a new token will erase the existing one.")
+
+ logger.info(
+ " To log in, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens ."
+ )
+ if os.name == "nt":
+ logger.info("Token can be pasted using 'Right-Click'.")
+ token = getpass("Enter your token (input will not be visible): ")
+ add_to_git_credential = _ask_for_confirmation_no_tui("Add token as git credential?")
+
+ _login(token=token, add_to_git_credential=add_to_git_credential)
+
+
+###
+# Notebook-based login (widget)
+###
+
+NOTEBOOK_LOGIN_PASSWORD_HTML = """
Immediately click login after typing your password or
+it might be stored in plain text in this notebook file.
"""
+
+
+NOTEBOOK_LOGIN_TOKEN_HTML_START = """
Copy a token from your Hugging Face
+tokens page and paste it below. Immediately click login after copying
+your token or it might be stored in plain text in this notebook file.
"""
+
+
+NOTEBOOK_LOGIN_TOKEN_HTML_END = """
+Pro Tip: If you don't already have one, you can create a dedicated
+'notebooks' token with 'write' access, that you can then easily reuse for all
+notebooks. """
+
+
+@_deprecate_arguments(
+ version="1.0",
+ deprecated_args="write_permission",
+ custom_message="Fine-grained tokens added complexity to the permissions, making it irrelevant to check if a token has 'write' access.",
+)
+@_deprecate_positional_args(version="1.0")
+def notebook_login(*, new_session: bool = True, write_permission: bool = False) -> None:
+ """
+ Displays a widget to log in to the HF website and store the token.
+
+ This is equivalent to [`login`] without passing a token when run in a notebook.
+ [`notebook_login`] is useful if you want to force the use of the notebook widget
+ instead of a prompt in the terminal.
+
+ For more details, see [`login`].
+
+ Args:
+ new_session (`bool`, defaults to `True`):
+ If `True`, will request a token even if one is already saved on the machine.
+ write_permission (`bool`):
+ Ignored and deprecated argument.
+ """
+ try:
+ import ipywidgets.widgets as widgets # type: ignore
+ from IPython.display import display # type: ignore
+ except ImportError:
+ raise ImportError(
+ "The `notebook_login` function can only be used in a notebook (Jupyter or"
+ " Colab) and you need the `ipywidgets` module: `pip install ipywidgets`."
+ )
+ if not new_session and get_token() is not None:
+ logger.info("User is already logged in.")
+ return
+
+ box_layout = widgets.Layout(display="flex", flex_flow="column", align_items="center", width="50%")
+
+ token_widget = widgets.Password(description="Token:")
+ git_checkbox_widget = widgets.Checkbox(value=True, description="Add token as git credential?")
+ token_finish_button = widgets.Button(description="Login")
+
+ login_token_widget = widgets.VBox(
+ [
+ widgets.HTML(NOTEBOOK_LOGIN_TOKEN_HTML_START),
+ token_widget,
+ git_checkbox_widget,
+ token_finish_button,
+ widgets.HTML(NOTEBOOK_LOGIN_TOKEN_HTML_END),
+ ],
+ layout=box_layout,
+ )
+ display(login_token_widget)
+
+ # On click events
+ def login_token_event(t):
+ """Event handler for the login button."""
+ token = token_widget.value
+ add_to_git_credential = git_checkbox_widget.value
+ # Erase token and clear value to make sure it's not saved in the notebook.
+ token_widget.value = ""
+ # Hide inputs
+ login_token_widget.children = [widgets.Label("Connecting...")]
+ try:
+ with capture_output() as captured:
+ _login(token, add_to_git_credential=add_to_git_credential)
+ message = captured.getvalue()
+ except Exception as error:
+ message = str(error)
+ # Print result (success message or error)
+ login_token_widget.children = [widgets.Label(line) for line in message.split("\n") if line.strip()]
+
+ token_finish_button.on_click(login_token_event)
+
+
+###
+# Login private helpers
+###
+
+
+def _login(
+ token: str,
+ add_to_git_credential: bool,
+) -> None:
+ from .hf_api import whoami # avoid circular import
+
+ if token.startswith("api_org"):
+ raise ValueError("You must use your personal account token, not an organization token.")
+
+ token_info = whoami(token)
+ permission = token_info["auth"]["accessToken"]["role"]
+ logger.info(f"Token is valid (permission: {permission}).")
+
+ token_name = token_info["auth"]["accessToken"]["displayName"]
+ # Store token locally
+ _save_token(token=token, token_name=token_name)
+ # Set active token
+ _set_active_token(token_name=token_name, add_to_git_credential=add_to_git_credential)
+ logger.info("Login successful.")
+ if _get_token_from_environment():
+ logger.warning(
+ "Note: Environment variable`HF_TOKEN` is set and is the current active token independently from the token you've just configured."
+ )
+ else:
+ logger.info(f"The current active token is: `{token_name}`")
+
+
+def _logout_from_token(token_name: str) -> None:
+ """Logout from a specific access token.
+
+ Args:
+ token_name (`str`):
+ The name of the access token to logout from.
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError):
+ If the access token name is not found.
+ """
+ stored_tokens = get_stored_tokens()
+ # If there is no access tokens saved or the access token name is not found, do nothing
+ if not stored_tokens or token_name not in stored_tokens:
+ return
+
+ token = stored_tokens.pop(token_name)
+ _save_stored_tokens(stored_tokens)
+
+ if token == _get_token_from_file():
+ logger.warning(f"Active token '{token_name}' has been deleted.")
+ Path(constants.HF_TOKEN_PATH).unlink(missing_ok=True)
+
+
+def _set_active_token(
+ token_name: str,
+ add_to_git_credential: bool,
+) -> None:
+ """Set the active access token.
+
+ Args:
+ token_name (`str`):
+ The name of the token to set as active.
+ """
+ token = _get_token_by_name(token_name)
+ if not token:
+ raise ValueError(f"Token {token_name} not found in {constants.HF_STORED_TOKENS_PATH}")
+ if add_to_git_credential:
+ if _is_git_credential_helper_configured():
+ set_git_credential(token)
+ logger.info(
+ "Your token has been saved in your configured git credential helpers"
+ + f" ({','.join(list_credential_helpers())})."
+ )
+ else:
+ logger.warning("Token has not been saved to git credential helper.")
+ # Write token to HF_TOKEN_PATH
+ path = Path(constants.HF_TOKEN_PATH)
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(token)
+ logger.info(f"Your token has been saved to {constants.HF_TOKEN_PATH}")
+
+
+def _is_git_credential_helper_configured() -> bool:
+ """Check if a git credential helper is configured.
+
+ Warns user if not the case (except for Google Colab where "store" is set by default
+ by `huggingface_hub`).
+ """
+ helpers = list_credential_helpers()
+ if len(helpers) > 0:
+ return True # Do not warn: at least 1 helper is set
+
+ # Only in Google Colab to avoid the warning message
+ # See https://github.com/huggingface/huggingface_hub/issues/1043#issuecomment-1247010710
+ if is_google_colab():
+ _set_store_as_git_credential_helper_globally()
+ return True # Do not warn: "store" is used by default in Google Colab
+
+ # Otherwise, warn user
+ print(
+ ANSI.red(
+ "Cannot authenticate through git-credential as no helper is defined on your"
+ " machine.\nYou might have to re-authenticate when pushing to the Hugging"
+ " Face Hub.\nRun the following command in your terminal in case you want to"
+ " set the 'store' credential helper as default.\n\ngit config --global"
+ " credential.helper store\n\nRead"
+ " https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage for more"
+ " details."
+ )
+ )
+ return False
+
+
+def _set_store_as_git_credential_helper_globally() -> None:
+ """Set globally the credential.helper to `store`.
+
+ To be used only in Google Colab as we assume the user doesn't care about the git
+ credential config. It is the only particular case where we don't want to display the
+ warning message in [`notebook_login()`].
+
+ Related:
+ - https://github.com/huggingface/huggingface_hub/issues/1043
+ - https://github.com/huggingface/huggingface_hub/issues/1051
+ - https://git-scm.com/docs/git-credential-store
+ """
+ try:
+ run_subprocess("git config --global credential.helper store")
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
diff --git a/env/Lib/site-packages/huggingface_hub/_snapshot_download.py b/env/Lib/site-packages/huggingface_hub/_snapshot_download.py
new file mode 100644
index 0000000000000000000000000000000000000000..b928dd346664121eb0c3e033fc39af136cbcdcc8
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_snapshot_download.py
@@ -0,0 +1,307 @@
+import os
+from pathlib import Path
+from typing import Dict, List, Literal, Optional, Union
+
+import requests
+from tqdm.auto import tqdm as base_tqdm
+from tqdm.contrib.concurrent import thread_map
+
+from . import constants
+from .errors import GatedRepoError, LocalEntryNotFoundError, RepositoryNotFoundError, RevisionNotFoundError
+from .file_download import REGEX_COMMIT_HASH, hf_hub_download, repo_folder_name
+from .hf_api import DatasetInfo, HfApi, ModelInfo, SpaceInfo
+from .utils import OfflineModeIsEnabled, filter_repo_objects, logging, validate_hf_hub_args
+from .utils import tqdm as hf_tqdm
+
+
+logger = logging.get_logger(__name__)
+
+
+@validate_hf_hub_args
+def snapshot_download(
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Optional[Union[Dict, str]] = None,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
+ force_download: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ local_files_only: bool = False,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ max_workers: int = 8,
+ tqdm_class: Optional[base_tqdm] = None,
+ headers: Optional[Dict[str, str]] = None,
+ endpoint: Optional[str] = None,
+ # Deprecated args
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ resume_download: Optional[bool] = None,
+) -> str:
+ """Download repo files.
+
+ Download a whole snapshot of a repo's files at the specified revision. This is useful when you want all files from
+ a repo, because you don't know which ones you will need a priori. All files are nested inside a folder in order
+ to keep their actual filename relative to that folder. You can also filter which files to download using
+ `allow_patterns` and `ignore_patterns`.
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this
+ option, the `cache_dir` will not be used and a `.cache/huggingface/` folder will be created at the root of `local_dir`
+ to store some metadata related to the downloaded files. While this mechanism is not as robust as the main
+ cache-system, it's optimized for regularly pulling the latest version of a repository.
+
+ An alternative would be to clone the repo but this requires git and git-lfs to be installed and properly
+ configured. It is also not possible to filter which files to download when cloning a repository using git.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded files will be placed under this directory.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ user_agent (`str`, `dict`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in the local cache.
+ token (`str`, `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If a string, it's used as the authentication token.
+ headers (`dict`, *optional*):
+ Additional headers to include in the request. Those headers take precedence over the others.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are downloaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not downloaded.
+ max_workers (`int`, *optional*):
+ Number of concurrent threads to download files (1 thread = 1 file download).
+ Defaults to 8.
+ tqdm_class (`tqdm`, *optional*):
+ If provided, overwrites the default behavior for the progress bar. Passed
+ argument must inherit from `tqdm.auto.tqdm` or at least mimic its behavior.
+ Note that the `tqdm_class` is not passed to each individual download.
+ Defaults to the custom HF progress bar that can be disabled by setting
+ `HF_HUB_DISABLE_PROGRESS_BARS` environment variable.
+
+ Returns:
+ `str`: folder path of the repo snapshot.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `token=True` and the token cannot be found.
+ [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) if
+ ETag cannot be determined.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid.
+ """
+ if cache_dir is None:
+ cache_dir = constants.HF_HUB_CACHE
+ if revision is None:
+ revision = constants.DEFAULT_REVISION
+ if isinstance(cache_dir, Path):
+ cache_dir = str(cache_dir)
+
+ if repo_type is None:
+ repo_type = "model"
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type: {repo_type}. Accepted repo types are: {str(constants.REPO_TYPES)}")
+
+ storage_folder = os.path.join(cache_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type))
+
+ repo_info: Union[ModelInfo, DatasetInfo, SpaceInfo, None] = None
+ api_call_error: Optional[Exception] = None
+ if not local_files_only:
+ # try/except logic to handle different errors => taken from `hf_hub_download`
+ try:
+ # if we have internet connection we want to list files to download
+ api = HfApi(
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ endpoint=endpoint,
+ headers=headers,
+ )
+ repo_info = api.repo_info(repo_id=repo_id, repo_type=repo_type, revision=revision, token=token)
+ except (requests.exceptions.SSLError, requests.exceptions.ProxyError):
+ # Actually raise for those subclasses of ConnectionError
+ raise
+ except (
+ requests.exceptions.ConnectionError,
+ requests.exceptions.Timeout,
+ OfflineModeIsEnabled,
+ ) as error:
+ # Internet connection is down
+ # => will try to use local files only
+ api_call_error = error
+ pass
+ except RevisionNotFoundError:
+ # The repo was found but the revision doesn't exist on the Hub (never existed or got deleted)
+ raise
+ except requests.HTTPError as error:
+ # Multiple reasons for an http error:
+ # - Repository is private and invalid/missing token sent
+ # - Repository is gated and invalid/missing token sent
+ # - Hub is down (error 500 or 504)
+ # => let's switch to 'local_files_only=True' to check if the files are already cached.
+ # (if it's not the case, the error will be re-raised)
+ api_call_error = error
+ pass
+
+ # At this stage, if `repo_info` is None it means either:
+ # - internet connection is down
+ # - internet connection is deactivated (local_files_only=True or HF_HUB_OFFLINE=True)
+ # - repo is private/gated and invalid/missing token sent
+ # - Hub is down
+ # => let's look if we can find the appropriate folder in the cache:
+ # - if the specified revision is a commit hash, look inside "snapshots".
+ # - f the specified revision is a branch or tag, look inside "refs".
+ # => if local_dir is not None, we will return the path to the local folder if it exists.
+ if repo_info is None:
+ # Try to get which commit hash corresponds to the specified revision
+ commit_hash = None
+ if REGEX_COMMIT_HASH.match(revision):
+ commit_hash = revision
+ else:
+ ref_path = os.path.join(storage_folder, "refs", revision)
+ if os.path.exists(ref_path):
+ # retrieve commit_hash from refs file
+ with open(ref_path) as f:
+ commit_hash = f.read()
+
+ # Try to locate snapshot folder for this commit hash
+ if commit_hash is not None:
+ snapshot_folder = os.path.join(storage_folder, "snapshots", commit_hash)
+ if os.path.exists(snapshot_folder):
+ # Snapshot folder exists => let's return it
+ # (but we can't check if all the files are actually there)
+ return snapshot_folder
+ # If local_dir is not None, return it if it exists and is not empty
+ if local_dir is not None:
+ local_dir = Path(local_dir)
+ if local_dir.is_dir() and any(local_dir.iterdir()):
+ logger.warning(
+ f"Returning existing local_dir `{local_dir}` as remote repo cannot be accessed in `snapshot_download` ({api_call_error})."
+ )
+ return str(local_dir.resolve())
+ # If we couldn't find the appropriate folder on disk, raise an error.
+ if local_files_only:
+ raise LocalEntryNotFoundError(
+ "Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and "
+ "outgoing traffic has been disabled. To enable repo look-ups and downloads online, pass "
+ "'local_files_only=False' as input."
+ )
+ elif isinstance(api_call_error, OfflineModeIsEnabled):
+ raise LocalEntryNotFoundError(
+ "Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and "
+ "outgoing traffic has been disabled. To enable repo look-ups and downloads online, set "
+ "'HF_HUB_OFFLINE=0' as environment variable."
+ ) from api_call_error
+ elif isinstance(api_call_error, RepositoryNotFoundError) or isinstance(api_call_error, GatedRepoError):
+ # Repo not found => let's raise the actual error
+ raise api_call_error
+ else:
+ # Otherwise: most likely a connection issue or Hub downtime => let's warn the user
+ raise LocalEntryNotFoundError(
+ "An error happened while trying to locate the files on the Hub and we cannot find the appropriate"
+ " snapshot folder for the specified revision on the local disk. Please check your internet connection"
+ " and try again."
+ ) from api_call_error
+
+ # At this stage, internet connection is up and running
+ # => let's download the files!
+ assert repo_info.sha is not None, "Repo info returned from server must have a revision sha."
+ assert repo_info.siblings is not None, "Repo info returned from server must have a siblings list."
+ filtered_repo_files = list(
+ filter_repo_objects(
+ items=[f.rfilename for f in repo_info.siblings],
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ )
+ )
+ commit_hash = repo_info.sha
+ snapshot_folder = os.path.join(storage_folder, "snapshots", commit_hash)
+ # if passed revision is not identical to commit_hash
+ # then revision has to be a branch name or tag name.
+ # In that case store a ref.
+ if revision != commit_hash:
+ ref_path = os.path.join(storage_folder, "refs", revision)
+ try:
+ os.makedirs(os.path.dirname(ref_path), exist_ok=True)
+ with open(ref_path, "w") as f:
+ f.write(commit_hash)
+ except OSError as e:
+ logger.warning(f"Ignored error while writing commit hash to {ref_path}: {e}.")
+
+ # we pass the commit_hash to hf_hub_download
+ # so no network call happens if we already
+ # have the file locally.
+ def _inner_hf_hub_download(repo_file: str):
+ return hf_hub_download(
+ repo_id,
+ filename=repo_file,
+ repo_type=repo_type,
+ revision=commit_hash,
+ endpoint=endpoint,
+ cache_dir=cache_dir,
+ local_dir=local_dir,
+ local_dir_use_symlinks=local_dir_use_symlinks,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ force_download=force_download,
+ token=token,
+ headers=headers,
+ )
+
+ if constants.HF_HUB_ENABLE_HF_TRANSFER:
+ # when using hf_transfer we don't want extra parallelism
+ # from the one hf_transfer provides
+ for file in filtered_repo_files:
+ _inner_hf_hub_download(file)
+ else:
+ thread_map(
+ _inner_hf_hub_download,
+ filtered_repo_files,
+ desc=f"Fetching {len(filtered_repo_files)} files",
+ max_workers=max_workers,
+ # User can use its own tqdm class or the default one from `huggingface_hub.utils`
+ tqdm_class=tqdm_class or hf_tqdm,
+ )
+
+ if local_dir is not None:
+ return str(os.path.realpath(local_dir))
+ return snapshot_folder
diff --git a/env/Lib/site-packages/huggingface_hub/_space_api.py b/env/Lib/site-packages/huggingface_hub/_space_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..51d14b1f6d7f9d5ccc1d185805f52d28c90ad495
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_space_api.py
@@ -0,0 +1,160 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from dataclasses import dataclass
+from datetime import datetime
+from enum import Enum
+from typing import Dict, Optional
+
+from huggingface_hub.utils import parse_datetime
+
+
+class SpaceStage(str, Enum):
+ """
+ Enumeration of possible stage of a Space on the Hub.
+
+ Value can be compared to a string:
+ ```py
+ assert SpaceStage.BUILDING == "BUILDING"
+ ```
+
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L61 (private url).
+ """
+
+ # Copied from moon-landing > server > repo_types > SpaceInfo.ts (private repo)
+ NO_APP_FILE = "NO_APP_FILE"
+ CONFIG_ERROR = "CONFIG_ERROR"
+ BUILDING = "BUILDING"
+ BUILD_ERROR = "BUILD_ERROR"
+ RUNNING = "RUNNING"
+ RUNNING_BUILDING = "RUNNING_BUILDING"
+ RUNTIME_ERROR = "RUNTIME_ERROR"
+ DELETING = "DELETING"
+ STOPPED = "STOPPED"
+ PAUSED = "PAUSED"
+
+
+class SpaceHardware(str, Enum):
+ """
+ Enumeration of hardwares available to run your Space on the Hub.
+
+ Value can be compared to a string:
+ ```py
+ assert SpaceHardware.CPU_BASIC == "cpu-basic"
+ ```
+
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L73 (private url).
+ """
+
+ CPU_BASIC = "cpu-basic"
+ CPU_UPGRADE = "cpu-upgrade"
+ T4_SMALL = "t4-small"
+ T4_MEDIUM = "t4-medium"
+ L4X1 = "l4x1"
+ L4X4 = "l4x4"
+ ZERO_A10G = "zero-a10g"
+ A10G_SMALL = "a10g-small"
+ A10G_LARGE = "a10g-large"
+ A10G_LARGEX2 = "a10g-largex2"
+ A10G_LARGEX4 = "a10g-largex4"
+ A100_LARGE = "a100-large"
+ V5E_1X1 = "v5e-1x1"
+ V5E_2X2 = "v5e-2x2"
+ V5E_2X4 = "v5e-2x4"
+
+
+class SpaceStorage(str, Enum):
+ """
+ Enumeration of persistent storage available for your Space on the Hub.
+
+ Value can be compared to a string:
+ ```py
+ assert SpaceStorage.SMALL == "small"
+ ```
+
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceHardwareFlavor.ts#L24 (private url).
+ """
+
+ SMALL = "small"
+ MEDIUM = "medium"
+ LARGE = "large"
+
+
+@dataclass
+class SpaceRuntime:
+ """
+ Contains information about the current runtime of a Space.
+
+ Args:
+ stage (`str`):
+ Current stage of the space. Example: RUNNING.
+ hardware (`str` or `None`):
+ Current hardware of the space. Example: "cpu-basic". Can be `None` if Space
+ is `BUILDING` for the first time.
+ requested_hardware (`str` or `None`):
+ Requested hardware. Can be different than `hardware` especially if the request
+ has just been made. Example: "t4-medium". Can be `None` if no hardware has
+ been requested yet.
+ sleep_time (`int` or `None`):
+ Number of seconds the Space will be kept alive after the last request. By default (if value is `None`), the
+ Space will never go to sleep if it's running on an upgraded hardware, while it will go to sleep after 48
+ hours on a free 'cpu-basic' hardware. For more details, see https://huggingface.co/docs/hub/spaces-gpus#sleep-time.
+ raw (`dict`):
+ Raw response from the server. Contains more information about the Space
+ runtime like number of replicas, number of cpu, memory size,...
+ """
+
+ stage: SpaceStage
+ hardware: Optional[SpaceHardware]
+ requested_hardware: Optional[SpaceHardware]
+ sleep_time: Optional[int]
+ storage: Optional[SpaceStorage]
+ raw: Dict
+
+ def __init__(self, data: Dict) -> None:
+ self.stage = data["stage"]
+ self.hardware = data.get("hardware", {}).get("current")
+ self.requested_hardware = data.get("hardware", {}).get("requested")
+ self.sleep_time = data.get("gcTimeout")
+ self.storage = data.get("storage")
+ self.raw = data
+
+
+@dataclass
+class SpaceVariable:
+ """
+ Contains information about the current variables of a Space.
+
+ Args:
+ key (`str`):
+ Variable key. Example: `"MODEL_REPO_ID"`
+ value (`str`):
+ Variable value. Example: `"the_model_repo_id"`.
+ description (`str` or None):
+ Description of the variable. Example: `"Model Repo ID of the implemented model"`.
+ updatedAt (`datetime` or None):
+ datetime of the last update of the variable (if the variable has been updated at least once).
+ """
+
+ key: str
+ value: str
+ description: Optional[str]
+ updated_at: Optional[datetime]
+
+ def __init__(self, key: str, values: Dict) -> None:
+ self.key = key
+ self.value = values["value"]
+ self.description = values.get("description")
+ updated_at = values.get("updatedAt")
+ self.updated_at = parse_datetime(updated_at) if updated_at is not None else None
diff --git a/env/Lib/site-packages/huggingface_hub/_tensorboard_logger.py b/env/Lib/site-packages/huggingface_hub/_tensorboard_logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e910972463d3e6bc8b8796c95fde5696d999952
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_tensorboard_logger.py
@@ -0,0 +1,194 @@
+# Copyright 2023 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a logger to push training logs to the Hub, using Tensorboard."""
+
+from pathlib import Path
+from typing import TYPE_CHECKING, List, Optional, Union
+
+from ._commit_scheduler import CommitScheduler
+from .errors import EntryNotFoundError
+from .repocard import ModelCard
+from .utils import experimental
+
+
+# Depending on user's setup, SummaryWriter can come either from 'tensorboardX'
+# or from 'torch.utils.tensorboard'. Both are compatible so let's try to load
+# from either of them.
+try:
+ from tensorboardX import SummaryWriter
+
+ is_summary_writer_available = True
+
+except ImportError:
+ try:
+ from torch.utils.tensorboard import SummaryWriter
+
+ is_summary_writer_available = False
+ except ImportError:
+ # Dummy class to avoid failing at import. Will raise on instance creation.
+ SummaryWriter = object
+ is_summary_writer_available = False
+
+if TYPE_CHECKING:
+ from tensorboardX import SummaryWriter
+
+
+class HFSummaryWriter(SummaryWriter):
+ """
+ Wrapper around the tensorboard's `SummaryWriter` to push training logs to the Hub.
+
+ Data is logged locally and then pushed to the Hub asynchronously. Pushing data to the Hub is done in a separate
+ thread to avoid blocking the training script. In particular, if the upload fails for any reason (e.g. a connection
+ issue), the main script will not be interrupted. Data is automatically pushed to the Hub every `commit_every`
+ minutes (default to every 5 minutes).
+
+
+
+ `HFSummaryWriter` is experimental. Its API is subject to change in the future without prior notice.
+
+
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to which the logs will be pushed.
+ logdir (`str`, *optional*):
+ The directory where the logs will be written. If not specified, a local directory will be created by the
+ underlying `SummaryWriter` object.
+ commit_every (`int` or `float`, *optional*):
+ The frequency (in minutes) at which the logs will be pushed to the Hub. Defaults to 5 minutes.
+ squash_history (`bool`, *optional*):
+ Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
+ useful to avoid degraded performances on the repo when it grows too large.
+ repo_type (`str`, *optional*):
+ The type of the repo to which the logs will be pushed. Defaults to "model".
+ repo_revision (`str`, *optional*):
+ The revision of the repo to which the logs will be pushed. Defaults to "main".
+ repo_private (`bool`, *optional*):
+ Whether to make the repo private. If `None` (default), the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.
+ path_in_repo (`str`, *optional*):
+ The path to the folder in the repo where the logs will be pushed. Defaults to "tensorboard/".
+ repo_allow_patterns (`List[str]` or `str`, *optional*):
+ A list of patterns to include in the upload. Defaults to `"*.tfevents.*"`. Check out the
+ [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-folder) for more details.
+ repo_ignore_patterns (`List[str]` or `str`, *optional*):
+ A list of patterns to exclude in the upload. Check out the
+ [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-folder) for more details.
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token. See https://huggingface.co/settings/token for more
+ details
+ kwargs:
+ Additional keyword arguments passed to `SummaryWriter`.
+
+ Examples:
+ ```diff
+ # Taken from https://pytorch.org/docs/stable/tensorboard.html
+ - from torch.utils.tensorboard import SummaryWriter
+ + from huggingface_hub import HFSummaryWriter
+
+ import numpy as np
+
+ - writer = SummaryWriter()
+ + writer = HFSummaryWriter(repo_id="username/my-trained-model")
+
+ for n_iter in range(100):
+ writer.add_scalar('Loss/train', np.random.random(), n_iter)
+ writer.add_scalar('Loss/test', np.random.random(), n_iter)
+ writer.add_scalar('Accuracy/train', np.random.random(), n_iter)
+ writer.add_scalar('Accuracy/test', np.random.random(), n_iter)
+ ```
+
+ ```py
+ >>> from huggingface_hub import HFSummaryWriter
+
+ # Logs are automatically pushed every 15 minutes (5 by default) + when exiting the context manager
+ >>> with HFSummaryWriter(repo_id="test_hf_logger", commit_every=15) as logger:
+ ... logger.add_scalar("a", 1)
+ ... logger.add_scalar("b", 2)
+ ```
+ """
+
+ @experimental
+ def __new__(cls, *args, **kwargs) -> "HFSummaryWriter":
+ if not is_summary_writer_available:
+ raise ImportError(
+ "You must have `tensorboard` installed to use `HFSummaryWriter`. Please run `pip install --upgrade"
+ " tensorboardX` first."
+ )
+ return super().__new__(cls)
+
+ def __init__(
+ self,
+ repo_id: str,
+ *,
+ logdir: Optional[str] = None,
+ commit_every: Union[int, float] = 5,
+ squash_history: bool = False,
+ repo_type: Optional[str] = None,
+ repo_revision: Optional[str] = None,
+ repo_private: Optional[bool] = None,
+ path_in_repo: Optional[str] = "tensorboard",
+ repo_allow_patterns: Optional[Union[List[str], str]] = "*.tfevents.*",
+ repo_ignore_patterns: Optional[Union[List[str], str]] = None,
+ token: Optional[str] = None,
+ **kwargs,
+ ):
+ # Initialize SummaryWriter
+ super().__init__(logdir=logdir, **kwargs)
+
+ # Check logdir has been correctly initialized and fail early otherwise. In practice, SummaryWriter takes care of it.
+ if not isinstance(self.logdir, str):
+ raise ValueError(f"`self.logdir` must be a string. Got '{self.logdir}' of type {type(self.logdir)}.")
+
+ # Append logdir name to `path_in_repo`
+ if path_in_repo is None or path_in_repo == "":
+ path_in_repo = Path(self.logdir).name
+ else:
+ path_in_repo = path_in_repo.strip("/") + "/" + Path(self.logdir).name
+
+ # Initialize scheduler
+ self.scheduler = CommitScheduler(
+ folder_path=self.logdir,
+ path_in_repo=path_in_repo,
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=repo_revision,
+ private=repo_private,
+ token=token,
+ allow_patterns=repo_allow_patterns,
+ ignore_patterns=repo_ignore_patterns,
+ every=commit_every,
+ squash_history=squash_history,
+ )
+
+ # Exposing some high-level info at root level
+ self.repo_id = self.scheduler.repo_id
+ self.repo_type = self.scheduler.repo_type
+ self.repo_revision = self.scheduler.revision
+
+ # Add `hf-summary-writer` tag to the model card metadata
+ try:
+ card = ModelCard.load(repo_id_or_path=self.repo_id, repo_type=self.repo_type)
+ except EntryNotFoundError:
+ card = ModelCard("")
+ tags = card.data.get("tags", [])
+ if "hf-summary-writer" not in tags:
+ tags.append("hf-summary-writer")
+ card.data["tags"] = tags
+ card.push_to_hub(repo_id=self.repo_id, repo_type=self.repo_type)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Push to hub in a non-blocking way when exiting the logger's context manager."""
+ super().__exit__(exc_type, exc_val, exc_tb)
+ future = self.scheduler.trigger()
+ future.result()
diff --git a/env/Lib/site-packages/huggingface_hub/_upload_large_folder.py b/env/Lib/site-packages/huggingface_hub/_upload_large_folder.py
new file mode 100644
index 0000000000000000000000000000000000000000..c925a31ff57d69fbb108d911815691b01ad5fe57
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_upload_large_folder.py
@@ -0,0 +1,622 @@
+# coding=utf-8
+# Copyright 2024-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import enum
+import logging
+import os
+import queue
+import shutil
+import sys
+import threading
+import time
+import traceback
+from datetime import datetime
+from pathlib import Path
+from threading import Lock
+from typing import TYPE_CHECKING, List, Optional, Tuple, Union
+from urllib.parse import quote
+
+from . import constants
+from ._commit_api import CommitOperationAdd, UploadInfo, _fetch_upload_modes
+from ._local_folder import LocalUploadFileMetadata, LocalUploadFilePaths, get_local_upload_paths, read_upload_metadata
+from .constants import DEFAULT_REVISION, REPO_TYPES
+from .utils import DEFAULT_IGNORE_PATTERNS, filter_repo_objects, tqdm
+from .utils._cache_manager import _format_size
+from .utils.sha import sha_fileobj
+
+
+if TYPE_CHECKING:
+ from .hf_api import HfApi
+
+logger = logging.getLogger(__name__)
+
+WAITING_TIME_IF_NO_TASKS = 10 # seconds
+MAX_NB_REGULAR_FILES_PER_COMMIT = 75
+MAX_NB_LFS_FILES_PER_COMMIT = 150
+
+
+def upload_large_folder_internal(
+ api: "HfApi",
+ repo_id: str,
+ folder_path: Union[str, Path],
+ *,
+ repo_type: str, # Repo type is required!
+ revision: Optional[str] = None,
+ private: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ num_workers: Optional[int] = None,
+ print_report: bool = True,
+ print_report_every: int = 60,
+):
+ """Upload a large folder to the Hub in the most resilient way possible.
+
+ See [`HfApi.upload_large_folder`] for the full documentation.
+ """
+ # 1. Check args and setup
+ if repo_type is None:
+ raise ValueError(
+ "For large uploads, `repo_type` is explicitly required. Please set it to `model`, `dataset` or `space`."
+ " If you are using the CLI, pass it as `--repo-type=model`."
+ )
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if revision is None:
+ revision = DEFAULT_REVISION
+
+ folder_path = Path(folder_path).expanduser().resolve()
+ if not folder_path.is_dir():
+ raise ValueError(f"Provided path: '{folder_path}' is not a directory")
+
+ if ignore_patterns is None:
+ ignore_patterns = []
+ elif isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+ ignore_patterns += DEFAULT_IGNORE_PATTERNS
+
+ if num_workers is None:
+ nb_cores = os.cpu_count() or 1
+ num_workers = max(nb_cores - 2, 2) # Use all but 2 cores, or at least 2 cores
+
+ # 2. Create repo if missing
+ repo_url = api.create_repo(repo_id=repo_id, repo_type=repo_type, private=private, exist_ok=True)
+ logger.info(f"Repo created: {repo_url}")
+ repo_id = repo_url.repo_id
+
+ # 3. List files to upload
+ filtered_paths_list = filter_repo_objects(
+ (path.relative_to(folder_path).as_posix() for path in folder_path.glob("**/*") if path.is_file()),
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ )
+ paths_list = [get_local_upload_paths(folder_path, relpath) for relpath in filtered_paths_list]
+ logger.info(f"Found {len(paths_list)} candidate files to upload")
+
+ # Read metadata for each file
+ items = [
+ (paths, read_upload_metadata(folder_path, paths.path_in_repo))
+ for paths in tqdm(paths_list, desc="Recovering from metadata files")
+ ]
+
+ # 4. Start workers
+ status = LargeUploadStatus(items)
+ threads = [
+ threading.Thread(
+ target=_worker_job,
+ kwargs={
+ "status": status,
+ "api": api,
+ "repo_id": repo_id,
+ "repo_type": repo_type,
+ "revision": revision,
+ },
+ )
+ for _ in range(num_workers)
+ ]
+
+ for thread in threads:
+ thread.start()
+
+ # 5. Print regular reports
+ if print_report:
+ print("\n\n" + status.current_report())
+ last_report_ts = time.time()
+ while True:
+ time.sleep(1)
+ if time.time() - last_report_ts >= print_report_every:
+ if print_report:
+ _print_overwrite(status.current_report())
+ last_report_ts = time.time()
+ if status.is_done():
+ logging.info("Is done: exiting main loop")
+ break
+
+ for thread in threads:
+ thread.join()
+
+ logger.info(status.current_report())
+ logging.info("Upload is complete!")
+
+
+####################
+# Logic to manage workers and synchronize tasks
+####################
+
+
+class WorkerJob(enum.Enum):
+ SHA256 = enum.auto()
+ GET_UPLOAD_MODE = enum.auto()
+ PREUPLOAD_LFS = enum.auto()
+ COMMIT = enum.auto()
+ WAIT = enum.auto() # if no tasks are available but we don't want to exit
+
+
+JOB_ITEM_T = Tuple[LocalUploadFilePaths, LocalUploadFileMetadata]
+
+
+class LargeUploadStatus:
+ """Contains information, queues and tasks for a large upload process."""
+
+ def __init__(self, items: List[JOB_ITEM_T]):
+ self.items = items
+ self.queue_sha256: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
+ self.queue_get_upload_mode: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
+ self.queue_preupload_lfs: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
+ self.queue_commit: "queue.Queue[JOB_ITEM_T]" = queue.Queue()
+ self.lock = Lock()
+
+ self.nb_workers_sha256: int = 0
+ self.nb_workers_get_upload_mode: int = 0
+ self.nb_workers_preupload_lfs: int = 0
+ self.nb_workers_commit: int = 0
+ self.nb_workers_waiting: int = 0
+ self.last_commit_attempt: Optional[float] = None
+
+ self._started_at = datetime.now()
+
+ # Setup queues
+ for item in self.items:
+ paths, metadata = item
+ if metadata.sha256 is None:
+ self.queue_sha256.put(item)
+ elif metadata.upload_mode is None:
+ self.queue_get_upload_mode.put(item)
+ elif metadata.upload_mode == "lfs" and not metadata.is_uploaded:
+ self.queue_preupload_lfs.put(item)
+ elif not metadata.is_committed:
+ self.queue_commit.put(item)
+ else:
+ logger.debug(f"Skipping file {paths.path_in_repo} (already uploaded and committed)")
+
+ def current_report(self) -> str:
+ """Generate a report of the current status of the large upload."""
+ nb_hashed = 0
+ size_hashed = 0
+ nb_preuploaded = 0
+ nb_lfs = 0
+ nb_lfs_unsure = 0
+ size_preuploaded = 0
+ nb_committed = 0
+ size_committed = 0
+ total_size = 0
+ ignored_files = 0
+ total_files = 0
+
+ with self.lock:
+ for _, metadata in self.items:
+ if metadata.should_ignore:
+ ignored_files += 1
+ continue
+ total_size += metadata.size
+ total_files += 1
+ if metadata.sha256 is not None:
+ nb_hashed += 1
+ size_hashed += metadata.size
+ if metadata.upload_mode == "lfs":
+ nb_lfs += 1
+ if metadata.upload_mode is None:
+ nb_lfs_unsure += 1
+ if metadata.is_uploaded:
+ nb_preuploaded += 1
+ size_preuploaded += metadata.size
+ if metadata.is_committed:
+ nb_committed += 1
+ size_committed += metadata.size
+ total_size_str = _format_size(total_size)
+
+ now = datetime.now()
+ now_str = now.strftime("%Y-%m-%d %H:%M:%S")
+ elapsed = now - self._started_at
+ elapsed_str = str(elapsed).split(".")[0] # remove milliseconds
+
+ message = "\n" + "-" * 10
+ message += f" {now_str} ({elapsed_str}) "
+ message += "-" * 10 + "\n"
+
+ message += "Files: "
+ message += f"hashed {nb_hashed}/{total_files} ({_format_size(size_hashed)}/{total_size_str}) | "
+ message += f"pre-uploaded: {nb_preuploaded}/{nb_lfs} ({_format_size(size_preuploaded)}/{total_size_str})"
+ if nb_lfs_unsure > 0:
+ message += f" (+{nb_lfs_unsure} unsure)"
+ message += f" | committed: {nb_committed}/{total_files} ({_format_size(size_committed)}/{total_size_str})"
+ message += f" | ignored: {ignored_files}\n"
+
+ message += "Workers: "
+ message += f"hashing: {self.nb_workers_sha256} | "
+ message += f"get upload mode: {self.nb_workers_get_upload_mode} | "
+ message += f"pre-uploading: {self.nb_workers_preupload_lfs} | "
+ message += f"committing: {self.nb_workers_commit} | "
+ message += f"waiting: {self.nb_workers_waiting}\n"
+ message += "-" * 51
+
+ return message
+
+ def is_done(self) -> bool:
+ with self.lock:
+ return all(metadata.is_committed or metadata.should_ignore for _, metadata in self.items)
+
+
+def _worker_job(
+ status: LargeUploadStatus,
+ api: "HfApi",
+ repo_id: str,
+ repo_type: str,
+ revision: str,
+):
+ """
+ Main process for a worker. The worker will perform tasks based on the priority list until all files are uploaded
+ and committed. If no tasks are available, the worker will wait for 10 seconds before checking again.
+
+ If a task fails for any reason, the item(s) are put back in the queue for another worker to pick up.
+
+ Read `upload_large_folder` docstring for more information on how tasks are prioritized.
+ """
+ while True:
+ next_job: Optional[Tuple[WorkerJob, List[JOB_ITEM_T]]] = None
+
+ # Determine next task
+ next_job = _determine_next_job(status)
+ if next_job is None:
+ return
+ job, items = next_job
+
+ # Perform task
+ if job == WorkerJob.SHA256:
+ item = items[0] # single item
+ try:
+ _compute_sha256(item)
+ status.queue_get_upload_mode.put(item)
+ except KeyboardInterrupt:
+ raise
+ except Exception as e:
+ logger.error(f"Failed to compute sha256: {e}")
+ traceback.format_exc()
+ status.queue_sha256.put(item)
+
+ with status.lock:
+ status.nb_workers_sha256 -= 1
+
+ elif job == WorkerJob.GET_UPLOAD_MODE:
+ try:
+ _get_upload_mode(items, api=api, repo_id=repo_id, repo_type=repo_type, revision=revision)
+ except KeyboardInterrupt:
+ raise
+ except Exception as e:
+ logger.error(f"Failed to get upload mode: {e}")
+ traceback.format_exc()
+
+ # Items are either:
+ # - dropped (if should_ignore)
+ # - put in LFS queue (if LFS)
+ # - put in commit queue (if regular)
+ # - or put back (if error occurred).
+ for item in items:
+ _, metadata = item
+ if metadata.should_ignore:
+ continue
+ if metadata.upload_mode == "lfs":
+ status.queue_preupload_lfs.put(item)
+ elif metadata.upload_mode == "regular":
+ status.queue_commit.put(item)
+ else:
+ status.queue_get_upload_mode.put(item)
+
+ with status.lock:
+ status.nb_workers_get_upload_mode -= 1
+
+ elif job == WorkerJob.PREUPLOAD_LFS:
+ item = items[0] # single item
+ try:
+ _preupload_lfs(item, api=api, repo_id=repo_id, repo_type=repo_type, revision=revision)
+ status.queue_commit.put(item)
+ except KeyboardInterrupt:
+ raise
+ except Exception as e:
+ logger.error(f"Failed to preupload LFS: {e}")
+ traceback.format_exc()
+ status.queue_preupload_lfs.put(item)
+
+ with status.lock:
+ status.nb_workers_preupload_lfs -= 1
+
+ elif job == WorkerJob.COMMIT:
+ try:
+ _commit(items, api=api, repo_id=repo_id, repo_type=repo_type, revision=revision)
+ except KeyboardInterrupt:
+ raise
+ except Exception as e:
+ logger.error(f"Failed to commit: {e}")
+ traceback.format_exc()
+ for item in items:
+ status.queue_commit.put(item)
+ with status.lock:
+ status.last_commit_attempt = time.time()
+ status.nb_workers_commit -= 1
+
+ elif job == WorkerJob.WAIT:
+ time.sleep(WAITING_TIME_IF_NO_TASKS)
+ with status.lock:
+ status.nb_workers_waiting -= 1
+
+
+def _determine_next_job(status: LargeUploadStatus) -> Optional[Tuple[WorkerJob, List[JOB_ITEM_T]]]:
+ with status.lock:
+ # 1. Commit if more than 5 minutes since last commit attempt (and at least 1 file)
+ if (
+ status.nb_workers_commit == 0
+ and status.queue_commit.qsize() > 0
+ and status.last_commit_attempt is not None
+ and time.time() - status.last_commit_attempt > 5 * 60
+ ):
+ status.nb_workers_commit += 1
+ logger.debug("Job: commit (more than 5 minutes since last commit attempt)")
+ return (WorkerJob.COMMIT, _get_items_to_commit(status.queue_commit))
+
+ # 2. Commit if at least 100 files are ready to commit
+ elif status.nb_workers_commit == 0 and status.queue_commit.qsize() >= 150:
+ status.nb_workers_commit += 1
+ logger.debug("Job: commit (>100 files ready)")
+ return (WorkerJob.COMMIT, _get_items_to_commit(status.queue_commit))
+
+ # 3. Get upload mode if at least 10 files
+ elif status.queue_get_upload_mode.qsize() >= 10:
+ status.nb_workers_get_upload_mode += 1
+ logger.debug("Job: get upload mode (>10 files ready)")
+ return (WorkerJob.GET_UPLOAD_MODE, _get_n(status.queue_get_upload_mode, 50))
+
+ # 4. Preupload LFS file if at least 1 file and no worker is preuploading LFS
+ elif status.queue_preupload_lfs.qsize() > 0 and status.nb_workers_preupload_lfs == 0:
+ status.nb_workers_preupload_lfs += 1
+ logger.debug("Job: preupload LFS (no other worker preuploading LFS)")
+ return (WorkerJob.PREUPLOAD_LFS, _get_one(status.queue_preupload_lfs))
+
+ # 5. Compute sha256 if at least 1 file and no worker is computing sha256
+ elif status.queue_sha256.qsize() > 0 and status.nb_workers_sha256 == 0:
+ status.nb_workers_sha256 += 1
+ logger.debug("Job: sha256 (no other worker computing sha256)")
+ return (WorkerJob.SHA256, _get_one(status.queue_sha256))
+
+ # 6. Get upload mode if at least 1 file and no worker is getting upload mode
+ elif status.queue_get_upload_mode.qsize() > 0 and status.nb_workers_get_upload_mode == 0:
+ status.nb_workers_get_upload_mode += 1
+ logger.debug("Job: get upload mode (no other worker getting upload mode)")
+ return (WorkerJob.GET_UPLOAD_MODE, _get_n(status.queue_get_upload_mode, 50))
+
+ # 7. Preupload LFS file if at least 1 file
+ # Skip if hf_transfer is enabled and there is already a worker preuploading LFS
+ elif status.queue_preupload_lfs.qsize() > 0 and (
+ status.nb_workers_preupload_lfs == 0 or not constants.HF_HUB_ENABLE_HF_TRANSFER
+ ):
+ status.nb_workers_preupload_lfs += 1
+ logger.debug("Job: preupload LFS")
+ return (WorkerJob.PREUPLOAD_LFS, _get_one(status.queue_preupload_lfs))
+
+ # 8. Compute sha256 if at least 1 file
+ elif status.queue_sha256.qsize() > 0:
+ status.nb_workers_sha256 += 1
+ logger.debug("Job: sha256")
+ return (WorkerJob.SHA256, _get_one(status.queue_sha256))
+
+ # 9. Get upload mode if at least 1 file
+ elif status.queue_get_upload_mode.qsize() > 0:
+ status.nb_workers_get_upload_mode += 1
+ logger.debug("Job: get upload mode")
+ return (WorkerJob.GET_UPLOAD_MODE, _get_n(status.queue_get_upload_mode, 50))
+
+ # 10. Commit if at least 1 file and 1 min since last commit attempt
+ elif (
+ status.nb_workers_commit == 0
+ and status.queue_commit.qsize() > 0
+ and status.last_commit_attempt is not None
+ and time.time() - status.last_commit_attempt > 1 * 60
+ ):
+ status.nb_workers_commit += 1
+ logger.debug("Job: commit (1 min since last commit attempt)")
+ return (WorkerJob.COMMIT, _get_items_to_commit(status.queue_commit))
+
+ # 11. Commit if at least 1 file all other queues are empty and all workers are waiting
+ # e.g. when it's the last commit
+ elif (
+ status.nb_workers_commit == 0
+ and status.queue_commit.qsize() > 0
+ and status.queue_sha256.qsize() == 0
+ and status.queue_get_upload_mode.qsize() == 0
+ and status.queue_preupload_lfs.qsize() == 0
+ and status.nb_workers_sha256 == 0
+ and status.nb_workers_get_upload_mode == 0
+ and status.nb_workers_preupload_lfs == 0
+ ):
+ status.nb_workers_commit += 1
+ logger.debug("Job: commit")
+ return (WorkerJob.COMMIT, _get_items_to_commit(status.queue_commit))
+
+ # 12. If all queues are empty, exit
+ elif all(metadata.is_committed or metadata.should_ignore for _, metadata in status.items):
+ logger.info("All files have been processed! Exiting worker.")
+ return None
+
+ # 13. If no task is available, wait
+ else:
+ status.nb_workers_waiting += 1
+ logger.debug(f"No task available, waiting... ({WAITING_TIME_IF_NO_TASKS}s)")
+ return (WorkerJob.WAIT, [])
+
+
+####################
+# Atomic jobs (sha256, get_upload_mode, preupload_lfs, commit)
+####################
+
+
+def _compute_sha256(item: JOB_ITEM_T) -> None:
+ """Compute sha256 of a file and save it in metadata."""
+ paths, metadata = item
+ if metadata.sha256 is None:
+ with paths.file_path.open("rb") as f:
+ metadata.sha256 = sha_fileobj(f).hex()
+ metadata.save(paths)
+
+
+def _get_upload_mode(items: List[JOB_ITEM_T], api: "HfApi", repo_id: str, repo_type: str, revision: str) -> None:
+ """Get upload mode for each file and update metadata.
+
+ Also receive info if the file should be ignored.
+ """
+ additions = [_build_hacky_operation(item) for item in items]
+ _fetch_upload_modes(
+ additions=additions,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=api._build_hf_headers(),
+ revision=quote(revision, safe=""),
+ )
+ for item, addition in zip(items, additions):
+ paths, metadata = item
+ metadata.upload_mode = addition._upload_mode
+ metadata.should_ignore = addition._should_ignore
+ metadata.save(paths)
+
+
+def _preupload_lfs(item: JOB_ITEM_T, api: "HfApi", repo_id: str, repo_type: str, revision: str) -> None:
+ """Preupload LFS file and update metadata."""
+ paths, metadata = item
+ addition = _build_hacky_operation(item)
+ api.preupload_lfs_files(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=revision,
+ additions=[addition],
+ )
+
+ metadata.is_uploaded = True
+ metadata.save(paths)
+
+
+def _commit(items: List[JOB_ITEM_T], api: "HfApi", repo_id: str, repo_type: str, revision: str) -> None:
+ """Commit files to the repo."""
+ additions = [_build_hacky_operation(item) for item in items]
+ api.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=revision,
+ operations=additions,
+ commit_message="Add files using upload-large-folder tool",
+ )
+ for paths, metadata in items:
+ metadata.is_committed = True
+ metadata.save(paths)
+
+
+####################
+# Hacks with CommitOperationAdd to bypass checks/sha256 calculation
+####################
+
+
+class HackyCommitOperationAdd(CommitOperationAdd):
+ def __post_init__(self) -> None:
+ if isinstance(self.path_or_fileobj, Path):
+ self.path_or_fileobj = str(self.path_or_fileobj)
+
+
+def _build_hacky_operation(item: JOB_ITEM_T) -> HackyCommitOperationAdd:
+ paths, metadata = item
+ operation = HackyCommitOperationAdd(path_in_repo=paths.path_in_repo, path_or_fileobj=paths.file_path)
+ with paths.file_path.open("rb") as file:
+ sample = file.peek(512)[:512]
+ if metadata.sha256 is None:
+ raise ValueError("sha256 must have been computed by now!")
+ operation.upload_info = UploadInfo(sha256=bytes.fromhex(metadata.sha256), size=metadata.size, sample=sample)
+ return operation
+
+
+####################
+# Misc helpers
+####################
+
+
+def _get_one(queue: "queue.Queue[JOB_ITEM_T]") -> List[JOB_ITEM_T]:
+ return [queue.get()]
+
+
+def _get_n(queue: "queue.Queue[JOB_ITEM_T]", n: int) -> List[JOB_ITEM_T]:
+ return [queue.get() for _ in range(min(queue.qsize(), n))]
+
+
+def _get_items_to_commit(queue: "queue.Queue[JOB_ITEM_T]") -> List[JOB_ITEM_T]:
+ """Special case for commit job: the number of items to commit depends on the type of files."""
+ # Can take at most 50 regular files and/or 100 LFS files in a single commit
+ items: List[JOB_ITEM_T] = []
+ nb_lfs, nb_regular = 0, 0
+ while True:
+ # If empty queue => commit everything
+ if queue.qsize() == 0:
+ return items
+
+ # If we have enough items => commit them
+ if nb_lfs >= MAX_NB_LFS_FILES_PER_COMMIT or nb_regular >= MAX_NB_REGULAR_FILES_PER_COMMIT:
+ return items
+
+ # Else, get a new item and increase counter
+ item = queue.get()
+ items.append(item)
+ _, metadata = item
+ if metadata.upload_mode == "lfs":
+ nb_lfs += 1
+ else:
+ nb_regular += 1
+
+
+def _print_overwrite(report: str) -> None:
+ """Print a report, overwriting the previous lines.
+
+ Since tqdm in using `sys.stderr` to (re-)write progress bars, we need to use `sys.stdout`
+ to print the report.
+
+ Note: works well only if no other process is writing to `sys.stdout`!
+ """
+ report += "\n"
+ # Get terminal width
+ terminal_width = shutil.get_terminal_size().columns
+
+ # Count number of lines that should be cleared
+ nb_lines = sum(len(line) // terminal_width + 1 for line in report.splitlines())
+
+ # Clear previous lines based on the number of lines in the report
+ for _ in range(nb_lines):
+ sys.stdout.write("\r\033[K") # Clear line
+ sys.stdout.write("\033[F") # Move cursor up one line
+
+ # Print the new report, filling remaining space with whitespace
+ sys.stdout.write(report)
+ sys.stdout.write(" " * (terminal_width - len(report.splitlines()[-1])))
+ sys.stdout.flush()
diff --git a/env/Lib/site-packages/huggingface_hub/_webhooks_payload.py b/env/Lib/site-packages/huggingface_hub/_webhooks_payload.py
new file mode 100644
index 0000000000000000000000000000000000000000..288f4b08b9428980e99ca06703442eab62fad277
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_webhooks_payload.py
@@ -0,0 +1,137 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains data structures to parse the webhooks payload."""
+
+from typing import List, Literal, Optional
+
+from .utils import is_pydantic_available
+
+
+if is_pydantic_available():
+ from pydantic import BaseModel
+else:
+ # Define a dummy BaseModel to avoid import errors when pydantic is not installed
+ # Import error will be raised when trying to use the class
+
+ class BaseModel: # type: ignore [no-redef]
+ def __init__(self, *args, **kwargs) -> None:
+ raise ImportError(
+ "You must have `pydantic` installed to use `WebhookPayload`. This is an optional dependency that"
+ " should be installed separately. Please run `pip install --upgrade pydantic` and retry."
+ )
+
+
+# This is an adaptation of the ReportV3 interface implemented in moon-landing. V0, V1 and V2 have been ignored as they
+# are not in used anymore. To keep in sync when format is updated in
+# https://github.com/huggingface/moon-landing/blob/main/server/lib/HFWebhooks.ts (internal link).
+
+
+WebhookEvent_T = Literal[
+ "create",
+ "delete",
+ "move",
+ "update",
+]
+RepoChangeEvent_T = Literal[
+ "add",
+ "move",
+ "remove",
+ "update",
+]
+RepoType_T = Literal[
+ "dataset",
+ "model",
+ "space",
+]
+DiscussionStatus_T = Literal[
+ "closed",
+ "draft",
+ "open",
+ "merged",
+]
+SupportedWebhookVersion = Literal[3]
+
+
+class ObjectId(BaseModel):
+ id: str
+
+
+class WebhookPayloadUrl(BaseModel):
+ web: str
+ api: Optional[str] = None
+
+
+class WebhookPayloadMovedTo(BaseModel):
+ name: str
+ owner: ObjectId
+
+
+class WebhookPayloadWebhook(ObjectId):
+ version: SupportedWebhookVersion
+
+
+class WebhookPayloadEvent(BaseModel):
+ action: WebhookEvent_T
+ scope: str
+
+
+class WebhookPayloadDiscussionChanges(BaseModel):
+ base: str
+ mergeCommitId: Optional[str] = None
+
+
+class WebhookPayloadComment(ObjectId):
+ author: ObjectId
+ hidden: bool
+ content: Optional[str] = None
+ url: WebhookPayloadUrl
+
+
+class WebhookPayloadDiscussion(ObjectId):
+ num: int
+ author: ObjectId
+ url: WebhookPayloadUrl
+ title: str
+ isPullRequest: bool
+ status: DiscussionStatus_T
+ changes: Optional[WebhookPayloadDiscussionChanges] = None
+ pinned: Optional[bool] = None
+
+
+class WebhookPayloadRepo(ObjectId):
+ owner: ObjectId
+ head_sha: Optional[str] = None
+ name: str
+ private: bool
+ subdomain: Optional[str] = None
+ tags: Optional[List[str]] = None
+ type: Literal["dataset", "model", "space"]
+ url: WebhookPayloadUrl
+
+
+class WebhookPayloadUpdatedRef(BaseModel):
+ ref: str
+ oldSha: Optional[str] = None
+ newSha: Optional[str] = None
+
+
+class WebhookPayload(BaseModel):
+ event: WebhookPayloadEvent
+ repo: WebhookPayloadRepo
+ discussion: Optional[WebhookPayloadDiscussion] = None
+ comment: Optional[WebhookPayloadComment] = None
+ webhook: WebhookPayloadWebhook
+ movedTo: Optional[WebhookPayloadMovedTo] = None
+ updatedRefs: Optional[List[WebhookPayloadUpdatedRef]] = None
diff --git a/env/Lib/site-packages/huggingface_hub/_webhooks_server.py b/env/Lib/site-packages/huggingface_hub/_webhooks_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0c08e0092b373db189bc9a4d3deb3fe5692b559
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/_webhooks_server.py
@@ -0,0 +1,386 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily."""
+
+import atexit
+import inspect
+import os
+from functools import wraps
+from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
+
+from .utils import experimental, is_fastapi_available, is_gradio_available
+
+
+if TYPE_CHECKING:
+ import gradio as gr
+ from fastapi import Request
+
+if is_fastapi_available():
+ from fastapi import FastAPI, Request
+ from fastapi.responses import JSONResponse
+else:
+ # Will fail at runtime if FastAPI is not available
+ FastAPI = Request = JSONResponse = None # type: ignore [misc, assignment]
+
+
+_global_app: Optional["WebhooksServer"] = None
+_is_local = os.environ.get("SPACE_ID") is None
+
+
+@experimental
+class WebhooksServer:
+ """
+ The [`WebhooksServer`] class lets you create an instance of a Gradio app that can receive Huggingface webhooks.
+ These webhooks can be registered using the [`~WebhooksServer.add_webhook`] decorator. Webhook endpoints are added to
+ the app as a POST endpoint to the FastAPI router. Once all the webhooks are registered, the `launch` method has to be
+ called to start the app.
+
+ It is recommended to accept [`WebhookPayload`] as the first argument of the webhook function. It is a Pydantic
+ model that contains all the information about the webhook event. The data will be parsed automatically for you.
+
+ Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to setup your
+ WebhooksServer and deploy it on a Space.
+
+
+
+ `WebhooksServer` is experimental. Its API is subject to change in the future.
+
+
+
+
+
+ You must have `gradio` installed to use `WebhooksServer` (`pip install --upgrade gradio`).
+
+
+
+ Args:
+ ui (`gradio.Blocks`, optional):
+ A Gradio UI instance to be used as the Space landing page. If `None`, a UI displaying instructions
+ about the configured webhooks is created.
+ webhook_secret (`str`, optional):
+ A secret key to verify incoming webhook requests. You can set this value to any secret you want as long as
+ you also configure it in your [webhooks settings panel](https://huggingface.co/settings/webhooks). You
+ can also set this value as the `WEBHOOK_SECRET` environment variable. If no secret is provided, the
+ webhook endpoints are opened without any security.
+
+ Example:
+
+ ```python
+ import gradio as gr
+ from huggingface_hub import WebhooksServer, WebhookPayload
+
+ with gr.Blocks() as ui:
+ ...
+
+ app = WebhooksServer(ui=ui, webhook_secret="my_secret_key")
+
+ @app.add_webhook("/say_hello")
+ async def hello(payload: WebhookPayload):
+ return {"message": "hello"}
+
+ app.launch()
+ ```
+ """
+
+ def __new__(cls, *args, **kwargs) -> "WebhooksServer":
+ if not is_gradio_available():
+ raise ImportError(
+ "You must have `gradio` installed to use `WebhooksServer`. Please run `pip install --upgrade gradio`"
+ " first."
+ )
+ if not is_fastapi_available():
+ raise ImportError(
+ "You must have `fastapi` installed to use `WebhooksServer`. Please run `pip install --upgrade fastapi`"
+ " first."
+ )
+ return super().__new__(cls)
+
+ def __init__(
+ self,
+ ui: Optional["gr.Blocks"] = None,
+ webhook_secret: Optional[str] = None,
+ ) -> None:
+ self._ui = ui
+
+ self.webhook_secret = webhook_secret or os.getenv("WEBHOOK_SECRET")
+ self.registered_webhooks: Dict[str, Callable] = {}
+ _warn_on_empty_secret(self.webhook_secret)
+
+ def add_webhook(self, path: Optional[str] = None) -> Callable:
+ """
+ Decorator to add a webhook to the [`WebhooksServer`] server.
+
+ Args:
+ path (`str`, optional):
+ The URL path to register the webhook function. If not provided, the function name will be used as the
+ path. In any case, all webhooks are registered under `/webhooks`.
+
+ Raises:
+ ValueError: If the provided path is already registered as a webhook.
+
+ Example:
+ ```python
+ from huggingface_hub import WebhooksServer, WebhookPayload
+
+ app = WebhooksServer()
+
+ @app.add_webhook
+ async def trigger_training(payload: WebhookPayload):
+ if payload.repo.type == "dataset" and payload.event.action == "update":
+ # Trigger a training job if a dataset is updated
+ ...
+
+ app.launch()
+ ```
+ """
+ # Usage: directly as decorator. Example: `@app.add_webhook`
+ if callable(path):
+ # If path is a function, it means it was used as a decorator without arguments
+ return self.add_webhook()(path)
+
+ # Usage: provide a path. Example: `@app.add_webhook(...)`
+ @wraps(FastAPI.post)
+ def _inner_post(*args, **kwargs):
+ func = args[0]
+ abs_path = f"/webhooks/{(path or func.__name__).strip('/')}"
+ if abs_path in self.registered_webhooks:
+ raise ValueError(f"Webhook {abs_path} already exists.")
+ self.registered_webhooks[abs_path] = func
+
+ return _inner_post
+
+ def launch(self, prevent_thread_lock: bool = False, **launch_kwargs: Any) -> None:
+ """Launch the Gradio app and register webhooks to the underlying FastAPI server.
+
+ Input parameters are forwarded to Gradio when launching the app.
+ """
+ ui = self._ui or self._get_default_ui()
+
+ # Start Gradio App
+ # - as non-blocking so that webhooks can be added afterwards
+ # - as shared if launch locally (to debug webhooks)
+ launch_kwargs.setdefault("share", _is_local)
+ self.fastapi_app, _, _ = ui.launch(prevent_thread_lock=True, **launch_kwargs)
+
+ # Register webhooks to FastAPI app
+ for path, func in self.registered_webhooks.items():
+ # Add secret check if required
+ if self.webhook_secret is not None:
+ func = _wrap_webhook_to_check_secret(func, webhook_secret=self.webhook_secret)
+
+ # Add route to FastAPI app
+ self.fastapi_app.post(path)(func)
+
+ # Print instructions and block main thread
+ space_host = os.environ.get("SPACE_HOST")
+ url = "https://" + space_host if space_host is not None else (ui.share_url or ui.local_url)
+ url = url.strip("/")
+ message = "\nWebhooks are correctly setup and ready to use:"
+ message += "\n" + "\n".join(f" - POST {url}{webhook}" for webhook in self.registered_webhooks)
+ message += "\nGo to https://huggingface.co/settings/webhooks to setup your webhooks."
+ print(message)
+
+ if not prevent_thread_lock:
+ ui.block_thread()
+
+ def _get_default_ui(self) -> "gr.Blocks":
+ """Default UI if not provided (lists webhooks and provides basic instructions)."""
+ import gradio as gr
+
+ with gr.Blocks() as ui:
+ gr.Markdown("# This is an app to process 🤗 Webhooks")
+ gr.Markdown(
+ "Webhooks are a foundation for MLOps-related features. They allow you to listen for new changes on"
+ " specific repos or to all repos belonging to particular set of users/organizations (not just your"
+ " repos, but any repo). Check out this [guide](https://huggingface.co/docs/hub/webhooks) to get to"
+ " know more about webhooks on the Huggingface Hub."
+ )
+ gr.Markdown(
+ f"{len(self.registered_webhooks)} webhook(s) are registered:"
+ + "\n\n"
+ + "\n ".join(
+ f"- [{webhook_path}]({_get_webhook_doc_url(webhook.__name__, webhook_path)})"
+ for webhook_path, webhook in self.registered_webhooks.items()
+ )
+ )
+ gr.Markdown(
+ "Go to https://huggingface.co/settings/webhooks to setup your webhooks."
+ + "\nYou app is running locally. Please look at the logs to check the full URL you need to set."
+ if _is_local
+ else (
+ "\nThis app is running on a Space. You can find the corresponding URL in the options menu"
+ " (top-right) > 'Embed the Space'. The URL looks like 'https://{username}-{repo_name}.hf.space'."
+ )
+ )
+ return ui
+
+
+@experimental
+def webhook_endpoint(path: Optional[str] = None) -> Callable:
+ """Decorator to start a [`WebhooksServer`] and register the decorated function as a webhook endpoint.
+
+ This is a helper to get started quickly. If you need more flexibility (custom landing page or webhook secret),
+ you can use [`WebhooksServer`] directly. You can register multiple webhook endpoints (to the same server) by using
+ this decorator multiple times.
+
+ Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to setup your
+ server and deploy it on a Space.
+
+
+
+ `webhook_endpoint` is experimental. Its API is subject to change in the future.
+
+
+
+
+
+ You must have `gradio` installed to use `webhook_endpoint` (`pip install --upgrade gradio`).
+
+
+
+ Args:
+ path (`str`, optional):
+ The URL path to register the webhook function. If not provided, the function name will be used as the path.
+ In any case, all webhooks are registered under `/webhooks`.
+
+ Examples:
+ The default usage is to register a function as a webhook endpoint. The function name will be used as the path.
+ The server will be started automatically at exit (i.e. at the end of the script).
+
+ ```python
+ from huggingface_hub import webhook_endpoint, WebhookPayload
+
+ @webhook_endpoint
+ async def trigger_training(payload: WebhookPayload):
+ if payload.repo.type == "dataset" and payload.event.action == "update":
+ # Trigger a training job if a dataset is updated
+ ...
+
+ # Server is automatically started at the end of the script.
+ ```
+
+ Advanced usage: register a function as a webhook endpoint and start the server manually. This is useful if you
+ are running it in a notebook.
+
+ ```python
+ from huggingface_hub import webhook_endpoint, WebhookPayload
+
+ @webhook_endpoint
+ async def trigger_training(payload: WebhookPayload):
+ if payload.repo.type == "dataset" and payload.event.action == "update":
+ # Trigger a training job if a dataset is updated
+ ...
+
+ # Start the server manually
+ trigger_training.launch()
+ ```
+ """
+ if callable(path):
+ # If path is a function, it means it was used as a decorator without arguments
+ return webhook_endpoint()(path)
+
+ @wraps(WebhooksServer.add_webhook)
+ def _inner(func: Callable) -> Callable:
+ app = _get_global_app()
+ app.add_webhook(path)(func)
+ if len(app.registered_webhooks) == 1:
+ # Register `app.launch` to run at exit (only once)
+ atexit.register(app.launch)
+
+ @wraps(app.launch)
+ def _launch_now():
+ # Run the app directly (without waiting atexit)
+ atexit.unregister(app.launch)
+ app.launch()
+
+ func.launch = _launch_now # type: ignore
+ return func
+
+ return _inner
+
+
+def _get_global_app() -> WebhooksServer:
+ global _global_app
+ if _global_app is None:
+ _global_app = WebhooksServer()
+ return _global_app
+
+
+def _warn_on_empty_secret(webhook_secret: Optional[str]) -> None:
+ if webhook_secret is None:
+ print("Webhook secret is not defined. This means your webhook endpoints will be open to everyone.")
+ print(
+ "To add a secret, set `WEBHOOK_SECRET` as environment variable or pass it at initialization: "
+ "\n\t`app = WebhooksServer(webhook_secret='my_secret', ...)`"
+ )
+ print(
+ "For more details about webhook secrets, please refer to"
+ " https://huggingface.co/docs/hub/webhooks#webhook-secret."
+ )
+ else:
+ print("Webhook secret is correctly defined.")
+
+
+def _get_webhook_doc_url(webhook_name: str, webhook_path: str) -> str:
+ """Returns the anchor to a given webhook in the docs (experimental)"""
+ return "/docs#/default/" + webhook_name + webhook_path.replace("/", "_") + "_post"
+
+
+def _wrap_webhook_to_check_secret(func: Callable, webhook_secret: str) -> Callable:
+ """Wraps a webhook function to check the webhook secret before calling the function.
+
+ This is a hacky way to add the `request` parameter to the function signature. Since FastAPI based itself on route
+ parameters to inject the values to the function, we need to hack the function signature to retrieve the `Request`
+ object (and hence the headers). A far cleaner solution would be to use a middleware. However, since
+ `fastapi==0.90.1`, a middleware cannot be added once the app has started. And since the FastAPI app is started by
+ Gradio internals (and not by us), we cannot add a middleware.
+
+ This method is called only when a secret has been defined by the user. If a request is sent without the
+ "x-webhook-secret", the function will return a 401 error (unauthorized). If the header is sent but is incorrect,
+ the function will return a 403 error (forbidden).
+
+ Inspired by https://stackoverflow.com/a/33112180.
+ """
+ initial_sig = inspect.signature(func)
+
+ @wraps(func)
+ async def _protected_func(request: Request, **kwargs):
+ request_secret = request.headers.get("x-webhook-secret")
+ if request_secret is None:
+ return JSONResponse({"error": "x-webhook-secret header not set."}, status_code=401)
+ if request_secret != webhook_secret:
+ return JSONResponse({"error": "Invalid webhook secret."}, status_code=403)
+
+ # Inject `request` in kwargs if required
+ if "request" in initial_sig.parameters:
+ kwargs["request"] = request
+
+ # Handle both sync and async routes
+ if inspect.iscoroutinefunction(func):
+ return await func(**kwargs)
+ else:
+ return func(**kwargs)
+
+ # Update signature to include request
+ if "request" not in initial_sig.parameters:
+ _protected_func.__signature__ = initial_sig.replace( # type: ignore
+ parameters=(
+ inspect.Parameter(name="request", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=Request),
+ )
+ + tuple(initial_sig.parameters.values())
+ )
+
+ # Return protected route
+ return _protected_func
diff --git a/env/Lib/site-packages/huggingface_hub/commands/__init__.py b/env/Lib/site-packages/huggingface_hub/commands/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..49d088214505b9604964ab142e7f8a5b38ccd5ef
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/__init__.py
@@ -0,0 +1,27 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from abc import ABC, abstractmethod
+from argparse import _SubParsersAction
+
+
+class BaseHuggingfaceCLICommand(ABC):
+ @staticmethod
+ @abstractmethod
+ def register_subcommand(parser: _SubParsersAction):
+ raise NotImplementedError()
+
+ @abstractmethod
+ def run(self):
+ raise NotImplementedError()
diff --git a/env/Lib/site-packages/huggingface_hub/commands/_cli_utils.py b/env/Lib/site-packages/huggingface_hub/commands/_cli_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd56ad6896db2a257323e022896940c0ba0d68d3
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/_cli_utils.py
@@ -0,0 +1,69 @@
+# Copyright 2022 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a utility for good-looking prints."""
+
+import os
+from typing import List, Union
+
+
+class ANSI:
+ """
+ Helper for en.wikipedia.org/wiki/ANSI_escape_code
+ """
+
+ _bold = "\u001b[1m"
+ _gray = "\u001b[90m"
+ _red = "\u001b[31m"
+ _reset = "\u001b[0m"
+ _yellow = "\u001b[33m"
+
+ @classmethod
+ def bold(cls, s: str) -> str:
+ return cls._format(s, cls._bold)
+
+ @classmethod
+ def gray(cls, s: str) -> str:
+ return cls._format(s, cls._gray)
+
+ @classmethod
+ def red(cls, s: str) -> str:
+ return cls._format(s, cls._bold + cls._red)
+
+ @classmethod
+ def yellow(cls, s: str) -> str:
+ return cls._format(s, cls._yellow)
+
+ @classmethod
+ def _format(cls, s: str, code: str) -> str:
+ if os.environ.get("NO_COLOR"):
+ # See https://no-color.org/
+ return s
+ return f"{code}{s}{cls._reset}"
+
+
+def tabulate(rows: List[List[Union[str, int]]], headers: List[str]) -> str:
+ """
+ Inspired by:
+
+ - stackoverflow.com/a/8356620/593036
+ - stackoverflow.com/questions/9535954/printing-lists-as-tabular-data
+ """
+ col_widths = [max(len(str(x)) for x in col) for col in zip(*rows, headers)]
+ row_format = ("{{:{}}} " * len(headers)).format(*col_widths)
+ lines = []
+ lines.append(row_format.format(*headers))
+ lines.append(row_format.format(*["-" * w for w in col_widths]))
+ for row in rows:
+ lines.append(row_format.format(*row))
+ return "\n".join(lines)
diff --git a/env/Lib/site-packages/huggingface_hub/commands/delete_cache.py b/env/Lib/site-packages/huggingface_hub/commands/delete_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2fc44d31c8f59e6c517dd6df71466a319e5ea0e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/delete_cache.py
@@ -0,0 +1,428 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to delete some revisions from the HF cache directory.
+
+Usage:
+ huggingface-cli delete-cache
+ huggingface-cli delete-cache --disable-tui
+ huggingface-cli delete-cache --dir ~/.cache/huggingface/hub
+
+NOTE:
+ This command is based on `InquirerPy` to build the multiselect menu in the terminal.
+ This dependency has to be installed with `pip install huggingface_hub[cli]`. Since
+ we want to avoid as much as possible cross-platform issues, I chose a library that
+ is built on top of `python-prompt-toolkit` which seems to be a reference in terminal
+ GUI (actively maintained on both Unix and Windows, 7.9k stars).
+
+ For the moment, the TUI feature is in beta.
+
+ See:
+ - https://github.com/kazhala/InquirerPy
+ - https://inquirerpy.readthedocs.io/en/latest/
+ - https://github.com/prompt-toolkit/python-prompt-toolkit
+
+ Other solutions could have been:
+ - `simple_term_menu`: would be good as well for our use case but some issues suggest
+ that Windows is less supported.
+ See: https://github.com/IngoMeyer441/simple-term-menu
+ - `PyInquirer`: very similar to `InquirerPy` but older and not maintained anymore.
+ In particular, no support of Python3.10.
+ See: https://github.com/CITGuru/PyInquirer
+ - `pick` (or `pickpack`): easy to use and flexible but built on top of Python's
+ standard library `curses` that is specific to Unix (not implemented on Windows).
+ See https://github.com/wong2/pick and https://github.com/anafvana/pickpack.
+ - `inquirer`: lot of traction (700 stars) but explicitly states "experimental
+ support of Windows". Not built on top of `python-prompt-toolkit`.
+ See https://github.com/magmax/python-inquirer
+
+TODO: add support for `huggingface-cli delete-cache aaaaaa bbbbbb cccccc (...)` ?
+TODO: add "--keep-last" arg to delete revisions that are not on `main` ref
+TODO: add "--filter" arg to filter repositories by name ?
+TODO: add "--sort" arg to sort by size ?
+TODO: add "--limit" arg to limit to X repos ?
+TODO: add "-y" arg for immediate deletion ?
+See discussions in https://github.com/huggingface/huggingface_hub/issues/1025.
+"""
+
+import os
+from argparse import Namespace, _SubParsersAction
+from functools import wraps
+from tempfile import mkstemp
+from typing import Any, Callable, Iterable, List, Optional, Union
+
+from ..utils import CachedRepoInfo, CachedRevisionInfo, HFCacheInfo, scan_cache_dir
+from . import BaseHuggingfaceCLICommand
+from ._cli_utils import ANSI
+
+
+try:
+ from InquirerPy import inquirer
+ from InquirerPy.base.control import Choice
+ from InquirerPy.separator import Separator
+
+ _inquirer_py_available = True
+except ImportError:
+ _inquirer_py_available = False
+
+
+def require_inquirer_py(fn: Callable) -> Callable:
+ """Decorator to flag methods that require `InquirerPy`."""
+
+ # TODO: refactor this + imports in a unified pattern across codebase
+ @wraps(fn)
+ def _inner(*args, **kwargs):
+ if not _inquirer_py_available:
+ raise ImportError(
+ "The `delete-cache` command requires extra dependencies to work with"
+ " the TUI.\nPlease run `pip install huggingface_hub[cli]` to install"
+ " them.\nOtherwise, disable TUI using the `--disable-tui` flag."
+ )
+
+ return fn(*args, **kwargs)
+
+ return _inner
+
+
+# Possibility for the user to cancel deletion
+_CANCEL_DELETION_STR = "CANCEL_DELETION"
+
+
+class DeleteCacheCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ delete_cache_parser = parser.add_parser("delete-cache", help="Delete revisions from the cache directory.")
+
+ delete_cache_parser.add_argument(
+ "--dir",
+ type=str,
+ default=None,
+ help="cache directory (optional). Default to the default HuggingFace cache.",
+ )
+
+ delete_cache_parser.add_argument(
+ "--disable-tui",
+ action="store_true",
+ help=(
+ "Disable Terminal User Interface (TUI) mode. Useful if your"
+ " platform/terminal doesn't support the multiselect menu."
+ ),
+ )
+
+ delete_cache_parser.set_defaults(func=DeleteCacheCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.cache_dir: Optional[str] = args.dir
+ self.disable_tui: bool = args.disable_tui
+
+ def run(self):
+ """Run `delete-cache` command with or without TUI."""
+ # Scan cache directory
+ hf_cache_info = scan_cache_dir(self.cache_dir)
+
+ # Manual review from the user
+ if self.disable_tui:
+ selected_hashes = _manual_review_no_tui(hf_cache_info, preselected=[])
+ else:
+ selected_hashes = _manual_review_tui(hf_cache_info, preselected=[])
+
+ # If deletion is not cancelled
+ if len(selected_hashes) > 0 and _CANCEL_DELETION_STR not in selected_hashes:
+ confirm_message = _get_expectations_str(hf_cache_info, selected_hashes) + " Confirm deletion ?"
+
+ # Confirm deletion
+ if self.disable_tui:
+ confirmed = _ask_for_confirmation_no_tui(confirm_message)
+ else:
+ confirmed = _ask_for_confirmation_tui(confirm_message)
+
+ # Deletion is confirmed
+ if confirmed:
+ strategy = hf_cache_info.delete_revisions(*selected_hashes)
+ print("Start deletion.")
+ strategy.execute()
+ print(
+ f"Done. Deleted {len(strategy.repos)} repo(s) and"
+ f" {len(strategy.snapshots)} revision(s) for a total of"
+ f" {strategy.expected_freed_size_str}."
+ )
+ return
+
+ # Deletion is cancelled
+ print("Deletion is cancelled. Do nothing.")
+
+
+@require_inquirer_py
+def _manual_review_tui(hf_cache_info: HFCacheInfo, preselected: List[str]) -> List[str]:
+ """Ask the user for a manual review of the revisions to delete.
+
+ Displays a multi-select menu in the terminal (TUI).
+ """
+ # Define multiselect list
+ choices = _get_tui_choices_from_scan(repos=hf_cache_info.repos, preselected=preselected)
+ checkbox = inquirer.checkbox(
+ message="Select revisions to delete:",
+ choices=choices, # List of revisions with some pre-selection
+ cycle=False, # No loop between top and bottom
+ height=100, # Large list if possible
+ # We use the instruction to display to the user the expected effect of the
+ # deletion.
+ instruction=_get_expectations_str(
+ hf_cache_info,
+ selected_hashes=[c.value for c in choices if isinstance(c, Choice) and c.enabled],
+ ),
+ # We use the long instruction to should keybindings instructions to the user
+ long_instruction="Press to select, to validate and to quit without modification.",
+ # Message that is displayed once the user validates its selection.
+ transformer=lambda result: f"{len(result)} revision(s) selected.",
+ )
+
+ # Add a callback to update the information line when a revision is
+ # selected/unselected
+ def _update_expectations(_) -> None:
+ # Hacky way to dynamically set an instruction message to the checkbox when
+ # a revision hash is selected/unselected.
+ checkbox._instruction = _get_expectations_str(
+ hf_cache_info,
+ selected_hashes=[choice["value"] for choice in checkbox.content_control.choices if choice["enabled"]],
+ )
+
+ checkbox.kb_func_lookup["toggle"].append({"func": _update_expectations})
+
+ # Finally display the form to the user.
+ try:
+ return checkbox.execute()
+ except KeyboardInterrupt:
+ return [] # Quit without deletion
+
+
+@require_inquirer_py
+def _ask_for_confirmation_tui(message: str, default: bool = True) -> bool:
+ """Ask for confirmation using Inquirer."""
+ return inquirer.confirm(message, default=default).execute()
+
+
+def _get_tui_choices_from_scan(repos: Iterable[CachedRepoInfo], preselected: List[str]) -> List:
+ """Build a list of choices from the scanned repos.
+
+ Args:
+ repos (*Iterable[`CachedRepoInfo`]*):
+ List of scanned repos on which we want to delete revisions.
+ preselected (*List[`str`]*):
+ List of revision hashes that will be preselected.
+
+ Return:
+ The list of choices to pass to `inquirer.checkbox`.
+ """
+ choices: List[Union[Choice, Separator]] = []
+
+ # First choice is to cancel the deletion. If selected, nothing will be deleted,
+ # no matter the other selected items.
+ choices.append(
+ Choice(
+ _CANCEL_DELETION_STR,
+ name="None of the following (if selected, nothing will be deleted).",
+ enabled=False,
+ )
+ )
+
+ # Display a separator per repo and a Choice for each revisions of the repo
+ for repo in sorted(repos, key=_repo_sorting_order):
+ # Repo as separator
+ choices.append(
+ Separator(
+ f"\n{repo.repo_type.capitalize()} {repo.repo_id} ({repo.size_on_disk_str},"
+ f" used {repo.last_accessed_str})"
+ )
+ )
+ for revision in sorted(repo.revisions, key=_revision_sorting_order):
+ # Revision as choice
+ choices.append(
+ Choice(
+ revision.commit_hash,
+ name=(
+ f"{revision.commit_hash[:8]}:"
+ f" {', '.join(sorted(revision.refs)) or '(detached)'} #"
+ f" modified {revision.last_modified_str}"
+ ),
+ enabled=revision.commit_hash in preselected,
+ )
+ )
+
+ # Return choices
+ return choices
+
+
+def _manual_review_no_tui(hf_cache_info: HFCacheInfo, preselected: List[str]) -> List[str]:
+ """Ask the user for a manual review of the revisions to delete.
+
+ Used when TUI is disabled. Manual review happens in a separate tmp file that the
+ user can manually edit.
+ """
+ # 1. Generate temporary file with delete commands.
+ fd, tmp_path = mkstemp(suffix=".txt") # suffix to make it easier to find by editors
+ os.close(fd)
+
+ lines = []
+ for repo in sorted(hf_cache_info.repos, key=_repo_sorting_order):
+ lines.append(
+ f"\n# {repo.repo_type.capitalize()} {repo.repo_id} ({repo.size_on_disk_str},"
+ f" used {repo.last_accessed_str})"
+ )
+ for revision in sorted(repo.revisions, key=_revision_sorting_order):
+ lines.append(
+ # Deselect by prepending a '#'
+ f"{'' if revision.commit_hash in preselected else '#'} "
+ f" {revision.commit_hash} # Refs:"
+ # Print `refs` as comment on same line
+ f" {', '.join(sorted(revision.refs)) or '(detached)'} # modified"
+ # Print `last_modified` as comment on same line
+ f" {revision.last_modified_str}"
+ )
+
+ with open(tmp_path, "w") as f:
+ f.write(_MANUAL_REVIEW_NO_TUI_INSTRUCTIONS)
+ f.write("\n".join(lines))
+
+ # 2. Prompt instructions to user.
+ instructions = f"""
+ TUI is disabled. In order to select which revisions you want to delete, please edit
+ the following file using the text editor of your choice. Instructions for manual
+ editing are located at the beginning of the file. Edit the file, save it and confirm
+ to continue.
+ File to edit: {ANSI.bold(tmp_path)}
+ """
+ print("\n".join(line.strip() for line in instructions.strip().split("\n")))
+
+ # 3. Wait for user confirmation.
+ while True:
+ selected_hashes = _read_manual_review_tmp_file(tmp_path)
+ if _ask_for_confirmation_no_tui(
+ _get_expectations_str(hf_cache_info, selected_hashes) + " Continue ?",
+ default=False,
+ ):
+ break
+
+ # 4. Return selected_hashes
+ os.remove(tmp_path)
+ return selected_hashes
+
+
+def _ask_for_confirmation_no_tui(message: str, default: bool = True) -> bool:
+ """Ask for confirmation using pure-python."""
+ YES = ("y", "yes", "1")
+ NO = ("n", "no", "0")
+ DEFAULT = ""
+ ALL = YES + NO + (DEFAULT,)
+ full_message = message + (" (Y/n) " if default else " (y/N) ")
+ while True:
+ answer = input(full_message).lower()
+ if answer == DEFAULT:
+ return default
+ if answer in YES:
+ return True
+ if answer in NO:
+ return False
+ print(f"Invalid input. Must be one of {ALL}")
+
+
+def _get_expectations_str(hf_cache_info: HFCacheInfo, selected_hashes: List[str]) -> str:
+ """Format a string to display to the user how much space would be saved.
+
+ Example:
+ ```
+ >>> _get_expectations_str(hf_cache_info, selected_hashes)
+ '7 revisions selected counting for 4.3G.'
+ ```
+ """
+ if _CANCEL_DELETION_STR in selected_hashes:
+ return "Nothing will be deleted."
+ strategy = hf_cache_info.delete_revisions(*selected_hashes)
+ return f"{len(selected_hashes)} revisions selected counting for {strategy.expected_freed_size_str}."
+
+
+def _read_manual_review_tmp_file(tmp_path: str) -> List[str]:
+ """Read the manually reviewed instruction file and return a list of revision hash.
+
+ Example:
+ ```txt
+ # This is the tmp file content
+ ###
+
+ # Commented out line
+ 123456789 # revision hash
+
+ # Something else
+ # a_newer_hash # 2 days ago
+ an_older_hash # 3 days ago
+ ```
+
+ ```py
+ >>> _read_manual_review_tmp_file(tmp_path)
+ ['123456789', 'an_older_hash']
+ ```
+ """
+ with open(tmp_path) as f:
+ content = f.read()
+
+ # Split lines
+ lines = [line.strip() for line in content.split("\n")]
+
+ # Filter commented lines
+ selected_lines = [line for line in lines if not line.startswith("#")]
+
+ # Select only before comment
+ selected_hashes = [line.split("#")[0].strip() for line in selected_lines]
+
+ # Return revision hashes
+ return [hash for hash in selected_hashes if len(hash) > 0]
+
+
+_MANUAL_REVIEW_NO_TUI_INSTRUCTIONS = f"""
+# INSTRUCTIONS
+# ------------
+# This is a temporary file created by running `huggingface-cli delete-cache` with the
+# `--disable-tui` option. It contains a set of revisions that can be deleted from your
+# local cache directory.
+#
+# Please manually review the revisions you want to delete:
+# - Revision hashes can be commented out with '#'.
+# - Only non-commented revisions in this file will be deleted.
+# - Revision hashes that are removed from this file are ignored as well.
+# - If `{_CANCEL_DELETION_STR}` line is uncommented, the all cache deletion is cancelled and
+# no changes will be applied.
+#
+# Once you've manually reviewed this file, please confirm deletion in the terminal. This
+# file will be automatically removed once done.
+# ------------
+
+# KILL SWITCH
+# ------------
+# Un-comment following line to completely cancel the deletion process
+# {_CANCEL_DELETION_STR}
+# ------------
+
+# REVISIONS
+# ------------
+""".strip()
+
+
+def _repo_sorting_order(repo: CachedRepoInfo) -> Any:
+ # First split by Dataset/Model, then sort by last accessed (oldest first)
+ return (repo.repo_type, repo.last_accessed)
+
+
+def _revision_sorting_order(revision: CachedRevisionInfo) -> Any:
+ # Sort by last modified (oldest first)
+ return revision.last_modified
diff --git a/env/Lib/site-packages/huggingface_hub/commands/download.py b/env/Lib/site-packages/huggingface_hub/commands/download.py
new file mode 100644
index 0000000000000000000000000000000000000000..10e22c3d1eb83dbb52c4a633fb66f19b3f35d8e7
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/download.py
@@ -0,0 +1,200 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to download files from the Hub with the CLI.
+
+Usage:
+ huggingface-cli download --help
+
+ # Download file
+ huggingface-cli download gpt2 config.json
+
+ # Download entire repo
+ huggingface-cli download fffiloni/zeroscope --repo-type=space --revision=refs/pr/78
+
+ # Download repo with filters
+ huggingface-cli download gpt2 --include="*.safetensors"
+
+ # Download with token
+ huggingface-cli download Wauplin/private-model --token=hf_***
+
+ # Download quietly (no progress bar, no warnings, only the returned path)
+ huggingface-cli download gpt2 config.json --quiet
+
+ # Download to local dir
+ huggingface-cli download gpt2 --local-dir=./models/gpt2
+"""
+
+import warnings
+from argparse import Namespace, _SubParsersAction
+from typing import List, Optional
+
+from huggingface_hub import logging
+from huggingface_hub._snapshot_download import snapshot_download
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.file_download import hf_hub_download
+from huggingface_hub.utils import disable_progress_bars, enable_progress_bars
+
+
+logger = logging.get_logger(__name__)
+
+
+class DownloadCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ download_parser = parser.add_parser("download", help="Download files from the Hub")
+ download_parser.add_argument(
+ "repo_id", type=str, help="ID of the repo to download from (e.g. `username/repo-name`)."
+ )
+ download_parser.add_argument(
+ "filenames", type=str, nargs="*", help="Files to download (e.g. `config.json`, `data/metadata.jsonl`)."
+ )
+ download_parser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ default="model",
+ help="Type of repo to download from (defaults to 'model').",
+ )
+ download_parser.add_argument(
+ "--revision",
+ type=str,
+ help="An optional Git revision id which can be a branch name, a tag, or a commit hash.",
+ )
+ download_parser.add_argument(
+ "--include", nargs="*", type=str, help="Glob patterns to match files to download."
+ )
+ download_parser.add_argument(
+ "--exclude", nargs="*", type=str, help="Glob patterns to exclude from files to download."
+ )
+ download_parser.add_argument(
+ "--cache-dir", type=str, help="Path to the directory where to save the downloaded files."
+ )
+ download_parser.add_argument(
+ "--local-dir",
+ type=str,
+ help=(
+ "If set, the downloaded file will be placed under this directory. Check out"
+ " https://huggingface.co/docs/huggingface_hub/guides/download#download-files-to-local-folder for more"
+ " details."
+ ),
+ )
+ download_parser.add_argument(
+ "--local-dir-use-symlinks",
+ choices=["auto", "True", "False"],
+ help=("Deprecated and ignored. Downloading to a local directory does not use symlinks anymore."),
+ )
+ download_parser.add_argument(
+ "--force-download",
+ action="store_true",
+ help="If True, the files will be downloaded even if they are already cached.",
+ )
+ download_parser.add_argument(
+ "--resume-download",
+ action="store_true",
+ help="Deprecated and ignored. Downloading a file to local dir always attempts to resume previously interrupted downloads (unless hf-transfer is enabled).",
+ )
+ download_parser.add_argument(
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
+ )
+ download_parser.add_argument(
+ "--quiet",
+ action="store_true",
+ help="If True, progress bars are disabled and only the path to the download files is printed.",
+ )
+ download_parser.add_argument(
+ "--max-workers",
+ type=int,
+ default=8,
+ help="Maximum number of workers to use for downloading files. Default is 8.",
+ )
+ download_parser.set_defaults(func=DownloadCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.token = args.token
+ self.repo_id: str = args.repo_id
+ self.filenames: List[str] = args.filenames
+ self.repo_type: str = args.repo_type
+ self.revision: Optional[str] = args.revision
+ self.include: Optional[List[str]] = args.include
+ self.exclude: Optional[List[str]] = args.exclude
+ self.cache_dir: Optional[str] = args.cache_dir
+ self.local_dir: Optional[str] = args.local_dir
+ self.force_download: bool = args.force_download
+ self.resume_download: Optional[bool] = args.resume_download or None
+ self.quiet: bool = args.quiet
+ self.max_workers: int = args.max_workers
+
+ if args.local_dir_use_symlinks is not None:
+ warnings.warn(
+ "Ignoring --local-dir-use-symlinks. Downloading to a local directory does not use symlinks anymore.",
+ FutureWarning,
+ )
+
+ def run(self) -> None:
+ if self.quiet:
+ disable_progress_bars()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ print(self._download()) # Print path to downloaded files
+ enable_progress_bars()
+ else:
+ logging.set_verbosity_info()
+ print(self._download()) # Print path to downloaded files
+ logging.set_verbosity_warning()
+
+ def _download(self) -> str:
+ # Warn user if patterns are ignored
+ if len(self.filenames) > 0:
+ if self.include is not None and len(self.include) > 0:
+ warnings.warn("Ignoring `--include` since filenames have being explicitly set.")
+ if self.exclude is not None and len(self.exclude) > 0:
+ warnings.warn("Ignoring `--exclude` since filenames have being explicitly set.")
+
+ # Single file to download: use `hf_hub_download`
+ if len(self.filenames) == 1:
+ return hf_hub_download(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ filename=self.filenames[0],
+ cache_dir=self.cache_dir,
+ resume_download=self.resume_download,
+ force_download=self.force_download,
+ token=self.token,
+ local_dir=self.local_dir,
+ library_name="huggingface-cli",
+ )
+
+ # Otherwise: use `snapshot_download` to ensure all files comes from same revision
+ elif len(self.filenames) == 0:
+ allow_patterns = self.include
+ ignore_patterns = self.exclude
+ else:
+ allow_patterns = self.filenames
+ ignore_patterns = None
+
+ return snapshot_download(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ resume_download=self.resume_download,
+ force_download=self.force_download,
+ cache_dir=self.cache_dir,
+ token=self.token,
+ local_dir=self.local_dir,
+ library_name="huggingface-cli",
+ max_workers=self.max_workers,
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/commands/env.py b/env/Lib/site-packages/huggingface_hub/commands/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..23f2828bbfebda0a633b4b3c6883432e4a534c79
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/env.py
@@ -0,0 +1,36 @@
+# Copyright 2022 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to print information about the environment.
+
+Usage:
+ huggingface-cli env
+"""
+
+from argparse import _SubParsersAction
+
+from ..utils import dump_environment_info
+from . import BaseHuggingfaceCLICommand
+
+
+class EnvironmentCommand(BaseHuggingfaceCLICommand):
+ def __init__(self, args):
+ self.args = args
+
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ env_parser = parser.add_parser("env", help="Print information about the environment.")
+ env_parser.set_defaults(func=EnvironmentCommand)
+
+ def run(self) -> None:
+ dump_environment_info()
diff --git a/env/Lib/site-packages/huggingface_hub/commands/huggingface_cli.py b/env/Lib/site-packages/huggingface_hub/commands/huggingface_cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e790b5eb1b40710072ef5fc2597b9e6c3325355
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/huggingface_cli.py
@@ -0,0 +1,61 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from argparse import ArgumentParser
+
+from huggingface_hub.commands.delete_cache import DeleteCacheCommand
+from huggingface_hub.commands.download import DownloadCommand
+from huggingface_hub.commands.env import EnvironmentCommand
+from huggingface_hub.commands.lfs import LfsCommands
+from huggingface_hub.commands.repo_files import RepoFilesCommand
+from huggingface_hub.commands.scan_cache import ScanCacheCommand
+from huggingface_hub.commands.tag import TagCommands
+from huggingface_hub.commands.upload import UploadCommand
+from huggingface_hub.commands.upload_large_folder import UploadLargeFolderCommand
+from huggingface_hub.commands.user import UserCommands
+from huggingface_hub.commands.version import VersionCommand
+
+
+def main():
+ parser = ArgumentParser("huggingface-cli", usage="huggingface-cli []")
+ commands_parser = parser.add_subparsers(help="huggingface-cli command helpers")
+
+ # Register commands
+ DownloadCommand.register_subcommand(commands_parser)
+ UploadCommand.register_subcommand(commands_parser)
+ RepoFilesCommand.register_subcommand(commands_parser)
+ EnvironmentCommand.register_subcommand(commands_parser)
+ UserCommands.register_subcommand(commands_parser)
+ LfsCommands.register_subcommand(commands_parser)
+ ScanCacheCommand.register_subcommand(commands_parser)
+ DeleteCacheCommand.register_subcommand(commands_parser)
+ TagCommands.register_subcommand(commands_parser)
+ VersionCommand.register_subcommand(commands_parser)
+
+ # Experimental
+ UploadLargeFolderCommand.register_subcommand(commands_parser)
+
+ # Let's go
+ args = parser.parse_args()
+ if not hasattr(args, "func"):
+ parser.print_help()
+ exit(1)
+
+ # Run
+ service = args.func(args)
+ service.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/env/Lib/site-packages/huggingface_hub/commands/lfs.py b/env/Lib/site-packages/huggingface_hub/commands/lfs.py
new file mode 100644
index 0000000000000000000000000000000000000000..e510e345e6a4bf6da03f71b35cbfa2a4f0eb7325
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/lfs.py
@@ -0,0 +1,200 @@
+"""
+Implementation of a custom transfer agent for the transfer type "multipart" for
+git-lfs.
+
+Inspired by:
+github.com/cbartz/git-lfs-swift-transfer-agent/blob/master/git_lfs_swift_transfer.py
+
+Spec is: github.com/git-lfs/git-lfs/blob/master/docs/custom-transfers.md
+
+
+To launch debugger while developing:
+
+``` [lfs "customtransfer.multipart"]
+path = /path/to/huggingface_hub/.env/bin/python args = -m debugpy --listen 5678
+--wait-for-client
+/path/to/huggingface_hub/src/huggingface_hub/commands/huggingface_cli.py
+lfs-multipart-upload ```"""
+
+import json
+import os
+import subprocess
+import sys
+from argparse import _SubParsersAction
+from typing import Dict, List, Optional
+
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.lfs import LFS_MULTIPART_UPLOAD_COMMAND
+
+from ..utils import get_session, hf_raise_for_status, logging
+from ..utils._lfs import SliceFileObj
+
+
+logger = logging.get_logger(__name__)
+
+
+class LfsCommands(BaseHuggingfaceCLICommand):
+ """
+ Implementation of a custom transfer agent for the transfer type "multipart"
+ for git-lfs. This lets users upload large files >5GB 🔥. Spec for LFS custom
+ transfer agent is:
+ https://github.com/git-lfs/git-lfs/blob/master/docs/custom-transfers.md
+
+ This introduces two commands to the CLI:
+
+ 1. $ huggingface-cli lfs-enable-largefiles
+
+ This should be executed once for each model repo that contains a model file
+ >5GB. It's documented in the error message you get if you just try to git
+ push a 5GB file without having enabled it before.
+
+ 2. $ huggingface-cli lfs-multipart-upload
+
+ This command is called by lfs directly and is not meant to be called by the
+ user.
+ """
+
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ enable_parser = parser.add_parser(
+ "lfs-enable-largefiles", help="Configure your repository to enable upload of files > 5GB."
+ )
+ enable_parser.add_argument("path", type=str, help="Local path to repository you want to configure.")
+ enable_parser.set_defaults(func=lambda args: LfsEnableCommand(args))
+
+ # Command will get called by git-lfs, do not call it directly.
+ upload_parser = parser.add_parser(LFS_MULTIPART_UPLOAD_COMMAND, add_help=False)
+ upload_parser.set_defaults(func=lambda args: LfsUploadCommand(args))
+
+
+class LfsEnableCommand:
+ def __init__(self, args):
+ self.args = args
+
+ def run(self):
+ local_path = os.path.abspath(self.args.path)
+ if not os.path.isdir(local_path):
+ print("This does not look like a valid git repo.")
+ exit(1)
+ subprocess.run(
+ "git config lfs.customtransfer.multipart.path huggingface-cli".split(),
+ check=True,
+ cwd=local_path,
+ )
+ subprocess.run(
+ f"git config lfs.customtransfer.multipart.args {LFS_MULTIPART_UPLOAD_COMMAND}".split(),
+ check=True,
+ cwd=local_path,
+ )
+ print("Local repo set up for largefiles")
+
+
+def write_msg(msg: Dict):
+ """Write out the message in Line delimited JSON."""
+ msg_str = json.dumps(msg) + "\n"
+ sys.stdout.write(msg_str)
+ sys.stdout.flush()
+
+
+def read_msg() -> Optional[Dict]:
+ """Read Line delimited JSON from stdin."""
+ msg = json.loads(sys.stdin.readline().strip())
+
+ if "terminate" in (msg.get("type"), msg.get("event")):
+ # terminate message received
+ return None
+
+ if msg.get("event") not in ("download", "upload"):
+ logger.critical("Received unexpected message")
+ sys.exit(1)
+
+ return msg
+
+
+class LfsUploadCommand:
+ def __init__(self, args) -> None:
+ self.args = args
+
+ def run(self) -> None:
+ # Immediately after invoking a custom transfer process, git-lfs
+ # sends initiation data to the process over stdin.
+ # This tells the process useful information about the configuration.
+ init_msg = json.loads(sys.stdin.readline().strip())
+ if not (init_msg.get("event") == "init" and init_msg.get("operation") == "upload"):
+ write_msg({"error": {"code": 32, "message": "Wrong lfs init operation"}})
+ sys.exit(1)
+
+ # The transfer process should use the information it needs from the
+ # initiation structure, and also perform any one-off setup tasks it
+ # needs to do. It should then respond on stdout with a simple empty
+ # confirmation structure, as follows:
+ write_msg({})
+
+ # After the initiation exchange, git-lfs will send any number of
+ # transfer requests to the stdin of the transfer process, in a serial sequence.
+ while True:
+ msg = read_msg()
+ if msg is None:
+ # When all transfers have been processed, git-lfs will send
+ # a terminate event to the stdin of the transfer process.
+ # On receiving this message the transfer process should
+ # clean up and terminate. No response is expected.
+ sys.exit(0)
+
+ oid = msg["oid"]
+ filepath = msg["path"]
+ completion_url = msg["action"]["href"]
+ header = msg["action"]["header"]
+ chunk_size = int(header.pop("chunk_size"))
+ presigned_urls: List[str] = list(header.values())
+
+ # Send a "started" progress event to allow other workers to start.
+ # Otherwise they're delayed until first "progress" event is reported,
+ # i.e. after the first 5GB by default (!)
+ write_msg(
+ {
+ "event": "progress",
+ "oid": oid,
+ "bytesSoFar": 1,
+ "bytesSinceLast": 0,
+ }
+ )
+
+ parts = []
+ with open(filepath, "rb") as file:
+ for i, presigned_url in enumerate(presigned_urls):
+ with SliceFileObj(
+ file,
+ seek_from=i * chunk_size,
+ read_limit=chunk_size,
+ ) as data:
+ r = get_session().put(presigned_url, data=data)
+ hf_raise_for_status(r)
+ parts.append(
+ {
+ "etag": r.headers.get("etag"),
+ "partNumber": i + 1,
+ }
+ )
+ # In order to support progress reporting while data is uploading / downloading,
+ # the transfer process should post messages to stdout
+ write_msg(
+ {
+ "event": "progress",
+ "oid": oid,
+ "bytesSoFar": (i + 1) * chunk_size,
+ "bytesSinceLast": chunk_size,
+ }
+ )
+ # Not precise but that's ok.
+
+ r = get_session().post(
+ completion_url,
+ json={
+ "oid": oid,
+ "parts": parts,
+ },
+ )
+ hf_raise_for_status(r)
+
+ write_msg({"event": "complete", "oid": oid})
diff --git a/env/Lib/site-packages/huggingface_hub/commands/repo_files.py b/env/Lib/site-packages/huggingface_hub/commands/repo_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..f15bbed04f3634d7783d8230324cdaee44df4f59
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/repo_files.py
@@ -0,0 +1,128 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to update or delete files in a repository using the CLI.
+
+Usage:
+ # delete all
+ huggingface-cli repo-files delete "*"
+
+ # delete single file
+ huggingface-cli repo-files delete file.txt
+
+ # delete single folder
+ huggingface-cli repo-files delete folder/
+
+ # delete multiple
+ huggingface-cli repo-files delete file.txt folder/ file2.txt
+
+ # delete multiple patterns
+ huggingface-cli repo-files delete file.txt "*.json" "folder/*.parquet"
+
+ # delete from different revision / repo-type
+ huggingface-cli repo-files delete file.txt --revision=refs/pr/1 --repo-type=dataset
+"""
+
+from argparse import _SubParsersAction
+from typing import List, Optional
+
+from huggingface_hub import logging
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.hf_api import HfApi
+
+
+logger = logging.get_logger(__name__)
+
+
+class DeleteFilesSubCommand:
+ def __init__(self, args) -> None:
+ self.args = args
+ self.repo_id: str = args.repo_id
+ self.repo_type: Optional[str] = args.repo_type
+ self.revision: Optional[str] = args.revision
+ self.api: HfApi = HfApi(token=args.token, library_name="huggingface-cli")
+ self.patterns: List[str] = args.patterns
+ self.commit_message: Optional[str] = args.commit_message
+ self.commit_description: Optional[str] = args.commit_description
+ self.create_pr: bool = args.create_pr
+ self.token: Optional[str] = args.token
+
+ def run(self) -> None:
+ logging.set_verbosity_info()
+ url = self.api.delete_files(
+ delete_patterns=self.patterns,
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ commit_message=self.commit_message,
+ commit_description=self.commit_description,
+ create_pr=self.create_pr,
+ )
+ print(f"Files correctly deleted from repo. Commit: {url}.")
+ logging.set_verbosity_warning()
+
+
+class RepoFilesCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ repo_files_parser = parser.add_parser("repo-files", help="Manage files in a repo on the Hub")
+ repo_files_parser.add_argument(
+ "repo_id", type=str, help="The ID of the repo to manage (e.g. `username/repo-name`)."
+ )
+ repo_files_subparsers = repo_files_parser.add_subparsers(
+ help="Action to execute against the files.",
+ required=True,
+ )
+ delete_subparser = repo_files_subparsers.add_parser(
+ "delete",
+ help="Delete files from a repo on the Hub",
+ )
+ delete_subparser.set_defaults(func=lambda args: DeleteFilesSubCommand(args))
+ delete_subparser.add_argument(
+ "patterns",
+ nargs="+",
+ type=str,
+ help="Glob patterns to match files to delete.",
+ )
+ delete_subparser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ default="model",
+ help="Type of the repo to upload to (e.g. `dataset`).",
+ )
+ delete_subparser.add_argument(
+ "--revision",
+ type=str,
+ help=(
+ "An optional Git revision to push to. It can be a branch name "
+ "or a PR reference. If revision does not"
+ " exist and `--create-pr` is not set, a branch will be automatically created."
+ ),
+ )
+ delete_subparser.add_argument(
+ "--commit-message", type=str, help="The summary / title / first line of the generated commit."
+ )
+ delete_subparser.add_argument(
+ "--commit-description", type=str, help="The description of the generated commit."
+ )
+ delete_subparser.add_argument(
+ "--create-pr", action="store_true", help="Whether to create a new Pull Request for these changes."
+ )
+ repo_files_parser.add_argument(
+ "--token",
+ type=str,
+ help="A User Access Token generated from https://huggingface.co/settings/tokens",
+ )
+
+ repo_files_parser.set_defaults(func=RepoFilesCommand)
diff --git a/env/Lib/site-packages/huggingface_hub/commands/scan_cache.py b/env/Lib/site-packages/huggingface_hub/commands/scan_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..799b9ba5523134a668aa0171e9f3668694299341
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/scan_cache.py
@@ -0,0 +1,181 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to scan the HF cache directory.
+
+Usage:
+ huggingface-cli scan-cache
+ huggingface-cli scan-cache -v
+ huggingface-cli scan-cache -vvv
+ huggingface-cli scan-cache --dir ~/.cache/huggingface/hub
+"""
+
+import time
+from argparse import Namespace, _SubParsersAction
+from typing import Optional
+
+from ..utils import CacheNotFound, HFCacheInfo, scan_cache_dir
+from . import BaseHuggingfaceCLICommand
+from ._cli_utils import ANSI, tabulate
+
+
+class ScanCacheCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ scan_cache_parser = parser.add_parser("scan-cache", help="Scan cache directory.")
+
+ scan_cache_parser.add_argument(
+ "--dir",
+ type=str,
+ default=None,
+ help="cache directory to scan (optional). Default to the default HuggingFace cache.",
+ )
+ scan_cache_parser.add_argument(
+ "-v",
+ "--verbose",
+ action="count",
+ default=0,
+ help="show a more verbose output",
+ )
+ scan_cache_parser.set_defaults(func=ScanCacheCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.verbosity: int = args.verbose
+ self.cache_dir: Optional[str] = args.dir
+
+ def run(self):
+ try:
+ t0 = time.time()
+ hf_cache_info = scan_cache_dir(self.cache_dir)
+ t1 = time.time()
+ except CacheNotFound as exc:
+ cache_dir = exc.cache_dir
+ print(f"Cache directory not found: {cache_dir}")
+ return
+
+ self._print_hf_cache_info_as_table(hf_cache_info)
+
+ print(
+ f"\nDone in {round(t1 - t0, 1)}s. Scanned {len(hf_cache_info.repos)} repo(s)"
+ f" for a total of {ANSI.red(hf_cache_info.size_on_disk_str)}."
+ )
+ if len(hf_cache_info.warnings) > 0:
+ message = f"Got {len(hf_cache_info.warnings)} warning(s) while scanning."
+ if self.verbosity >= 3:
+ print(ANSI.gray(message))
+ for warning in hf_cache_info.warnings:
+ print(ANSI.gray(warning))
+ else:
+ print(ANSI.gray(message + " Use -vvv to print details."))
+
+ def _print_hf_cache_info_as_table(self, hf_cache_info: HFCacheInfo) -> None:
+ print(get_table(hf_cache_info, verbosity=self.verbosity))
+
+
+def get_table(hf_cache_info: HFCacheInfo, *, verbosity: int = 0) -> str:
+ """Generate a table from the [`HFCacheInfo`] object.
+
+ Pass `verbosity=0` to get a table with a single row per repo, with columns
+ "repo_id", "repo_type", "size_on_disk", "nb_files", "last_accessed", "last_modified", "refs", "local_path".
+
+ Pass `verbosity=1` to get a table with a row per repo and revision (thus multiple rows can appear for a single repo), with columns
+ "repo_id", "repo_type", "revision", "size_on_disk", "nb_files", "last_modified", "refs", "local_path".
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import scan_cache_dir
+ >>> from huggingface_hub.commands.scan_cache import get_table
+
+ >>> hf_cache_info = scan_cache_dir()
+ HFCacheInfo(...)
+
+ >>> print(get_table(hf_cache_info, verbosity=0))
+ REPO ID REPO TYPE SIZE ON DISK NB FILES LAST_ACCESSED LAST_MODIFIED REFS LOCAL PATH
+ --------------------------------------------------- --------- ------------ -------- ------------- ------------- ---- --------------------------------------------------------------------------------------------------
+ roberta-base model 2.7M 5 1 day ago 1 week ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--roberta-base
+ suno/bark model 8.8K 1 1 week ago 1 week ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--suno--bark
+ t5-base model 893.8M 4 4 days ago 7 months ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--t5-base
+ t5-large model 3.0G 4 5 weeks ago 5 months ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--t5-large
+
+ >>> print(get_table(hf_cache_info, verbosity=1))
+ REPO ID REPO TYPE REVISION SIZE ON DISK NB FILES LAST_MODIFIED REFS LOCAL PATH
+ --------------------------------------------------- --------- ---------------------------------------- ------------ -------- ------------- ---- -----------------------------------------------------------------------------------------------------------------------------------------------------
+ roberta-base model e2da8e2f811d1448a5b465c236feacd80ffbac7b 2.7M 5 1 week ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--roberta-base\\snapshots\\e2da8e2f811d1448a5b465c236feacd80ffbac7b
+ suno/bark model 70a8a7d34168586dc5d028fa9666aceade177992 8.8K 1 1 week ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--suno--bark\\snapshots\\70a8a7d34168586dc5d028fa9666aceade177992
+ t5-base model a9723ea7f1b39c1eae772870f3b547bf6ef7e6c1 893.8M 4 7 months ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--t5-base\\snapshots\\a9723ea7f1b39c1eae772870f3b547bf6ef7e6c1
+ t5-large model 150ebc2c4b72291e770f58e6057481c8d2ed331a 3.0G 4 5 months ago main C:\\Users\\admin\\.cache\\huggingface\\hub\\models--t5-large\\snapshots\\150ebc2c4b72291e770f58e6057481c8d2ed331a ```
+ ```
+
+ Args:
+ hf_cache_info ([`HFCacheInfo`]):
+ The HFCacheInfo object to print.
+ verbosity (`int`, *optional*):
+ The verbosity level. Defaults to 0.
+
+ Returns:
+ `str`: The table as a string.
+ """
+ if verbosity == 0:
+ return tabulate(
+ rows=[
+ [
+ repo.repo_id,
+ repo.repo_type,
+ "{:>12}".format(repo.size_on_disk_str),
+ repo.nb_files,
+ repo.last_accessed_str,
+ repo.last_modified_str,
+ ", ".join(sorted(repo.refs)),
+ str(repo.repo_path),
+ ]
+ for repo in sorted(hf_cache_info.repos, key=lambda repo: repo.repo_path)
+ ],
+ headers=[
+ "REPO ID",
+ "REPO TYPE",
+ "SIZE ON DISK",
+ "NB FILES",
+ "LAST_ACCESSED",
+ "LAST_MODIFIED",
+ "REFS",
+ "LOCAL PATH",
+ ],
+ )
+ else:
+ return tabulate(
+ rows=[
+ [
+ repo.repo_id,
+ repo.repo_type,
+ revision.commit_hash,
+ "{:>12}".format(revision.size_on_disk_str),
+ revision.nb_files,
+ revision.last_modified_str,
+ ", ".join(sorted(revision.refs)),
+ str(revision.snapshot_path),
+ ]
+ for repo in sorted(hf_cache_info.repos, key=lambda repo: repo.repo_path)
+ for revision in sorted(repo.revisions, key=lambda revision: revision.commit_hash)
+ ],
+ headers=[
+ "REPO ID",
+ "REPO TYPE",
+ "REVISION",
+ "SIZE ON DISK",
+ "NB FILES",
+ "LAST_MODIFIED",
+ "REFS",
+ "LOCAL PATH",
+ ],
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/commands/tag.py b/env/Lib/site-packages/huggingface_hub/commands/tag.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3beab90a0a2858906c848fd1e3f54edfb9d4864
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/tag.py
@@ -0,0 +1,159 @@
+# coding=utf-8
+# Copyright 2024-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Contains commands to perform tag management with the CLI.
+
+Usage Examples:
+ - Create a tag:
+ $ huggingface-cli tag user/my-model 1.0 --message "First release"
+ $ huggingface-cli tag user/my-model 1.0 -m "First release" --revision develop
+ $ huggingface-cli tag user/my-dataset 1.0 -m "First release" --repo-type dataset
+ $ huggingface-cli tag user/my-space 1.0
+ - List all tags:
+ $ huggingface-cli tag -l user/my-model
+ $ huggingface-cli tag --list user/my-dataset --repo-type dataset
+ - Delete a tag:
+ $ huggingface-cli tag -d user/my-model 1.0
+ $ huggingface-cli tag --delete user/my-dataset 1.0 --repo-type dataset
+ $ huggingface-cli tag -d user/my-space 1.0 -y
+"""
+
+from argparse import Namespace, _SubParsersAction
+
+from requests.exceptions import HTTPError
+
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.constants import (
+ REPO_TYPES,
+)
+from huggingface_hub.hf_api import HfApi
+
+from ..errors import HfHubHTTPError, RepositoryNotFoundError, RevisionNotFoundError
+from ._cli_utils import ANSI
+
+
+class TagCommands(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ tag_parser = parser.add_parser("tag", help="(create, list, delete) tags for a repo in the hub")
+
+ tag_parser.add_argument("repo_id", type=str, help="The ID of the repo to tag (e.g. `username/repo-name`).")
+ tag_parser.add_argument("tag", nargs="?", type=str, help="The name of the tag for creation or deletion.")
+ tag_parser.add_argument("-m", "--message", type=str, help="The description of the tag to create.")
+ tag_parser.add_argument("--revision", type=str, help="The git revision to tag.")
+ tag_parser.add_argument(
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens."
+ )
+ tag_parser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ default="model",
+ help="Set the type of repository (model, dataset, or space).",
+ )
+ tag_parser.add_argument("-y", "--yes", action="store_true", help="Answer Yes to prompts automatically.")
+
+ tag_parser.add_argument("-l", "--list", action="store_true", help="List tags for a repository.")
+ tag_parser.add_argument("-d", "--delete", action="store_true", help="Delete a tag for a repository.")
+
+ tag_parser.set_defaults(func=lambda args: handle_commands(args))
+
+
+def handle_commands(args: Namespace):
+ if args.list:
+ return TagListCommand(args)
+ elif args.delete:
+ return TagDeleteCommand(args)
+ else:
+ return TagCreateCommand(args)
+
+
+class TagCommand:
+ def __init__(self, args: Namespace):
+ self.args = args
+ self.api = HfApi(token=self.args.token)
+ self.repo_id = self.args.repo_id
+ self.repo_type = self.args.repo_type
+ if self.repo_type not in REPO_TYPES:
+ print("Invalid repo --repo-type")
+ exit(1)
+
+
+class TagCreateCommand(TagCommand):
+ def run(self):
+ print(f"You are about to create tag {ANSI.bold(self.args.tag)} on {self.repo_type} {ANSI.bold(self.repo_id)}")
+
+ try:
+ self.api.create_tag(
+ repo_id=self.repo_id,
+ tag=self.args.tag,
+ tag_message=self.args.message,
+ revision=self.args.revision,
+ repo_type=self.repo_type,
+ )
+ except RepositoryNotFoundError:
+ print(f"{self.repo_type.capitalize()} {ANSI.bold(self.repo_id)} not found.")
+ exit(1)
+ except RevisionNotFoundError:
+ print(f"Revision {ANSI.bold(self.args.revision)} not found.")
+ exit(1)
+ except HfHubHTTPError as e:
+ if e.response.status_code == 409:
+ print(f"Tag {ANSI.bold(self.args.tag)} already exists on {ANSI.bold(self.repo_id)}")
+ exit(1)
+ raise e
+
+ print(f"Tag {ANSI.bold(self.args.tag)} created on {ANSI.bold(self.repo_id)}")
+
+
+class TagListCommand(TagCommand):
+ def run(self):
+ try:
+ refs = self.api.list_repo_refs(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ )
+ except RepositoryNotFoundError:
+ print(f"{self.repo_type.capitalize()} {ANSI.bold(self.repo_id)} not found.")
+ exit(1)
+ except HTTPError as e:
+ print(e)
+ print(ANSI.red(e.response.text))
+ exit(1)
+ if len(refs.tags) == 0:
+ print("No tags found")
+ exit(0)
+ print(f"Tags for {self.repo_type} {ANSI.bold(self.repo_id)}:")
+ for tag in refs.tags:
+ print(tag.name)
+
+
+class TagDeleteCommand(TagCommand):
+ def run(self):
+ print(f"You are about to delete tag {ANSI.bold(self.args.tag)} on {self.repo_type} {ANSI.bold(self.repo_id)}")
+
+ if not self.args.yes:
+ choice = input("Proceed? [Y/n] ").lower()
+ if choice not in ("", "y", "yes"):
+ print("Abort")
+ exit()
+ try:
+ self.api.delete_tag(repo_id=self.repo_id, tag=self.args.tag, repo_type=self.repo_type)
+ except RepositoryNotFoundError:
+ print(f"{self.repo_type.capitalize()} {ANSI.bold(self.repo_id)} not found.")
+ exit(1)
+ except RevisionNotFoundError:
+ print(f"Tag {ANSI.bold(self.args.tag)} not found on {ANSI.bold(self.repo_id)}")
+ exit(1)
+ print(f"Tag {ANSI.bold(self.args.tag)} deleted on {ANSI.bold(self.repo_id)}")
diff --git a/env/Lib/site-packages/huggingface_hub/commands/upload.py b/env/Lib/site-packages/huggingface_hub/commands/upload.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5db1118e3cc562c2d09b06b11611bbaee6d53c2
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/upload.py
@@ -0,0 +1,299 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to upload a repo or file with the CLI.
+
+Usage:
+ # Upload file (implicit)
+ huggingface-cli upload my-cool-model ./my-cool-model.safetensors
+
+ # Upload file (explicit)
+ huggingface-cli upload my-cool-model ./my-cool-model.safetensors model.safetensors
+
+ # Upload directory (implicit). If `my-cool-model/` is a directory it will be uploaded, otherwise an exception is raised.
+ huggingface-cli upload my-cool-model
+
+ # Upload directory (explicit)
+ huggingface-cli upload my-cool-model ./models/my-cool-model .
+
+ # Upload filtered directory (example: tensorboard logs except for the last run)
+ huggingface-cli upload my-cool-model ./model/training /logs --include "*.tfevents.*" --exclude "*20230905*"
+
+ # Upload private dataset
+ huggingface-cli upload Wauplin/my-cool-dataset ./data . --repo-type=dataset --private
+
+ # Upload with token
+ huggingface-cli upload Wauplin/my-cool-model --token=hf_****
+
+ # Sync local Space with Hub (upload new files, delete removed files)
+ huggingface-cli upload Wauplin/space-example --repo-type=space --exclude="/logs/*" --delete="*" --commit-message="Sync local Space with Hub"
+
+ # Schedule commits every 30 minutes
+ huggingface-cli upload Wauplin/my-cool-model --every=30
+"""
+
+import os
+import time
+import warnings
+from argparse import Namespace, _SubParsersAction
+from typing import List, Optional
+
+from huggingface_hub import logging
+from huggingface_hub._commit_scheduler import CommitScheduler
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.constants import HF_HUB_ENABLE_HF_TRANSFER
+from huggingface_hub.errors import RevisionNotFoundError
+from huggingface_hub.hf_api import HfApi
+from huggingface_hub.utils import disable_progress_bars, enable_progress_bars
+
+
+logger = logging.get_logger(__name__)
+
+
+class UploadCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ upload_parser = parser.add_parser("upload", help="Upload a file or a folder to a repo on the Hub")
+ upload_parser.add_argument(
+ "repo_id", type=str, help="The ID of the repo to upload to (e.g. `username/repo-name`)."
+ )
+ upload_parser.add_argument(
+ "local_path", nargs="?", help="Local path to the file or folder to upload. Defaults to current directory."
+ )
+ upload_parser.add_argument(
+ "path_in_repo",
+ nargs="?",
+ help="Path of the file or folder in the repo. Defaults to the relative path of the file or folder.",
+ )
+ upload_parser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ default="model",
+ help="Type of the repo to upload to (e.g. `dataset`).",
+ )
+ upload_parser.add_argument(
+ "--revision",
+ type=str,
+ help=(
+ "An optional Git revision to push to. It can be a branch name or a PR reference. If revision does not"
+ " exist and `--create-pr` is not set, a branch will be automatically created."
+ ),
+ )
+ upload_parser.add_argument(
+ "--private",
+ action="store_true",
+ help=(
+ "Whether to create a private repo if repo doesn't exist on the Hub. Ignored if the repo already"
+ " exists."
+ ),
+ )
+ upload_parser.add_argument("--include", nargs="*", type=str, help="Glob patterns to match files to upload.")
+ upload_parser.add_argument(
+ "--exclude", nargs="*", type=str, help="Glob patterns to exclude from files to upload."
+ )
+ upload_parser.add_argument(
+ "--delete",
+ nargs="*",
+ type=str,
+ help="Glob patterns for file to be deleted from the repo while committing.",
+ )
+ upload_parser.add_argument(
+ "--commit-message", type=str, help="The summary / title / first line of the generated commit."
+ )
+ upload_parser.add_argument("--commit-description", type=str, help="The description of the generated commit.")
+ upload_parser.add_argument(
+ "--create-pr", action="store_true", help="Whether to upload content as a new Pull Request."
+ )
+ upload_parser.add_argument(
+ "--every",
+ type=float,
+ help="If set, a background job is scheduled to create commits every `every` minutes.",
+ )
+ upload_parser.add_argument(
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
+ )
+ upload_parser.add_argument(
+ "--quiet",
+ action="store_true",
+ help="If True, progress bars are disabled and only the path to the uploaded files is printed.",
+ )
+ upload_parser.set_defaults(func=UploadCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.repo_id: str = args.repo_id
+ self.repo_type: Optional[str] = args.repo_type
+ self.revision: Optional[str] = args.revision
+ self.private: bool = args.private
+
+ self.include: Optional[List[str]] = args.include
+ self.exclude: Optional[List[str]] = args.exclude
+ self.delete: Optional[List[str]] = args.delete
+
+ self.commit_message: Optional[str] = args.commit_message
+ self.commit_description: Optional[str] = args.commit_description
+ self.create_pr: bool = args.create_pr
+ self.api: HfApi = HfApi(token=args.token, library_name="huggingface-cli")
+ self.quiet: bool = args.quiet # disable warnings and progress bars
+
+ # Check `--every` is valid
+ if args.every is not None and args.every <= 0:
+ raise ValueError(f"`every` must be a positive value (got '{args.every}')")
+ self.every: Optional[float] = args.every
+
+ # Resolve `local_path` and `path_in_repo`
+ repo_name: str = args.repo_id.split("/")[-1] # e.g. "Wauplin/my-cool-model" => "my-cool-model"
+ self.local_path: str
+ self.path_in_repo: str
+ if args.local_path is None and os.path.isfile(repo_name):
+ # Implicit case 1: user provided only a repo_id which happen to be a local file as well => upload it with same name
+ self.local_path = repo_name
+ self.path_in_repo = repo_name
+ elif args.local_path is None and os.path.isdir(repo_name):
+ # Implicit case 2: user provided only a repo_id which happen to be a local folder as well => upload it at root
+ self.local_path = repo_name
+ self.path_in_repo = "."
+ elif args.local_path is None:
+ # Implicit case 3: user provided only a repo_id that does not match a local file or folder
+ # => the user must explicitly provide a local_path => raise exception
+ raise ValueError(f"'{repo_name}' is not a local file or folder. Please set `local_path` explicitly.")
+ elif args.path_in_repo is None and os.path.isfile(args.local_path):
+ # Explicit local path to file, no path in repo => upload it at root with same name
+ self.local_path = args.local_path
+ self.path_in_repo = os.path.basename(args.local_path)
+ elif args.path_in_repo is None:
+ # Explicit local path to folder, no path in repo => upload at root
+ self.local_path = args.local_path
+ self.path_in_repo = "."
+ else:
+ # Finally, if both paths are explicit
+ self.local_path = args.local_path
+ self.path_in_repo = args.path_in_repo
+
+ def run(self) -> None:
+ if self.quiet:
+ disable_progress_bars()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ print(self._upload())
+ enable_progress_bars()
+ else:
+ logging.set_verbosity_info()
+ print(self._upload())
+ logging.set_verbosity_warning()
+
+ def _upload(self) -> str:
+ if os.path.isfile(self.local_path):
+ if self.include is not None and len(self.include) > 0:
+ warnings.warn("Ignoring `--include` since a single file is uploaded.")
+ if self.exclude is not None and len(self.exclude) > 0:
+ warnings.warn("Ignoring `--exclude` since a single file is uploaded.")
+ if self.delete is not None and len(self.delete) > 0:
+ warnings.warn("Ignoring `--delete` since a single file is uploaded.")
+
+ if not HF_HUB_ENABLE_HF_TRANSFER:
+ logger.info(
+ "Consider using `hf_transfer` for faster uploads. This solution comes with some limitations. See"
+ " https://huggingface.co/docs/huggingface_hub/hf_transfer for more details."
+ )
+
+ # Schedule commits if `every` is set
+ if self.every is not None:
+ if os.path.isfile(self.local_path):
+ # If file => watch entire folder + use allow_patterns
+ folder_path = os.path.dirname(self.local_path)
+ path_in_repo = (
+ self.path_in_repo[: -len(self.local_path)] # remove filename from path_in_repo
+ if self.path_in_repo.endswith(self.local_path)
+ else self.path_in_repo
+ )
+ allow_patterns = [self.local_path]
+ ignore_patterns = []
+ else:
+ folder_path = self.local_path
+ path_in_repo = self.path_in_repo
+ allow_patterns = self.include or []
+ ignore_patterns = self.exclude or []
+ if self.delete is not None and len(self.delete) > 0:
+ warnings.warn("Ignoring `--delete` when uploading with scheduled commits.")
+
+ scheduler = CommitScheduler(
+ folder_path=folder_path,
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ path_in_repo=path_in_repo,
+ private=self.private,
+ every=self.every,
+ hf_api=self.api,
+ )
+ print(f"Scheduling commits every {self.every} minutes to {scheduler.repo_id}.")
+ try: # Block main thread until KeyboardInterrupt
+ while True:
+ time.sleep(100)
+ except KeyboardInterrupt:
+ scheduler.stop()
+ return "Stopped scheduled commits."
+
+ # Otherwise, create repo and proceed with the upload
+ if not os.path.isfile(self.local_path) and not os.path.isdir(self.local_path):
+ raise FileNotFoundError(f"No such file or directory: '{self.local_path}'.")
+ repo_id = self.api.create_repo(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ exist_ok=True,
+ private=self.private,
+ space_sdk="gradio" if self.repo_type == "space" else None,
+ # ^ We don't want it to fail when uploading to a Space => let's set Gradio by default.
+ # ^ I'd rather not add CLI args to set it explicitly as we already have `huggingface-cli repo create` for that.
+ ).repo_id
+
+ # Check if branch already exists and if not, create it
+ if self.revision is not None and not self.create_pr:
+ try:
+ self.api.repo_info(repo_id=repo_id, repo_type=self.repo_type, revision=self.revision)
+ except RevisionNotFoundError:
+ logger.info(f"Branch '{self.revision}' not found. Creating it...")
+ self.api.create_branch(repo_id=repo_id, repo_type=self.repo_type, branch=self.revision, exist_ok=True)
+ # ^ `exist_ok=True` to avoid race concurrency issues
+
+ # File-based upload
+ if os.path.isfile(self.local_path):
+ return self.api.upload_file(
+ path_or_fileobj=self.local_path,
+ path_in_repo=self.path_in_repo,
+ repo_id=repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ commit_message=self.commit_message,
+ commit_description=self.commit_description,
+ create_pr=self.create_pr,
+ )
+
+ # Folder-based upload
+ else:
+ return self.api.upload_folder(
+ folder_path=self.local_path,
+ path_in_repo=self.path_in_repo,
+ repo_id=repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ commit_message=self.commit_message,
+ commit_description=self.commit_description,
+ create_pr=self.create_pr,
+ allow_patterns=self.include,
+ ignore_patterns=self.exclude,
+ delete_patterns=self.delete,
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/commands/upload_large_folder.py b/env/Lib/site-packages/huggingface_hub/commands/upload_large_folder.py
new file mode 100644
index 0000000000000000000000000000000000000000..61c12a9f62f8e12591d8db4c9defc50dd91db705
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/upload_large_folder.py
@@ -0,0 +1,129 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to upload a large folder with the CLI."""
+
+import os
+from argparse import Namespace, _SubParsersAction
+from typing import List, Optional
+
+from huggingface_hub import logging
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.hf_api import HfApi
+from huggingface_hub.utils import disable_progress_bars
+
+from ._cli_utils import ANSI
+
+
+logger = logging.get_logger(__name__)
+
+
+class UploadLargeFolderCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ subparser = parser.add_parser("upload-large-folder", help="Upload a large folder to a repo on the Hub")
+ subparser.add_argument(
+ "repo_id", type=str, help="The ID of the repo to upload to (e.g. `username/repo-name`)."
+ )
+ subparser.add_argument("local_path", type=str, help="Local path to the file or folder to upload.")
+ subparser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ help="Type of the repo to upload to (e.g. `dataset`).",
+ )
+ subparser.add_argument(
+ "--revision",
+ type=str,
+ help=("An optional Git revision to push to. It can be a branch name or a PR reference."),
+ )
+ subparser.add_argument(
+ "--private",
+ action="store_true",
+ help=(
+ "Whether to create a private repo if repo doesn't exist on the Hub. Ignored if the repo already exists."
+ ),
+ )
+ subparser.add_argument("--include", nargs="*", type=str, help="Glob patterns to match files to upload.")
+ subparser.add_argument("--exclude", nargs="*", type=str, help="Glob patterns to exclude from files to upload.")
+ subparser.add_argument(
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
+ )
+ subparser.add_argument(
+ "--num-workers", type=int, help="Number of workers to use to hash, upload and commit files."
+ )
+ subparser.add_argument("--no-report", action="store_true", help="Whether to disable regular status report.")
+ subparser.add_argument("--no-bars", action="store_true", help="Whether to disable progress bars.")
+ subparser.set_defaults(func=UploadLargeFolderCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.repo_id: str = args.repo_id
+ self.local_path: str = args.local_path
+ self.repo_type: str = args.repo_type
+ self.revision: Optional[str] = args.revision
+ self.private: bool = args.private
+
+ self.include: Optional[List[str]] = args.include
+ self.exclude: Optional[List[str]] = args.exclude
+
+ self.api: HfApi = HfApi(token=args.token, library_name="huggingface-cli")
+
+ self.num_workers: Optional[int] = args.num_workers
+ self.no_report: bool = args.no_report
+ self.no_bars: bool = args.no_bars
+
+ if not os.path.isdir(self.local_path):
+ raise ValueError("Large upload is only supported for folders.")
+
+ def run(self) -> None:
+ logging.set_verbosity_info()
+
+ print(
+ ANSI.yellow(
+ "You are about to upload a large folder to the Hub using `huggingface-cli upload-large-folder`. "
+ "This is a new feature so feedback is very welcome!\n"
+ "\n"
+ "A few things to keep in mind:\n"
+ " - Repository limits still apply: https://huggingface.co/docs/hub/repositories-recommendations\n"
+ " - Do not start several processes in parallel.\n"
+ " - You can interrupt and resume the process at any time. "
+ "The script will pick up where it left off except for partially uploaded files that would have to be entirely reuploaded.\n"
+ " - Do not upload the same folder to several repositories. If you need to do so, you must delete the `./.cache/huggingface/` folder first.\n"
+ "\n"
+ f"Some temporary metadata will be stored under `{self.local_path}/.cache/huggingface`.\n"
+ " - You must not modify those files manually.\n"
+ " - You must not delete the `./.cache/huggingface/` folder while a process is running.\n"
+ " - You can delete the `./.cache/huggingface/` folder to reinitialize the upload state when process is not running. Files will have to be hashed and preuploaded again, except for already committed files.\n"
+ "\n"
+ "If the process output is too verbose, you can disable the progress bars with `--no-bars`. "
+ "You can also entirely disable the status report with `--no-report`.\n"
+ "\n"
+ "For more details, run `huggingface-cli upload-large-folder --help` or check the documentation at "
+ "https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-large-folder."
+ )
+ )
+
+ if self.no_bars:
+ disable_progress_bars()
+
+ self.api.upload_large_folder(
+ repo_id=self.repo_id,
+ folder_path=self.local_path,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ private=self.private,
+ allow_patterns=self.include,
+ ignore_patterns=self.exclude,
+ num_workers=self.num_workers,
+ print_report=not self.no_report,
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/commands/user.py b/env/Lib/site-packages/huggingface_hub/commands/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..9741a219f17c951c9d20582a5756750b9b92630f
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/user.py
@@ -0,0 +1,304 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains commands to authenticate to the Hugging Face Hub and interact with your repositories.
+
+Usage:
+ # login and save token locally.
+ huggingface-cli login --token=hf_*** --add-to-git-credential
+
+ # switch between tokens
+ huggingface-cli auth switch
+
+ # list all tokens
+ huggingface-cli auth list
+
+ # logout from a specific token, if no token-name is provided, all tokens will be deleted from your machine.
+ huggingface-cli logout --token-name=your_token_name
+
+ # find out which huggingface.co account you are logged in as
+ huggingface-cli whoami
+
+ # create a new dataset repo on the Hub
+ huggingface-cli repo create mydataset --type=dataset
+
+"""
+
+import subprocess
+from argparse import _SubParsersAction
+from typing import List, Optional
+
+from requests.exceptions import HTTPError
+
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.constants import ENDPOINT, REPO_TYPES, REPO_TYPES_URL_PREFIXES, SPACES_SDK_TYPES
+from huggingface_hub.hf_api import HfApi
+
+from .._login import ( # noqa: F401 # for backward compatibility # noqa: F401 # for backward compatibility
+ NOTEBOOK_LOGIN_PASSWORD_HTML,
+ NOTEBOOK_LOGIN_TOKEN_HTML_END,
+ NOTEBOOK_LOGIN_TOKEN_HTML_START,
+ auth_list,
+ auth_switch,
+ login,
+ logout,
+ notebook_login,
+)
+from ..utils import get_stored_tokens, get_token, logging
+from ._cli_utils import ANSI
+
+
+logger = logging.get_logger(__name__)
+
+try:
+ from InquirerPy import inquirer
+ from InquirerPy.base.control import Choice
+
+ _inquirer_py_available = True
+except ImportError:
+ _inquirer_py_available = False
+
+
+class UserCommands(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ login_parser = parser.add_parser("login", help="Log in using a token from huggingface.co/settings/tokens")
+ login_parser.add_argument(
+ "--token",
+ type=str,
+ help="Token generated from https://huggingface.co/settings/tokens",
+ )
+ login_parser.add_argument(
+ "--add-to-git-credential",
+ action="store_true",
+ help="Optional: Save token to git credential helper.",
+ )
+ login_parser.set_defaults(func=lambda args: LoginCommand(args))
+ whoami_parser = parser.add_parser("whoami", help="Find out which huggingface.co account you are logged in as.")
+ whoami_parser.set_defaults(func=lambda args: WhoamiCommand(args))
+
+ logout_parser = parser.add_parser("logout", help="Log out")
+ logout_parser.add_argument(
+ "--token-name",
+ type=str,
+ help="Optional: Name of the access token to log out from.",
+ )
+ logout_parser.set_defaults(func=lambda args: LogoutCommand(args))
+
+ auth_parser = parser.add_parser("auth", help="Other authentication related commands")
+ auth_subparsers = auth_parser.add_subparsers(help="Authentication subcommands")
+ auth_switch_parser = auth_subparsers.add_parser("switch", help="Switch between access tokens")
+ auth_switch_parser.add_argument(
+ "--token-name",
+ type=str,
+ help="Optional: Name of the access token to switch to.",
+ )
+ auth_switch_parser.add_argument(
+ "--add-to-git-credential",
+ action="store_true",
+ help="Optional: Save token to git credential helper.",
+ )
+ auth_switch_parser.set_defaults(func=lambda args: AuthSwitchCommand(args))
+ auth_list_parser = auth_subparsers.add_parser("list", help="List all stored access tokens")
+ auth_list_parser.set_defaults(func=lambda args: AuthListCommand(args))
+ # new system: git-based repo system
+ repo_parser = parser.add_parser("repo", help="{create} Commands to interact with your huggingface.co repos.")
+ repo_subparsers = repo_parser.add_subparsers(help="huggingface.co repos related commands")
+ repo_create_parser = repo_subparsers.add_parser("create", help="Create a new repo on huggingface.co")
+ repo_create_parser.add_argument(
+ "name",
+ type=str,
+ help="Name for your repo. Will be namespaced under your username to build the repo id.",
+ )
+ repo_create_parser.add_argument(
+ "--type",
+ type=str,
+ help='Optional: repo_type: set to "dataset" or "space" if creating a dataset or space, default is model.',
+ )
+ repo_create_parser.add_argument("--organization", type=str, help="Optional: organization namespace.")
+ repo_create_parser.add_argument(
+ "--space_sdk",
+ type=str,
+ help='Optional: Hugging Face Spaces SDK type. Required when --type is set to "space".',
+ choices=SPACES_SDK_TYPES,
+ )
+ repo_create_parser.add_argument(
+ "-y",
+ "--yes",
+ action="store_true",
+ help="Optional: answer Yes to the prompt",
+ )
+ repo_create_parser.set_defaults(func=lambda args: RepoCreateCommand(args))
+
+
+class BaseUserCommand:
+ def __init__(self, args):
+ self.args = args
+ self._api = HfApi()
+
+
+class LoginCommand(BaseUserCommand):
+ def run(self):
+ logging.set_verbosity_info()
+ login(
+ token=self.args.token,
+ add_to_git_credential=self.args.add_to_git_credential,
+ )
+
+
+class LogoutCommand(BaseUserCommand):
+ def run(self):
+ logging.set_verbosity_info()
+ logout(token_name=self.args.token_name)
+
+
+class AuthSwitchCommand(BaseUserCommand):
+ def run(self):
+ logging.set_verbosity_info()
+ token_name = self.args.token_name
+ if token_name is None:
+ token_name = self._select_token_name()
+
+ if token_name is None:
+ print("No token name provided. Aborting.")
+ exit()
+ auth_switch(token_name, add_to_git_credential=self.args.add_to_git_credential)
+
+ def _select_token_name(self) -> Optional[str]:
+ token_names = list(get_stored_tokens().keys())
+
+ if not token_names:
+ logger.error("No stored tokens found. Please login first.")
+ return None
+
+ if _inquirer_py_available:
+ return self._select_token_name_tui(token_names)
+ # if inquirer is not available, use a simpler terminal UI
+ print("Available stored tokens:")
+ for i, token_name in enumerate(token_names, 1):
+ print(f"{i}. {token_name}")
+ while True:
+ try:
+ choice = input("Enter the number of the token to switch to (or 'q' to quit): ")
+ if choice.lower() == "q":
+ return None
+ index = int(choice) - 1
+ if 0 <= index < len(token_names):
+ return token_names[index]
+ else:
+ print("Invalid selection. Please try again.")
+ except ValueError:
+ print("Invalid input. Please enter a number or 'q' to quit.")
+
+ def _select_token_name_tui(self, token_names: List[str]) -> Optional[str]:
+ choices = [Choice(token_name, name=token_name) for token_name in token_names]
+ try:
+ return inquirer.select(
+ message="Select a token to switch to:",
+ choices=choices,
+ default=None,
+ ).execute()
+ except KeyboardInterrupt:
+ logger.info("Token selection cancelled.")
+ return None
+
+
+class AuthListCommand(BaseUserCommand):
+ def run(self):
+ logging.set_verbosity_info()
+ auth_list()
+
+
+class WhoamiCommand(BaseUserCommand):
+ def run(self):
+ token = get_token()
+ if token is None:
+ print("Not logged in")
+ exit()
+ try:
+ info = self._api.whoami(token)
+ print(info["name"])
+ orgs = [org["name"] for org in info["orgs"]]
+ if orgs:
+ print(ANSI.bold("orgs: "), ",".join(orgs))
+
+ if ENDPOINT != "https://huggingface.co":
+ print(f"Authenticated through private endpoint: {ENDPOINT}")
+ except HTTPError as e:
+ print(e)
+ print(ANSI.red(e.response.text))
+ exit(1)
+
+
+class RepoCreateCommand(BaseUserCommand):
+ def run(self):
+ token = get_token()
+ if token is None:
+ print("Not logged in")
+ exit(1)
+ try:
+ stdout = subprocess.check_output(["git", "--version"]).decode("utf-8")
+ print(ANSI.gray(stdout.strip()))
+ except FileNotFoundError:
+ print("Looks like you do not have git installed, please install.")
+
+ try:
+ stdout = subprocess.check_output(["git-lfs", "--version"]).decode("utf-8")
+ print(ANSI.gray(stdout.strip()))
+ except FileNotFoundError:
+ print(
+ ANSI.red(
+ "Looks like you do not have git-lfs installed, please install."
+ " You can install from https://git-lfs.github.com/."
+ " Then run `git lfs install` (you only have to do this once)."
+ )
+ )
+ print("")
+
+ user = self._api.whoami(token)["name"]
+ namespace = self.args.organization if self.args.organization is not None else user
+
+ repo_id = f"{namespace}/{self.args.name}"
+
+ if self.args.type not in REPO_TYPES:
+ print("Invalid repo --type")
+ exit(1)
+
+ if self.args.type in REPO_TYPES_URL_PREFIXES:
+ prefixed_repo_id = REPO_TYPES_URL_PREFIXES[self.args.type] + repo_id
+ else:
+ prefixed_repo_id = repo_id
+
+ print(f"You are about to create {ANSI.bold(prefixed_repo_id)}")
+
+ if not self.args.yes:
+ choice = input("Proceed? [Y/n] ").lower()
+ if not (choice == "" or choice == "y" or choice == "yes"):
+ print("Abort")
+ exit()
+ try:
+ url = self._api.create_repo(
+ repo_id=repo_id,
+ token=token,
+ repo_type=self.args.type,
+ space_sdk=self.args.space_sdk,
+ )
+ except HTTPError as e:
+ print(e)
+ print(ANSI.red(e.response.text))
+ exit(1)
+ print("\nYour repo now lives at:")
+ print(f" {ANSI.bold(url)}")
+ print("\nYou can clone it locally with the command below, and commit/push as usual.")
+ print(f"\n git clone {url}")
+ print("")
diff --git a/env/Lib/site-packages/huggingface_hub/commands/version.py b/env/Lib/site-packages/huggingface_hub/commands/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7e866b76f1dcbfbb90a4ec494c47cf3d61c17dd
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/commands/version.py
@@ -0,0 +1,37 @@
+# Copyright 2022 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to print information about the version.
+
+Usage:
+ huggingface-cli version
+"""
+
+from argparse import _SubParsersAction
+
+from huggingface_hub import __version__
+
+from . import BaseHuggingfaceCLICommand
+
+
+class VersionCommand(BaseHuggingfaceCLICommand):
+ def __init__(self, args):
+ self.args = args
+
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ version_parser = parser.add_parser("version", help="Print information about the huggingface-cli version.")
+ version_parser.set_defaults(func=VersionCommand)
+
+ def run(self) -> None:
+ print(f"huggingface_hub version: {__version__}")
diff --git a/env/Lib/site-packages/huggingface_hub/community.py b/env/Lib/site-packages/huggingface_hub/community.py
new file mode 100644
index 0000000000000000000000000000000000000000..16f2f02428dd5c2ce6437534af0397801bda45c5
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/community.py
@@ -0,0 +1,355 @@
+"""
+Data structures to interact with Discussions and Pull Requests on the Hub.
+
+See [the Discussions and Pull Requests guide](https://huggingface.co/docs/hub/repositories-pull-requests-discussions)
+for more information on Pull Requests, Discussions, and the community tab.
+"""
+
+from dataclasses import dataclass
+from datetime import datetime
+from typing import List, Literal, Optional, Union
+
+from . import constants
+from .utils import parse_datetime
+
+
+DiscussionStatus = Literal["open", "closed", "merged", "draft"]
+
+
+@dataclass
+class Discussion:
+ """
+ A Discussion or Pull Request on the Hub.
+
+ This dataclass is not intended to be instantiated directly.
+
+ Attributes:
+ title (`str`):
+ The title of the Discussion / Pull Request
+ status (`str`):
+ The status of the Discussion / Pull Request.
+ It must be one of:
+ * `"open"`
+ * `"closed"`
+ * `"merged"` (only for Pull Requests )
+ * `"draft"` (only for Pull Requests )
+ num (`int`):
+ The number of the Discussion / Pull Request.
+ repo_id (`str`):
+ The id (`"{namespace}/{repo_name}"`) of the repo on which
+ the Discussion / Pull Request was open.
+ repo_type (`str`):
+ The type of the repo on which the Discussion / Pull Request was open.
+ Possible values are: `"model"`, `"dataset"`, `"space"`.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ is_pull_request (`bool`):
+ Whether or not this is a Pull Request.
+ created_at (`datetime`):
+ The `datetime` of creation of the Discussion / Pull Request.
+ endpoint (`str`):
+ Endpoint of the Hub. Default is https://huggingface.co.
+ git_reference (`str`, *optional*):
+ (property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise.
+ url (`str`):
+ (property) URL of the discussion on the Hub.
+ """
+
+ title: str
+ status: DiscussionStatus
+ num: int
+ repo_id: str
+ repo_type: str
+ author: str
+ is_pull_request: bool
+ created_at: datetime
+ endpoint: str
+
+ @property
+ def git_reference(self) -> Optional[str]:
+ """
+ If this is a Pull Request , returns the git reference to which changes can be pushed.
+ Returns `None` otherwise.
+ """
+ if self.is_pull_request:
+ return f"refs/pr/{self.num}"
+ return None
+
+ @property
+ def url(self) -> str:
+ """Returns the URL of the discussion on the Hub."""
+ if self.repo_type is None or self.repo_type == constants.REPO_TYPE_MODEL:
+ return f"{self.endpoint}/{self.repo_id}/discussions/{self.num}"
+ return f"{self.endpoint}/{self.repo_type}s/{self.repo_id}/discussions/{self.num}"
+
+
+@dataclass
+class DiscussionWithDetails(Discussion):
+ """
+ Subclass of [`Discussion`].
+
+ Attributes:
+ title (`str`):
+ The title of the Discussion / Pull Request
+ status (`str`):
+ The status of the Discussion / Pull Request.
+ It can be one of:
+ * `"open"`
+ * `"closed"`
+ * `"merged"` (only for Pull Requests )
+ * `"draft"` (only for Pull Requests )
+ num (`int`):
+ The number of the Discussion / Pull Request.
+ repo_id (`str`):
+ The id (`"{namespace}/{repo_name}"`) of the repo on which
+ the Discussion / Pull Request was open.
+ repo_type (`str`):
+ The type of the repo on which the Discussion / Pull Request was open.
+ Possible values are: `"model"`, `"dataset"`, `"space"`.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ is_pull_request (`bool`):
+ Whether or not this is a Pull Request.
+ created_at (`datetime`):
+ The `datetime` of creation of the Discussion / Pull Request.
+ events (`list` of [`DiscussionEvent`])
+ The list of [`DiscussionEvents`] in this Discussion or Pull Request.
+ conflicting_files (`Union[List[str], bool, None]`, *optional*):
+ A list of conflicting files if this is a Pull Request.
+ `None` if `self.is_pull_request` is `False`.
+ `True` if there are conflicting files but the list can't be retrieved.
+ target_branch (`str`, *optional*):
+ The branch into which changes are to be merged if this is a
+ Pull Request . `None` if `self.is_pull_request` is `False`.
+ merge_commit_oid (`str`, *optional*):
+ If this is a merged Pull Request , this is set to the OID / SHA of
+ the merge commit, `None` otherwise.
+ diff (`str`, *optional*):
+ The git diff if this is a Pull Request , `None` otherwise.
+ endpoint (`str`):
+ Endpoint of the Hub. Default is https://huggingface.co.
+ git_reference (`str`, *optional*):
+ (property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise.
+ url (`str`):
+ (property) URL of the discussion on the Hub.
+ """
+
+ events: List["DiscussionEvent"]
+ conflicting_files: Union[List[str], bool, None]
+ target_branch: Optional[str]
+ merge_commit_oid: Optional[str]
+ diff: Optional[str]
+
+
+@dataclass
+class DiscussionEvent:
+ """
+ An event in a Discussion or Pull Request.
+
+ Use concrete classes:
+ * [`DiscussionComment`]
+ * [`DiscussionStatusChange`]
+ * [`DiscussionCommit`]
+ * [`DiscussionTitleChange`]
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ """
+
+ id: str
+ type: str
+ created_at: datetime
+ author: str
+
+ _event: dict
+ """Stores the original event data, in case we need to access it later."""
+
+
+@dataclass
+class DiscussionComment(DiscussionEvent):
+ """A comment in a Discussion / Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ content (`str`):
+ The raw markdown content of the comment. Mentions, links and images are not rendered.
+ edited (`bool`):
+ Whether or not this comment has been edited.
+ hidden (`bool`):
+ Whether or not this comment has been hidden.
+ """
+
+ content: str
+ edited: bool
+ hidden: bool
+
+ @property
+ def rendered(self) -> str:
+ """The rendered comment, as a HTML string"""
+ return self._event["data"]["latest"]["html"]
+
+ @property
+ def last_edited_at(self) -> datetime:
+ """The last edit time, as a `datetime` object."""
+ return parse_datetime(self._event["data"]["latest"]["updatedAt"])
+
+ @property
+ def last_edited_by(self) -> str:
+ """The last edit time, as a `datetime` object."""
+ return self._event["data"]["latest"].get("author", {}).get("name", "deleted")
+
+ @property
+ def edit_history(self) -> List[dict]:
+ """The edit history of the comment"""
+ return self._event["data"]["history"]
+
+ @property
+ def number_of_edits(self) -> int:
+ return len(self.edit_history)
+
+
+@dataclass
+class DiscussionStatusChange(DiscussionEvent):
+ """A change of status in a Discussion / Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ new_status (`str`):
+ The status of the Discussion / Pull Request after the change.
+ It can be one of:
+ * `"open"`
+ * `"closed"`
+ * `"merged"` (only for Pull Requests )
+ """
+
+ new_status: str
+
+
+@dataclass
+class DiscussionCommit(DiscussionEvent):
+ """A commit in a Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ summary (`str`):
+ The summary of the commit.
+ oid (`str`):
+ The OID / SHA of the commit, as a hexadecimal string.
+ """
+
+ summary: str
+ oid: str
+
+
+@dataclass
+class DiscussionTitleChange(DiscussionEvent):
+ """A rename event in a Discussion / Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ old_title (`str`):
+ The previous title for the Discussion / Pull Request.
+ new_title (`str`):
+ The new title.
+ """
+
+ old_title: str
+ new_title: str
+
+
+def deserialize_event(event: dict) -> DiscussionEvent:
+ """Instantiates a [`DiscussionEvent`] from a dict"""
+ event_id: str = event["id"]
+ event_type: str = event["type"]
+ created_at = parse_datetime(event["createdAt"])
+
+ common_args = dict(
+ id=event_id,
+ type=event_type,
+ created_at=created_at,
+ author=event.get("author", {}).get("name", "deleted"),
+ _event=event,
+ )
+
+ if event_type == "comment":
+ return DiscussionComment(
+ **common_args,
+ edited=event["data"]["edited"],
+ hidden=event["data"]["hidden"],
+ content=event["data"]["latest"]["raw"],
+ )
+ if event_type == "status-change":
+ return DiscussionStatusChange(
+ **common_args,
+ new_status=event["data"]["status"],
+ )
+ if event_type == "commit":
+ return DiscussionCommit(
+ **common_args,
+ summary=event["data"]["subject"],
+ oid=event["data"]["oid"],
+ )
+ if event_type == "title-change":
+ return DiscussionTitleChange(
+ **common_args,
+ old_title=event["data"]["from"],
+ new_title=event["data"]["to"],
+ )
+
+ return DiscussionEvent(**common_args)
diff --git a/env/Lib/site-packages/huggingface_hub/constants.py b/env/Lib/site-packages/huggingface_hub/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..6add6a8e4dfd3ed4617e3ec911c74618596a978d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/constants.py
@@ -0,0 +1,235 @@
+import os
+import re
+import typing
+from typing import Literal, Optional, Tuple
+
+
+# Possible values for env variables
+
+
+ENV_VARS_TRUE_VALUES = {"1", "ON", "YES", "TRUE"}
+ENV_VARS_TRUE_AND_AUTO_VALUES = ENV_VARS_TRUE_VALUES.union({"AUTO"})
+
+
+def _is_true(value: Optional[str]) -> bool:
+ if value is None:
+ return False
+ return value.upper() in ENV_VARS_TRUE_VALUES
+
+
+def _as_int(value: Optional[str]) -> Optional[int]:
+ if value is None:
+ return None
+ return int(value)
+
+
+# Constants for file downloads
+
+PYTORCH_WEIGHTS_NAME = "pytorch_model.bin"
+TF2_WEIGHTS_NAME = "tf_model.h5"
+TF_WEIGHTS_NAME = "model.ckpt"
+FLAX_WEIGHTS_NAME = "flax_model.msgpack"
+CONFIG_NAME = "config.json"
+REPOCARD_NAME = "README.md"
+DEFAULT_ETAG_TIMEOUT = 10
+DEFAULT_DOWNLOAD_TIMEOUT = 10
+DEFAULT_REQUEST_TIMEOUT = 10
+DOWNLOAD_CHUNK_SIZE = 10 * 1024 * 1024
+HF_TRANSFER_CONCURRENCY = 100
+
+# Constants for serialization
+
+PYTORCH_WEIGHTS_FILE_PATTERN = "pytorch_model{suffix}.bin" # Unsafe pickle: use safetensors instead
+SAFETENSORS_WEIGHTS_FILE_PATTERN = "model{suffix}.safetensors"
+TF2_WEIGHTS_FILE_PATTERN = "tf_model{suffix}.h5"
+
+# Constants for safetensors repos
+
+SAFETENSORS_SINGLE_FILE = "model.safetensors"
+SAFETENSORS_INDEX_FILE = "model.safetensors.index.json"
+SAFETENSORS_MAX_HEADER_LENGTH = 25_000_000
+
+# Timeout of aquiring file lock and logging the attempt
+FILELOCK_LOG_EVERY_SECONDS = 10
+
+# Git-related constants
+
+DEFAULT_REVISION = "main"
+REGEX_COMMIT_OID = re.compile(r"[A-Fa-f0-9]{5,40}")
+
+HUGGINGFACE_CO_URL_HOME = "https://huggingface.co/"
+
+_staging_mode = _is_true(os.environ.get("HUGGINGFACE_CO_STAGING"))
+
+_HF_DEFAULT_ENDPOINT = "https://huggingface.co"
+_HF_DEFAULT_STAGING_ENDPOINT = "https://hub-ci.huggingface.co"
+ENDPOINT = os.getenv("HF_ENDPOINT", _HF_DEFAULT_ENDPOINT).rstrip("/")
+HUGGINGFACE_CO_URL_TEMPLATE = ENDPOINT + "/{repo_id}/resolve/{revision}/{filename}"
+
+if _staging_mode:
+ ENDPOINT = _HF_DEFAULT_STAGING_ENDPOINT
+ HUGGINGFACE_CO_URL_TEMPLATE = _HF_DEFAULT_STAGING_ENDPOINT + "/{repo_id}/resolve/{revision}/{filename}"
+
+HUGGINGFACE_HEADER_X_REPO_COMMIT = "X-Repo-Commit"
+HUGGINGFACE_HEADER_X_LINKED_ETAG = "X-Linked-Etag"
+HUGGINGFACE_HEADER_X_LINKED_SIZE = "X-Linked-Size"
+
+INFERENCE_ENDPOINT = os.environ.get("HF_INFERENCE_ENDPOINT", "https://api-inference.huggingface.co")
+
+# See https://huggingface.co/docs/inference-endpoints/index
+INFERENCE_ENDPOINTS_ENDPOINT = "https://api.endpoints.huggingface.cloud/v2"
+
+# Proxy for third-party providers
+INFERENCE_PROXY_TEMPLATE = "https://router.huggingface.co/{provider}"
+
+REPO_ID_SEPARATOR = "--"
+# ^ this substring is not allowed in repo_ids on hf.co
+# and is the canonical one we use for serialization of repo ids elsewhere.
+
+
+REPO_TYPE_DATASET = "dataset"
+REPO_TYPE_SPACE = "space"
+REPO_TYPE_MODEL = "model"
+REPO_TYPES = [None, REPO_TYPE_MODEL, REPO_TYPE_DATASET, REPO_TYPE_SPACE]
+SPACES_SDK_TYPES = ["gradio", "streamlit", "docker", "static"]
+
+REPO_TYPES_URL_PREFIXES = {
+ REPO_TYPE_DATASET: "datasets/",
+ REPO_TYPE_SPACE: "spaces/",
+}
+REPO_TYPES_MAPPING = {
+ "datasets": REPO_TYPE_DATASET,
+ "spaces": REPO_TYPE_SPACE,
+ "models": REPO_TYPE_MODEL,
+}
+
+DiscussionTypeFilter = Literal["all", "discussion", "pull_request"]
+DISCUSSION_TYPES: Tuple[DiscussionTypeFilter, ...] = typing.get_args(DiscussionTypeFilter)
+DiscussionStatusFilter = Literal["all", "open", "closed"]
+DISCUSSION_STATUS: Tuple[DiscussionTypeFilter, ...] = typing.get_args(DiscussionStatusFilter)
+
+# Webhook subscription types
+WEBHOOK_DOMAIN_T = Literal["repo", "discussions"]
+
+# default cache
+default_home = os.path.join(os.path.expanduser("~"), ".cache")
+HF_HOME = os.path.expanduser(
+ os.getenv(
+ "HF_HOME",
+ os.path.join(os.getenv("XDG_CACHE_HOME", default_home), "huggingface"),
+ )
+)
+hf_cache_home = HF_HOME # for backward compatibility. TODO: remove this in 1.0.0
+
+default_cache_path = os.path.join(HF_HOME, "hub")
+default_assets_cache_path = os.path.join(HF_HOME, "assets")
+
+# Legacy env variables
+HUGGINGFACE_HUB_CACHE = os.getenv("HUGGINGFACE_HUB_CACHE", default_cache_path)
+HUGGINGFACE_ASSETS_CACHE = os.getenv("HUGGINGFACE_ASSETS_CACHE", default_assets_cache_path)
+
+# New env variables
+HF_HUB_CACHE = os.getenv("HF_HUB_CACHE", HUGGINGFACE_HUB_CACHE)
+HF_ASSETS_CACHE = os.getenv("HF_ASSETS_CACHE", HUGGINGFACE_ASSETS_CACHE)
+
+HF_HUB_OFFLINE = _is_true(os.environ.get("HF_HUB_OFFLINE") or os.environ.get("TRANSFORMERS_OFFLINE"))
+
+# If set, log level will be set to DEBUG and all requests made to the Hub will be logged
+# as curl commands for reproducibility.
+HF_DEBUG = _is_true(os.environ.get("HF_DEBUG"))
+
+# Opt-out from telemetry requests
+HF_HUB_DISABLE_TELEMETRY = (
+ _is_true(os.environ.get("HF_HUB_DISABLE_TELEMETRY")) # HF-specific env variable
+ or _is_true(os.environ.get("DISABLE_TELEMETRY"))
+ or _is_true(os.environ.get("DO_NOT_TRACK")) # https://consoledonottrack.com/
+)
+
+HF_TOKEN_PATH = os.environ.get("HF_TOKEN_PATH", os.path.join(HF_HOME, "token"))
+HF_STORED_TOKENS_PATH = os.path.join(os.path.dirname(HF_TOKEN_PATH), "stored_tokens")
+
+if _staging_mode:
+ # In staging mode, we use a different cache to ensure we don't mix up production and staging data or tokens
+ # In practice in `huggingface_hub` tests, we monkeypatch these values with temporary directories. The following
+ # lines are only used in third-party libraries tests (e.g. `transformers`, `diffusers`, etc.).
+ _staging_home = os.path.join(os.path.expanduser("~"), ".cache", "huggingface_staging")
+ HUGGINGFACE_HUB_CACHE = os.path.join(_staging_home, "hub")
+ HF_TOKEN_PATH = os.path.join(_staging_home, "token")
+
+# Here, `True` will disable progress bars globally without possibility of enabling it
+# programmatically. `False` will enable them without possibility of disabling them.
+# If environment variable is not set (None), then the user is free to enable/disable
+# them programmatically.
+# TL;DR: env variable has priority over code
+__HF_HUB_DISABLE_PROGRESS_BARS = os.environ.get("HF_HUB_DISABLE_PROGRESS_BARS")
+HF_HUB_DISABLE_PROGRESS_BARS: Optional[bool] = (
+ _is_true(__HF_HUB_DISABLE_PROGRESS_BARS) if __HF_HUB_DISABLE_PROGRESS_BARS is not None else None
+)
+
+# Disable warning on machines that do not support symlinks (e.g. Windows non-developer)
+HF_HUB_DISABLE_SYMLINKS_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_SYMLINKS_WARNING"))
+
+# Disable warning when using experimental features
+HF_HUB_DISABLE_EXPERIMENTAL_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_EXPERIMENTAL_WARNING"))
+
+# Disable sending the cached token by default is all HTTP requests to the Hub
+HF_HUB_DISABLE_IMPLICIT_TOKEN: bool = _is_true(os.environ.get("HF_HUB_DISABLE_IMPLICIT_TOKEN"))
+
+# Enable fast-download using external dependency "hf_transfer"
+# See:
+# - https://pypi.org/project/hf-transfer/
+# - https://github.com/huggingface/hf_transfer (private)
+HF_HUB_ENABLE_HF_TRANSFER: bool = _is_true(os.environ.get("HF_HUB_ENABLE_HF_TRANSFER"))
+
+
+# UNUSED
+# We don't use symlinks in local dir anymore.
+HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD: int = (
+ _as_int(os.environ.get("HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD")) or 5 * 1024 * 1024
+)
+
+# Used to override the etag timeout on a system level
+HF_HUB_ETAG_TIMEOUT: int = _as_int(os.environ.get("HF_HUB_ETAG_TIMEOUT")) or DEFAULT_ETAG_TIMEOUT
+
+# Used to override the get request timeout on a system level
+HF_HUB_DOWNLOAD_TIMEOUT: int = _as_int(os.environ.get("HF_HUB_DOWNLOAD_TIMEOUT")) or DEFAULT_DOWNLOAD_TIMEOUT
+
+# Allows to add information about the requester in the user-agent (eg. partner name)
+HF_HUB_USER_AGENT_ORIGIN: Optional[str] = os.environ.get("HF_HUB_USER_AGENT_ORIGIN")
+
+# List frameworks that are handled by the InferenceAPI service. Useful to scan endpoints and check which models are
+# deployed and running. Since 95% of the models are using the top 4 frameworks listed below, we scan only those by
+# default. We still keep the full list of supported frameworks in case we want to scan all of them.
+MAIN_INFERENCE_API_FRAMEWORKS = [
+ "diffusers",
+ "sentence-transformers",
+ "text-generation-inference",
+ "transformers",
+]
+
+ALL_INFERENCE_API_FRAMEWORKS = MAIN_INFERENCE_API_FRAMEWORKS + [
+ "adapter-transformers",
+ "allennlp",
+ "asteroid",
+ "bertopic",
+ "doctr",
+ "espnet",
+ "fairseq",
+ "fastai",
+ "fasttext",
+ "flair",
+ "k2",
+ "keras",
+ "mindspore",
+ "nemo",
+ "open_clip",
+ "paddlenlp",
+ "peft",
+ "pyannote-audio",
+ "sklearn",
+ "spacy",
+ "span-marker",
+ "speechbrain",
+ "stanza",
+ "timm",
+]
diff --git a/env/Lib/site-packages/huggingface_hub/errors.py b/env/Lib/site-packages/huggingface_hub/errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..226c8bb4000420ac38f80e1df6b78e7131cef8f0
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/errors.py
@@ -0,0 +1,329 @@
+"""Contains all custom errors."""
+
+from pathlib import Path
+from typing import Optional, Union
+
+from requests import HTTPError, Response
+
+
+# CACHE ERRORS
+
+
+class CacheNotFound(Exception):
+ """Exception thrown when the Huggingface cache is not found."""
+
+ cache_dir: Union[str, Path]
+
+ def __init__(self, msg: str, cache_dir: Union[str, Path], *args, **kwargs):
+ super().__init__(msg, *args, **kwargs)
+ self.cache_dir = cache_dir
+
+
+class CorruptedCacheException(Exception):
+ """Exception for any unexpected structure in the Huggingface cache-system."""
+
+
+# HEADERS ERRORS
+
+
+class LocalTokenNotFoundError(EnvironmentError):
+ """Raised if local token is required but not found."""
+
+
+# HTTP ERRORS
+
+
+class OfflineModeIsEnabled(ConnectionError):
+ """Raised when a request is made but `HF_HUB_OFFLINE=1` is set as environment variable."""
+
+
+class HfHubHTTPError(HTTPError):
+ """
+ HTTPError to inherit from for any custom HTTP Error raised in HF Hub.
+
+ Any HTTPError is converted at least into a `HfHubHTTPError`. If some information is
+ sent back by the server, it will be added to the error message.
+
+ Added details:
+ - Request id from "X-Request-Id" header if exists. If not, fallback to "X-Amzn-Trace-Id" header if exists.
+ - Server error message from the header "X-Error-Message".
+ - Server error message if we can found one in the response body.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub.utils import get_session, hf_raise_for_status, HfHubHTTPError
+
+ response = get_session().post(...)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ print(str(e)) # formatted message
+ e.request_id, e.server_message # details returned by server
+
+ # Complete the error message with additional information once it's raised
+ e.append_to_message("\n`create_commit` expects the repository to exist.")
+ raise
+ ```
+ """
+
+ def __init__(self, message: str, response: Optional[Response] = None, *, server_message: Optional[str] = None):
+ self.request_id = (
+ response.headers.get("x-request-id") or response.headers.get("X-Amzn-Trace-Id")
+ if response is not None
+ else None
+ )
+ self.server_message = server_message
+
+ super().__init__(
+ message,
+ response=response, # type: ignore [arg-type]
+ request=response.request if response is not None else None, # type: ignore [arg-type]
+ )
+
+ def append_to_message(self, additional_message: str) -> None:
+ """Append additional information to the `HfHubHTTPError` initial message."""
+ self.args = (self.args[0] + additional_message,) + self.args[1:]
+
+
+# INFERENCE CLIENT ERRORS
+
+
+class InferenceTimeoutError(HTTPError, TimeoutError):
+ """Error raised when a model is unavailable or the request times out."""
+
+
+# INFERENCE ENDPOINT ERRORS
+
+
+class InferenceEndpointError(Exception):
+ """Generic exception when dealing with Inference Endpoints."""
+
+
+class InferenceEndpointTimeoutError(InferenceEndpointError, TimeoutError):
+ """Exception for timeouts while waiting for Inference Endpoint."""
+
+
+# SAFETENSORS ERRORS
+
+
+class SafetensorsParsingError(Exception):
+ """Raised when failing to parse a safetensors file metadata.
+
+ This can be the case if the file is not a safetensors file or does not respect the specification.
+ """
+
+
+class NotASafetensorsRepoError(Exception):
+ """Raised when a repo is not a Safetensors repo i.e. doesn't have either a `model.safetensors` or a
+ `model.safetensors.index.json` file.
+ """
+
+
+# TEXT GENERATION ERRORS
+
+
+class TextGenerationError(HTTPError):
+ """Generic error raised if text-generation went wrong."""
+
+
+# Text Generation Inference Errors
+class ValidationError(TextGenerationError):
+ """Server-side validation error."""
+
+
+class GenerationError(TextGenerationError):
+ pass
+
+
+class OverloadedError(TextGenerationError):
+ pass
+
+
+class IncompleteGenerationError(TextGenerationError):
+ pass
+
+
+class UnknownError(TextGenerationError):
+ pass
+
+
+# VALIDATION ERRORS
+
+
+class HFValidationError(ValueError):
+ """Generic exception thrown by `huggingface_hub` validators.
+
+ Inherits from [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError).
+ """
+
+
+# FILE METADATA ERRORS
+
+
+class FileMetadataError(OSError):
+ """Error triggered when the metadata of a file on the Hub cannot be retrieved (missing ETag or commit_hash).
+
+ Inherits from `OSError` for backward compatibility.
+ """
+
+
+# REPOSITORY ERRORS
+
+
+class RepositoryNotFoundError(HfHubHTTPError):
+ """
+ Raised when trying to access a hf.co URL with an invalid repository name, or
+ with a private repo name the user does not have access to.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import model_info
+ >>> model_info("")
+ (...)
+ huggingface_hub.utils._errors.RepositoryNotFoundError: 401 Client Error. (Request ID: PvMw_VjBMjVdMz53WKIzP)
+
+ Repository Not Found for url: https://huggingface.co/api/models/%3Cnon_existent_repository%3E.
+ Please make sure you specified the correct `repo_id` and `repo_type`.
+ If the repo is private, make sure you are authenticated.
+ Invalid username or password.
+ ```
+ """
+
+
+class GatedRepoError(RepositoryNotFoundError):
+ """
+ Raised when trying to access a gated repository for which the user is not on the
+ authorized list.
+
+ Note: derives from `RepositoryNotFoundError` to ensure backward compatibility.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import model_info
+ >>> model_info("")
+ (...)
+ huggingface_hub.utils._errors.GatedRepoError: 403 Client Error. (Request ID: ViT1Bf7O_026LGSQuVqfa)
+
+ Cannot access gated repo for url https://huggingface.co/api/models/ardent-figment/gated-model.
+ Access to model ardent-figment/gated-model is restricted and you are not in the authorized list.
+ Visit https://huggingface.co/ardent-figment/gated-model to ask for access.
+ ```
+ """
+
+
+class DisabledRepoError(HfHubHTTPError):
+ """
+ Raised when trying to access a repository that has been disabled by its author.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import dataset_info
+ >>> dataset_info("laion/laion-art")
+ (...)
+ huggingface_hub.utils._errors.DisabledRepoError: 403 Client Error. (Request ID: Root=1-659fc3fa-3031673e0f92c71a2260dbe2;bc6f4dfb-b30a-4862-af0a-5cfe827610d8)
+
+ Cannot access repository for url https://huggingface.co/api/datasets/laion/laion-art.
+ Access to this resource is disabled.
+ ```
+ """
+
+
+# REVISION ERROR
+
+
+class RevisionNotFoundError(HfHubHTTPError):
+ """
+ Raised when trying to access a hf.co URL with a valid repository but an invalid
+ revision.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import hf_hub_download
+ >>> hf_hub_download('bert-base-cased', 'config.json', revision='')
+ (...)
+ huggingface_hub.utils._errors.RevisionNotFoundError: 404 Client Error. (Request ID: Mwhe_c3Kt650GcdKEFomX)
+
+ Revision Not Found for url: https://huggingface.co/bert-base-cased/resolve/%3Cnon-existent-revision%3E/config.json.
+ ```
+ """
+
+
+# ENTRY ERRORS
+class EntryNotFoundError(HfHubHTTPError):
+ """
+ Raised when trying to access a hf.co URL with a valid repository and revision
+ but an invalid filename.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import hf_hub_download
+ >>> hf_hub_download('bert-base-cased', '')
+ (...)
+ huggingface_hub.utils._errors.EntryNotFoundError: 404 Client Error. (Request ID: 53pNl6M0MxsnG5Sw8JA6x)
+
+ Entry Not Found for url: https://huggingface.co/bert-base-cased/resolve/main/%3Cnon-existent-file%3E.
+ ```
+ """
+
+
+class LocalEntryNotFoundError(EntryNotFoundError, FileNotFoundError, ValueError):
+ """
+ Raised when trying to access a file or snapshot that is not on the disk when network is
+ disabled or unavailable (connection issue). The entry may exist on the Hub.
+
+ Note: `ValueError` type is to ensure backward compatibility.
+ Note: `LocalEntryNotFoundError` derives from `HTTPError` because of `EntryNotFoundError`
+ even when it is not a network issue.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import hf_hub_download
+ >>> hf_hub_download('bert-base-cased', '', local_files_only=True)
+ (...)
+ huggingface_hub.utils._errors.LocalEntryNotFoundError: Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable hf.co look-ups and downloads online, set 'local_files_only' to False.
+ ```
+ """
+
+ def __init__(self, message: str):
+ super().__init__(message, response=None)
+
+
+# REQUEST ERROR
+class BadRequestError(HfHubHTTPError, ValueError):
+ """
+ Raised by `hf_raise_for_status` when the server returns a HTTP 400 error.
+
+ Example:
+
+ ```py
+ >>> resp = requests.post("hf.co/api/check", ...)
+ >>> hf_raise_for_status(resp, endpoint_name="check")
+ huggingface_hub.utils._errors.BadRequestError: Bad request for check endpoint: {details} (Request ID: XXX)
+ ```
+ """
+
+
+# DDUF file format ERROR
+
+
+class DDUFError(Exception):
+ """Base exception for errors related to the DDUF format."""
+
+
+class DDUFCorruptedFileError(DDUFError):
+ """Exception thrown when the DDUF file is corrupted."""
+
+
+class DDUFExportError(DDUFError):
+ """Base exception for errors during DDUF export."""
+
+
+class DDUFInvalidEntryNameError(DDUFExportError):
+ """Exception thrown when the entry name is invalid."""
diff --git a/env/Lib/site-packages/huggingface_hub/fastai_utils.py b/env/Lib/site-packages/huggingface_hub/fastai_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..e75eba2a8baee7bdeb8d36a1c06bd950cf857c44
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/fastai_utils.py
@@ -0,0 +1,425 @@
+import json
+import os
+from pathlib import Path
+from pickle import DEFAULT_PROTOCOL, PicklingError
+from typing import Any, Dict, List, Optional, Union
+
+from packaging import version
+
+from huggingface_hub import constants, snapshot_download
+from huggingface_hub.hf_api import HfApi
+from huggingface_hub.utils import (
+ SoftTemporaryDirectory,
+ get_fastai_version,
+ get_fastcore_version,
+ get_python_version,
+)
+
+from .utils import logging, validate_hf_hub_args
+from .utils._runtime import _PY_VERSION # noqa: F401 # for backward compatibility...
+
+
+logger = logging.get_logger(__name__)
+
+
+def _check_fastai_fastcore_versions(
+ fastai_min_version: str = "2.4",
+ fastcore_min_version: str = "1.3.27",
+):
+ """
+ Checks that the installed fastai and fastcore versions are compatible for pickle serialization.
+
+ Args:
+ fastai_min_version (`str`, *optional*):
+ The minimum fastai version supported.
+ fastcore_min_version (`str`, *optional*):
+ The minimum fastcore version supported.
+
+
+ Raises the following error:
+
+ - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ if the fastai or fastcore libraries are not available or are of an invalid version.
+
+
+ """
+
+ if (get_fastcore_version() or get_fastai_version()) == "N/A":
+ raise ImportError(
+ f"fastai>={fastai_min_version} and fastcore>={fastcore_min_version} are"
+ f" required. Currently using fastai=={get_fastai_version()} and"
+ f" fastcore=={get_fastcore_version()}."
+ )
+
+ current_fastai_version = version.Version(get_fastai_version())
+ current_fastcore_version = version.Version(get_fastcore_version())
+
+ if current_fastai_version < version.Version(fastai_min_version):
+ raise ImportError(
+ "`push_to_hub_fastai` and `from_pretrained_fastai` require a"
+ f" fastai>={fastai_min_version} version, but you are using fastai version"
+ f" {get_fastai_version()} which is incompatible. Upgrade with `pip install"
+ " fastai==2.5.6`."
+ )
+
+ if current_fastcore_version < version.Version(fastcore_min_version):
+ raise ImportError(
+ "`push_to_hub_fastai` and `from_pretrained_fastai` require a"
+ f" fastcore>={fastcore_min_version} version, but you are using fastcore"
+ f" version {get_fastcore_version()} which is incompatible. Upgrade with"
+ " `pip install fastcore==1.3.27`."
+ )
+
+
+def _check_fastai_fastcore_pyproject_versions(
+ storage_folder: str,
+ fastai_min_version: str = "2.4",
+ fastcore_min_version: str = "1.3.27",
+):
+ """
+ Checks that the `pyproject.toml` file in the directory `storage_folder` has fastai and fastcore versions
+ that are compatible with `from_pretrained_fastai` and `push_to_hub_fastai`. If `pyproject.toml` does not exist
+ or does not contain versions for fastai and fastcore, then it logs a warning.
+
+ Args:
+ storage_folder (`str`):
+ Folder to look for the `pyproject.toml` file.
+ fastai_min_version (`str`, *optional*):
+ The minimum fastai version supported.
+ fastcore_min_version (`str`, *optional*):
+ The minimum fastcore version supported.
+
+
+ Raises the following errors:
+
+ - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ if the `toml` module is not installed.
+ - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ if the `pyproject.toml` indicates a lower than minimum supported version of fastai or fastcore.
+
+
+ """
+
+ try:
+ import toml
+ except ModuleNotFoundError:
+ raise ImportError(
+ "`push_to_hub_fastai` and `from_pretrained_fastai` require the toml module."
+ " Install it with `pip install toml`."
+ )
+
+ # Checks that a `pyproject.toml`, with `build-system` and `requires` sections, exists in the repository. If so, get a list of required packages.
+ if not os.path.isfile(f"{storage_folder}/pyproject.toml"):
+ logger.warning(
+ "There is no `pyproject.toml` in the repository that contains the fastai"
+ " `Learner`. The `pyproject.toml` would allow us to verify that your fastai"
+ " and fastcore versions are compatible with those of the model you want to"
+ " load."
+ )
+ return
+ pyproject_toml = toml.load(f"{storage_folder}/pyproject.toml")
+
+ if "build-system" not in pyproject_toml.keys():
+ logger.warning(
+ "There is no `build-system` section in the pyproject.toml of the repository"
+ " that contains the fastai `Learner`. The `build-system` would allow us to"
+ " verify that your fastai and fastcore versions are compatible with those"
+ " of the model you want to load."
+ )
+ return
+ build_system_toml = pyproject_toml["build-system"]
+
+ if "requires" not in build_system_toml.keys():
+ logger.warning(
+ "There is no `requires` section in the pyproject.toml of the repository"
+ " that contains the fastai `Learner`. The `requires` would allow us to"
+ " verify that your fastai and fastcore versions are compatible with those"
+ " of the model you want to load."
+ )
+ return
+ package_versions = build_system_toml["requires"]
+
+ # Extracts contains fastai and fastcore versions from `pyproject.toml` if available.
+ # If the package is specified but not the version (e.g. "fastai" instead of "fastai=2.4"), the default versions are the highest.
+ fastai_packages = [pck for pck in package_versions if pck.startswith("fastai")]
+ if len(fastai_packages) == 0:
+ logger.warning("The repository does not have a fastai version specified in the `pyproject.toml`.")
+ # fastai_version is an empty string if not specified
+ else:
+ fastai_version = str(fastai_packages[0]).partition("=")[2]
+ if fastai_version != "" and version.Version(fastai_version) < version.Version(fastai_min_version):
+ raise ImportError(
+ "`from_pretrained_fastai` requires"
+ f" fastai>={fastai_min_version} version but the model to load uses"
+ f" {fastai_version} which is incompatible."
+ )
+
+ fastcore_packages = [pck for pck in package_versions if pck.startswith("fastcore")]
+ if len(fastcore_packages) == 0:
+ logger.warning("The repository does not have a fastcore version specified in the `pyproject.toml`.")
+ # fastcore_version is an empty string if not specified
+ else:
+ fastcore_version = str(fastcore_packages[0]).partition("=")[2]
+ if fastcore_version != "" and version.Version(fastcore_version) < version.Version(fastcore_min_version):
+ raise ImportError(
+ "`from_pretrained_fastai` requires"
+ f" fastcore>={fastcore_min_version} version, but you are using fastcore"
+ f" version {fastcore_version} which is incompatible."
+ )
+
+
+README_TEMPLATE = """---
+tags:
+- fastai
+---
+
+# Amazing!
+
+🥳 Congratulations on hosting your fastai model on the Hugging Face Hub!
+
+# Some next steps
+1. Fill out this model card with more information (see the template below and the [documentation here](https://huggingface.co/docs/hub/model-repos))!
+
+2. Create a demo in Gradio or Streamlit using 🤗 Spaces ([documentation here](https://huggingface.co/docs/hub/spaces)).
+
+3. Join the fastai community on the [Fastai Discord](https://discord.com/invite/YKrxeNn)!
+
+Greetings fellow fastlearner 🤝! Don't forget to delete this content from your model card.
+
+
+---
+
+
+# Model card
+
+## Model description
+More information needed
+
+## Intended uses & limitations
+More information needed
+
+## Training and evaluation data
+More information needed
+"""
+
+PYPROJECT_TEMPLATE = f"""[build-system]
+requires = ["setuptools>=40.8.0", "wheel", "python={get_python_version()}", "fastai={get_fastai_version()}", "fastcore={get_fastcore_version()}"]
+build-backend = "setuptools.build_meta:__legacy__"
+"""
+
+
+def _create_model_card(repo_dir: Path):
+ """
+ Creates a model card for the repository.
+
+ Args:
+ repo_dir (`Path`):
+ Directory where model card is created.
+ """
+ readme_path = repo_dir / "README.md"
+
+ if not readme_path.exists():
+ with readme_path.open("w", encoding="utf-8") as f:
+ f.write(README_TEMPLATE)
+
+
+def _create_model_pyproject(repo_dir: Path):
+ """
+ Creates a `pyproject.toml` for the repository.
+
+ Args:
+ repo_dir (`Path`):
+ Directory where `pyproject.toml` is created.
+ """
+ pyproject_path = repo_dir / "pyproject.toml"
+
+ if not pyproject_path.exists():
+ with pyproject_path.open("w", encoding="utf-8") as f:
+ f.write(PYPROJECT_TEMPLATE)
+
+
+def _save_pretrained_fastai(
+ learner,
+ save_directory: Union[str, Path],
+ config: Optional[Dict[str, Any]] = None,
+):
+ """
+ Saves a fastai learner to `save_directory` in pickle format using the default pickle protocol for the version of python used.
+
+ Args:
+ learner (`Learner`):
+ The `fastai.Learner` you'd like to save.
+ save_directory (`str` or `Path`):
+ Specific directory in which you want to save the fastai learner.
+ config (`dict`, *optional*):
+ Configuration object. Will be uploaded as a .json file. Example: 'https://huggingface.co/espejelomar/fastai-pet-breeds-classification/blob/main/config.json'.
+
+
+
+ Raises the following error:
+
+ - [`RuntimeError`](https://docs.python.org/3/library/exceptions.html#RuntimeError)
+ if the config file provided is not a dictionary.
+
+
+ """
+ _check_fastai_fastcore_versions()
+
+ os.makedirs(save_directory, exist_ok=True)
+
+ # if the user provides config then we update it with the fastai and fastcore versions in CONFIG_TEMPLATE.
+ if config is not None:
+ if not isinstance(config, dict):
+ raise RuntimeError(f"Provided config should be a dict. Got: '{type(config)}'")
+ path = os.path.join(save_directory, constants.CONFIG_NAME)
+ with open(path, "w") as f:
+ json.dump(config, f)
+
+ _create_model_card(Path(save_directory))
+ _create_model_pyproject(Path(save_directory))
+
+ # learner.export saves the model in `self.path`.
+ learner.path = Path(save_directory)
+ os.makedirs(save_directory, exist_ok=True)
+ try:
+ learner.export(
+ fname="model.pkl",
+ pickle_protocol=DEFAULT_PROTOCOL,
+ )
+ except PicklingError:
+ raise PicklingError(
+ "You are using a lambda function, i.e., an anonymous function. `pickle`"
+ " cannot pickle function objects and requires that all functions have"
+ " names. One possible solution is to name the function."
+ )
+
+
+@validate_hf_hub_args
+def from_pretrained_fastai(
+ repo_id: str,
+ revision: Optional[str] = None,
+):
+ """
+ Load pretrained fastai model from the Hub or from a local directory.
+
+ Args:
+ repo_id (`str`):
+ The location where the pickled fastai.Learner is. It can be either of the two:
+ - Hosted on the Hugging Face Hub. E.g.: 'espejelomar/fatai-pet-breeds-classification' or 'distilgpt2'.
+ You can add a `revision` by appending `@` at the end of `repo_id`. E.g.: `dbmdz/bert-base-german-cased@main`.
+ Revision is the specific model version to use. Since we use a git-based system for storing models and other
+ artifacts on the Hugging Face Hub, it can be a branch name, a tag name, or a commit id.
+ - Hosted locally. `repo_id` would be a directory containing the pickle and a pyproject.toml
+ indicating the fastai and fastcore versions used to build the `fastai.Learner`. E.g.: `./my_model_directory/`.
+ revision (`str`, *optional*):
+ Revision at which the repo's files are downloaded. See documentation of `snapshot_download`.
+
+ Returns:
+ The `fastai.Learner` model in the `repo_id` repo.
+ """
+ _check_fastai_fastcore_versions()
+
+ # Load the `repo_id` repo.
+ # `snapshot_download` returns the folder where the model was stored.
+ # `cache_dir` will be the default '/root/.cache/huggingface/hub'
+ if not os.path.isdir(repo_id):
+ storage_folder = snapshot_download(
+ repo_id=repo_id,
+ revision=revision,
+ library_name="fastai",
+ library_version=get_fastai_version(),
+ )
+ else:
+ storage_folder = repo_id
+
+ _check_fastai_fastcore_pyproject_versions(storage_folder)
+
+ from fastai.learner import load_learner # type: ignore
+
+ return load_learner(os.path.join(storage_folder, "model.pkl"))
+
+
+@validate_hf_hub_args
+def push_to_hub_fastai(
+ learner,
+ *,
+ repo_id: str,
+ commit_message: str = "Push FastAI model using huggingface_hub.",
+ private: Optional[bool] = None,
+ token: Optional[str] = None,
+ config: Optional[dict] = None,
+ branch: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ api_endpoint: Optional[str] = None,
+):
+ """
+ Upload learner checkpoint files to the Hub.
+
+ Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
+ `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
+ details.
+
+ Args:
+ learner (`Learner`):
+ The `fastai.Learner' you'd like to push to the Hub.
+ repo_id (`str`):
+ The repository id for your model in Hub in the format of "namespace/repo_name". The namespace can be your individual account or an organization to which you have write access (for example, 'stanfordnlp/stanza-de').
+ commit_message (`str`, *optional*):
+ Message to commit while pushing. Will default to :obj:`"add model"`.
+ private (`bool`, *optional*):
+ Whether or not the repository created should be private.
+ If `None` (default), will default to been public except if the organization's default is private.
+ token (`str`, *optional*):
+ The Hugging Face account token to use as HTTP bearer authorization for remote files. If :obj:`None`, the token will be asked by a prompt.
+ config (`dict`, *optional*):
+ Configuration object to be saved alongside the model weights.
+ branch (`str`, *optional*):
+ The git branch on which to push the model. This defaults to
+ the default branch as specified in your repository, which
+ defaults to `"main"`.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `branch` with that commit.
+ Defaults to `False`.
+ api_endpoint (`str`, *optional*):
+ The API endpoint to use when pushing the model to the hub.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are pushed.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not pushed.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo.
+
+ Returns:
+ The url of the commit of your model in the given repository.
+
+
+
+ Raises the following error:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if the user is not log on to the Hugging Face Hub.
+
+
+ """
+ _check_fastai_fastcore_versions()
+ api = HfApi(endpoint=api_endpoint)
+ repo_id = api.create_repo(repo_id=repo_id, token=token, private=private, exist_ok=True).repo_id
+
+ # Push the files to the repo in a single commit
+ with SoftTemporaryDirectory() as tmp:
+ saved_path = Path(tmp) / repo_id
+ _save_pretrained_fastai(learner, saved_path, config=config)
+ return api.upload_folder(
+ repo_id=repo_id,
+ token=token,
+ folder_path=saved_path,
+ commit_message=commit_message,
+ revision=branch,
+ create_pr=create_pr,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ delete_patterns=delete_patterns,
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/file_download.py b/env/Lib/site-packages/huggingface_hub/file_download.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9f3d9fba73b03357455ace7deefc2e7f41013ea
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/file_download.py
@@ -0,0 +1,1625 @@
+import contextlib
+import copy
+import errno
+import inspect
+import os
+import re
+import shutil
+import stat
+import time
+import uuid
+import warnings
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, BinaryIO, Dict, Literal, NoReturn, Optional, Tuple, Union
+from urllib.parse import quote, urlparse
+
+import requests
+
+from . import (
+ __version__, # noqa: F401 # for backward compatibility
+ constants,
+)
+from ._local_folder import get_local_download_paths, read_download_metadata, write_download_metadata
+from .constants import (
+ HUGGINGFACE_CO_URL_TEMPLATE, # noqa: F401 # for backward compatibility
+ HUGGINGFACE_HUB_CACHE, # noqa: F401 # for backward compatibility
+)
+from .errors import (
+ EntryNotFoundError,
+ FileMetadataError,
+ GatedRepoError,
+ HfHubHTTPError,
+ LocalEntryNotFoundError,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+)
+from .utils import (
+ OfflineModeIsEnabled,
+ SoftTemporaryDirectory,
+ WeakFileLock,
+ build_hf_headers,
+ get_fastai_version, # noqa: F401 # for backward compatibility
+ get_fastcore_version, # noqa: F401 # for backward compatibility
+ get_graphviz_version, # noqa: F401 # for backward compatibility
+ get_jinja_version, # noqa: F401 # for backward compatibility
+ get_pydot_version, # noqa: F401 # for backward compatibility
+ get_session,
+ get_tf_version, # noqa: F401 # for backward compatibility
+ get_torch_version, # noqa: F401 # for backward compatibility
+ hf_raise_for_status,
+ is_fastai_available, # noqa: F401 # for backward compatibility
+ is_fastcore_available, # noqa: F401 # for backward compatibility
+ is_graphviz_available, # noqa: F401 # for backward compatibility
+ is_jinja_available, # noqa: F401 # for backward compatibility
+ is_pydot_available, # noqa: F401 # for backward compatibility
+ is_tf_available, # noqa: F401 # for backward compatibility
+ is_torch_available, # noqa: F401 # for backward compatibility
+ logging,
+ reset_sessions,
+ tqdm,
+ validate_hf_hub_args,
+)
+from .utils._http import _adjust_range_header
+from .utils._runtime import _PY_VERSION # noqa: F401 # for backward compatibility
+from .utils._typing import HTTP_METHOD_T
+from .utils.sha import sha_fileobj
+from .utils.tqdm import is_tqdm_disabled
+
+
+logger = logging.get_logger(__name__)
+
+# Return value when trying to load a file from cache but the file does not exist in the distant repo.
+_CACHED_NO_EXIST = object()
+_CACHED_NO_EXIST_T = Any
+
+# Regex to get filename from a "Content-Disposition" header for CDN-served files
+HEADER_FILENAME_PATTERN = re.compile(r'filename="(?P.*?)";')
+
+# Regex to check if the revision IS directly a commit_hash
+REGEX_COMMIT_HASH = re.compile(r"^[0-9a-f]{40}$")
+
+# Regex to check if the file etag IS a valid sha256
+REGEX_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+
+_are_symlinks_supported_in_dir: Dict[str, bool] = {}
+
+
+def are_symlinks_supported(cache_dir: Union[str, Path, None] = None) -> bool:
+ """Return whether the symlinks are supported on the machine.
+
+ Since symlinks support can change depending on the mounted disk, we need to check
+ on the precise cache folder. By default, the default HF cache directory is checked.
+
+ Args:
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+
+ Returns: [bool] Whether symlinks are supported in the directory.
+ """
+ # Defaults to HF cache
+ if cache_dir is None:
+ cache_dir = constants.HF_HUB_CACHE
+ cache_dir = str(Path(cache_dir).expanduser().resolve()) # make it unique
+
+ # Check symlink compatibility only once (per cache directory) at first time use
+ if cache_dir not in _are_symlinks_supported_in_dir:
+ _are_symlinks_supported_in_dir[cache_dir] = True
+
+ os.makedirs(cache_dir, exist_ok=True)
+ with SoftTemporaryDirectory(dir=cache_dir) as tmpdir:
+ src_path = Path(tmpdir) / "dummy_file_src"
+ src_path.touch()
+ dst_path = Path(tmpdir) / "dummy_file_dst"
+
+ # Relative source path as in `_create_symlink``
+ relative_src = os.path.relpath(src_path, start=os.path.dirname(dst_path))
+ try:
+ os.symlink(relative_src, dst_path)
+ except OSError:
+ # Likely running on Windows
+ _are_symlinks_supported_in_dir[cache_dir] = False
+
+ if not constants.HF_HUB_DISABLE_SYMLINKS_WARNING:
+ message = (
+ "`huggingface_hub` cache-system uses symlinks by default to"
+ " efficiently store duplicated files but your machine does not"
+ f" support them in {cache_dir}. Caching files will still work"
+ " but in a degraded version that might require more space on"
+ " your disk. This warning can be disabled by setting the"
+ " `HF_HUB_DISABLE_SYMLINKS_WARNING` environment variable. For"
+ " more details, see"
+ " https://huggingface.co/docs/huggingface_hub/how-to-cache#limitations."
+ )
+ if os.name == "nt":
+ message += (
+ "\nTo support symlinks on Windows, you either need to"
+ " activate Developer Mode or to run Python as an"
+ " administrator. In order to activate developer mode,"
+ " see this article:"
+ " https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development"
+ )
+ warnings.warn(message)
+
+ return _are_symlinks_supported_in_dir[cache_dir]
+
+
+@dataclass(frozen=True)
+class HfFileMetadata:
+ """Data structure containing information about a file versioned on the Hub.
+
+ Returned by [`get_hf_file_metadata`] based on a URL.
+
+ Args:
+ commit_hash (`str`, *optional*):
+ The commit_hash related to the file.
+ etag (`str`, *optional*):
+ Etag of the file on the server.
+ location (`str`):
+ Location where to download the file. Can be a Hub url or not (CDN).
+ size (`size`):
+ Size of the file. In case of an LFS file, contains the size of the actual
+ LFS file, not the pointer.
+ """
+
+ commit_hash: Optional[str]
+ etag: Optional[str]
+ location: str
+ size: Optional[int]
+
+
+@validate_hf_hub_args
+def hf_hub_url(
+ repo_id: str,
+ filename: str,
+ *,
+ subfolder: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ endpoint: Optional[str] = None,
+) -> str:
+ """Construct the URL of a file from the given information.
+
+ The resolved address can either be a huggingface.co-hosted url, or a link to
+ Cloudfront (a Content Delivery Network, or CDN) for large files which are
+ more than a few MBs.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) name and a repo name separated
+ by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ subfolder (`str`, *optional*):
+ An optional value corresponding to a folder inside the repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import hf_hub_url
+
+ >>> hf_hub_url(
+ ... repo_id="julien-c/EsperBERTo-small", filename="pytorch_model.bin"
+ ... )
+ 'https://huggingface.co/julien-c/EsperBERTo-small/resolve/main/pytorch_model.bin'
+ ```
+
+
+
+ Notes:
+
+ Cloudfront is replicated over the globe so downloads are way faster for
+ the end user (and it also lowers our bandwidth costs).
+
+ Cloudfront aggressively caches files by default (default TTL is 24
+ hours), however this is not an issue here because we implement a
+ git-based versioning system on huggingface.co, which means that we store
+ the files on S3/Cloudfront in a content-addressable way (i.e., the file
+ name is its hash). Using content-addressable filenames means cache can't
+ ever be stale.
+
+ In terms of client-side caching from this library, we base our caching
+ on the objects' entity tag (`ETag`), which is an identifier of a
+ specific version of a resource [1]_. An object's ETag is: its git-sha1
+ if stored in git, or its sha256 if stored in git-lfs.
+
+
+
+ References:
+
+ - [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
+ """
+ if subfolder == "":
+ subfolder = None
+ if subfolder is not None:
+ filename = f"{subfolder}/{filename}"
+
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ if repo_type in constants.REPO_TYPES_URL_PREFIXES:
+ repo_id = constants.REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+
+ if revision is None:
+ revision = constants.DEFAULT_REVISION
+ url = HUGGINGFACE_CO_URL_TEMPLATE.format(
+ repo_id=repo_id, revision=quote(revision, safe=""), filename=quote(filename)
+ )
+ # Update endpoint if provided
+ if endpoint is not None and url.startswith(constants.ENDPOINT):
+ url = endpoint + url[len(constants.ENDPOINT) :]
+ return url
+
+
+def _request_wrapper(
+ method: HTTP_METHOD_T, url: str, *, follow_relative_redirects: bool = False, **params
+) -> requests.Response:
+ """Wrapper around requests methods to follow relative redirects if `follow_relative_redirects=True` even when
+ `allow_redirection=False`.
+
+ Args:
+ method (`str`):
+ HTTP method, such as 'GET' or 'HEAD'.
+ url (`str`):
+ The URL of the resource to fetch.
+ follow_relative_redirects (`bool`, *optional*, defaults to `False`)
+ If True, relative redirection (redirection to the same site) will be resolved even when `allow_redirection`
+ kwarg is set to False. Useful when we want to follow a redirection to a renamed repository without
+ following redirection to a CDN.
+ **params (`dict`, *optional*):
+ Params to pass to `requests.request`.
+ """
+ # Recursively follow relative redirects
+ if follow_relative_redirects:
+ response = _request_wrapper(
+ method=method,
+ url=url,
+ follow_relative_redirects=False,
+ **params,
+ )
+
+ # If redirection, we redirect only relative paths.
+ # This is useful in case of a renamed repository.
+ if 300 <= response.status_code <= 399:
+ parsed_target = urlparse(response.headers["Location"])
+ if parsed_target.netloc == "":
+ # This means it is a relative 'location' headers, as allowed by RFC 7231.
+ # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
+ # We want to follow this relative redirect !
+ #
+ # Highly inspired by `resolve_redirects` from requests library.
+ # See https://github.com/psf/requests/blob/main/requests/sessions.py#L159
+ next_url = urlparse(url)._replace(path=parsed_target.path).geturl()
+ return _request_wrapper(method=method, url=next_url, follow_relative_redirects=True, **params)
+ return response
+
+ # Perform request and return if status_code is not in the retry list.
+ response = get_session().request(method=method, url=url, **params)
+ hf_raise_for_status(response)
+ return response
+
+
+def http_get(
+ url: str,
+ temp_file: BinaryIO,
+ *,
+ proxies: Optional[Dict] = None,
+ resume_size: int = 0,
+ headers: Optional[Dict[str, Any]] = None,
+ expected_size: Optional[int] = None,
+ displayed_filename: Optional[str] = None,
+ _nb_retries: int = 5,
+ _tqdm_bar: Optional[tqdm] = None,
+) -> None:
+ """
+ Download a remote file. Do not gobble up errors, and will return errors tailored to the Hugging Face Hub.
+
+ If ConnectionError (SSLError) or ReadTimeout happen while streaming data from the server, it is most likely a
+ transient error (network outage?). We log a warning message and try to resume the download a few times before
+ giving up. The method gives up after 5 attempts if no new data has being received from the server.
+
+ Args:
+ url (`str`):
+ The URL of the file to download.
+ temp_file (`BinaryIO`):
+ The file-like object where to save the file.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to `requests.request`.
+ resume_size (`int`, *optional*):
+ The number of bytes already downloaded. If set to 0 (default), the whole file is download. If set to a
+ positive number, the download will resume at the given position.
+ headers (`dict`, *optional*):
+ Dictionary of HTTP Headers to send with the request.
+ expected_size (`int`, *optional*):
+ The expected size of the file to download. If set, the download will raise an error if the size of the
+ received content is different from the expected one.
+ displayed_filename (`str`, *optional*):
+ The filename of the file that is being downloaded. Value is used only to display a nice progress bar. If
+ not set, the filename is guessed from the URL or the `Content-Disposition` header.
+ """
+ if expected_size is not None and resume_size == expected_size:
+ # If the file is already fully downloaded, we don't need to download it again.
+ return
+
+ hf_transfer = None
+ if constants.HF_HUB_ENABLE_HF_TRANSFER:
+ if resume_size != 0:
+ warnings.warn("'hf_transfer' does not support `resume_size`: falling back to regular download method")
+ elif proxies is not None:
+ warnings.warn("'hf_transfer' does not support `proxies`: falling back to regular download method")
+ else:
+ try:
+ import hf_transfer # type: ignore[no-redef]
+ except ImportError:
+ raise ValueError(
+ "Fast download using 'hf_transfer' is enabled"
+ " (HF_HUB_ENABLE_HF_TRANSFER=1) but 'hf_transfer' package is not"
+ " available in your environment. Try `pip install hf_transfer`."
+ )
+
+ initial_headers = headers
+ headers = copy.deepcopy(headers) or {}
+ if resume_size > 0:
+ headers["Range"] = _adjust_range_header(headers.get("Range"), resume_size)
+
+ r = _request_wrapper(
+ method="GET", url=url, stream=True, proxies=proxies, headers=headers, timeout=constants.HF_HUB_DOWNLOAD_TIMEOUT
+ )
+ hf_raise_for_status(r)
+ content_length = r.headers.get("Content-Length")
+
+ # NOTE: 'total' is the total number of bytes to download, not the number of bytes in the file.
+ # If the file is compressed, the number of bytes in the saved file will be higher than 'total'.
+ total = resume_size + int(content_length) if content_length is not None else None
+
+ if displayed_filename is None:
+ displayed_filename = url
+ content_disposition = r.headers.get("Content-Disposition")
+ if content_disposition is not None:
+ match = HEADER_FILENAME_PATTERN.search(content_disposition)
+ if match is not None:
+ # Means file is on CDN
+ displayed_filename = match.groupdict()["filename"]
+
+ # Truncate filename if too long to display
+ if len(displayed_filename) > 40:
+ displayed_filename = f"(…){displayed_filename[-40:]}"
+
+ consistency_error_message = (
+ f"Consistency check failed: file should be of size {expected_size} but has size"
+ f" {{actual_size}} ({displayed_filename}).\nThis is usually due to network issues while downloading the file."
+ " Please retry with `force_download=True`."
+ )
+
+ # Stream file to buffer
+ progress_cm: tqdm = (
+ tqdm( # type: ignore[assignment]
+ unit="B",
+ unit_scale=True,
+ total=total,
+ initial=resume_size,
+ desc=displayed_filename,
+ disable=is_tqdm_disabled(logger.getEffectiveLevel()),
+ name="huggingface_hub.http_get",
+ )
+ if _tqdm_bar is None
+ else contextlib.nullcontext(_tqdm_bar)
+ # ^ `contextlib.nullcontext` mimics a context manager that does nothing
+ # Makes it easier to use the same code path for both cases but in the later
+ # case, the progress bar is not closed when exiting the context manager.
+ )
+
+ with progress_cm as progress:
+ if hf_transfer and total is not None and total > 5 * constants.DOWNLOAD_CHUNK_SIZE:
+ supports_callback = "callback" in inspect.signature(hf_transfer.download).parameters
+ if not supports_callback:
+ warnings.warn(
+ "You are using an outdated version of `hf_transfer`. "
+ "Consider upgrading to latest version to enable progress bars "
+ "using `pip install -U hf_transfer`."
+ )
+ try:
+ hf_transfer.download(
+ url=url,
+ filename=temp_file.name,
+ max_files=constants.HF_TRANSFER_CONCURRENCY,
+ chunk_size=constants.DOWNLOAD_CHUNK_SIZE,
+ headers=headers,
+ parallel_failures=3,
+ max_retries=5,
+ **({"callback": progress.update} if supports_callback else {}),
+ )
+ except Exception as e:
+ raise RuntimeError(
+ "An error occurred while downloading using `hf_transfer`. Consider"
+ " disabling HF_HUB_ENABLE_HF_TRANSFER for better error handling."
+ ) from e
+ if not supports_callback:
+ progress.update(total)
+ if expected_size is not None and expected_size != os.path.getsize(temp_file.name):
+ raise EnvironmentError(
+ consistency_error_message.format(
+ actual_size=os.path.getsize(temp_file.name),
+ )
+ )
+ return
+ new_resume_size = resume_size
+ try:
+ for chunk in r.iter_content(chunk_size=constants.DOWNLOAD_CHUNK_SIZE):
+ if chunk: # filter out keep-alive new chunks
+ progress.update(len(chunk))
+ temp_file.write(chunk)
+ new_resume_size += len(chunk)
+ # Some data has been downloaded from the server so we reset the number of retries.
+ _nb_retries = 5
+ except (requests.ConnectionError, requests.ReadTimeout) as e:
+ # If ConnectionError (SSLError) or ReadTimeout happen while streaming data from the server, it is most likely
+ # a transient error (network outage?). We log a warning message and try to resume the download a few times
+ # before giving up. Tre retry mechanism is basic but should be enough in most cases.
+ if _nb_retries <= 0:
+ logger.warning("Error while downloading from %s: %s\nMax retries exceeded.", url, str(e))
+ raise
+ logger.warning("Error while downloading from %s: %s\nTrying to resume download...", url, str(e))
+ time.sleep(1)
+ reset_sessions() # In case of SSLError it's best to reset the shared requests.Session objects
+ return http_get(
+ url=url,
+ temp_file=temp_file,
+ proxies=proxies,
+ resume_size=new_resume_size,
+ headers=initial_headers,
+ expected_size=expected_size,
+ _nb_retries=_nb_retries - 1,
+ _tqdm_bar=_tqdm_bar,
+ )
+
+ if expected_size is not None and expected_size != temp_file.tell():
+ raise EnvironmentError(
+ consistency_error_message.format(
+ actual_size=temp_file.tell(),
+ )
+ )
+
+
+def _normalize_etag(etag: Optional[str]) -> Optional[str]:
+ """Normalize ETag HTTP header, so it can be used to create nice filepaths.
+
+ The HTTP spec allows two forms of ETag:
+ ETag: W/""
+ ETag: ""
+
+ For now, we only expect the second form from the server, but we want to be future-proof so we support both. For
+ more context, see `TestNormalizeEtag` tests and https://github.com/huggingface/huggingface_hub/pull/1428.
+
+ Args:
+ etag (`str`, *optional*): HTTP header
+
+ Returns:
+ `str` or `None`: string that can be used as a nice directory name.
+ Returns `None` if input is None.
+ """
+ if etag is None:
+ return None
+ return etag.lstrip("W/").strip('"')
+
+
+def _create_relative_symlink(src: str, dst: str, new_blob: bool = False) -> None:
+ """Alias method used in `transformers` conversion script."""
+ return _create_symlink(src=src, dst=dst, new_blob=new_blob)
+
+
+def _create_symlink(src: str, dst: str, new_blob: bool = False) -> None:
+ """Create a symbolic link named dst pointing to src.
+
+ By default, it will try to create a symlink using a relative path. Relative paths have 2 advantages:
+ - If the cache_folder is moved (example: back-up on a shared drive), relative paths within the cache folder will
+ not break.
+ - Relative paths seems to be better handled on Windows. Issue was reported 3 times in less than a week when
+ changing from relative to absolute paths. See https://github.com/huggingface/huggingface_hub/issues/1398,
+ https://github.com/huggingface/diffusers/issues/2729 and https://github.com/huggingface/transformers/pull/22228.
+ NOTE: The issue with absolute paths doesn't happen on admin mode.
+ When creating a symlink from the cache to a local folder, it is possible that a relative path cannot be created.
+ This happens when paths are not on the same volume. In that case, we use absolute paths.
+
+
+ The result layout looks something like
+ └── [ 128] snapshots
+ ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
+ │ ├── [ 52] README.md -> ../../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
+ │ └── [ 76] pytorch_model.bin -> ../../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+
+ If symlinks cannot be created on this platform (most likely to be Windows), the workaround is to avoid symlinks by
+ having the actual file in `dst`. If it is a new file (`new_blob=True`), we move it to `dst`. If it is not a new file
+ (`new_blob=False`), we don't know if the blob file is already referenced elsewhere. To avoid breaking existing
+ cache, the file is duplicated on the disk.
+
+ In case symlinks are not supported, a warning message is displayed to the user once when loading `huggingface_hub`.
+ The warning message can be disabled with the `DISABLE_SYMLINKS_WARNING` environment variable.
+ """
+ try:
+ os.remove(dst)
+ except OSError:
+ pass
+
+ abs_src = os.path.abspath(os.path.expanduser(src))
+ abs_dst = os.path.abspath(os.path.expanduser(dst))
+ abs_dst_folder = os.path.dirname(abs_dst)
+
+ # Use relative_dst in priority
+ try:
+ relative_src = os.path.relpath(abs_src, abs_dst_folder)
+ except ValueError:
+ # Raised on Windows if src and dst are not on the same volume. This is the case when creating a symlink to a
+ # local_dir instead of within the cache directory.
+ # See https://docs.python.org/3/library/os.path.html#os.path.relpath
+ relative_src = None
+
+ try:
+ commonpath = os.path.commonpath([abs_src, abs_dst])
+ _support_symlinks = are_symlinks_supported(commonpath)
+ except ValueError:
+ # Raised if src and dst are not on the same volume. Symlinks will still work on Linux/Macos.
+ # See https://docs.python.org/3/library/os.path.html#os.path.commonpath
+ _support_symlinks = os.name != "nt"
+ except PermissionError:
+ # Permission error means src and dst are not in the same volume (e.g. destination path has been provided
+ # by the user via `local_dir`. Let's test symlink support there)
+ _support_symlinks = are_symlinks_supported(abs_dst_folder)
+ except OSError as e:
+ # OS error (errno=30) means that the commonpath is readonly on Linux/MacOS.
+ if e.errno == errno.EROFS:
+ _support_symlinks = are_symlinks_supported(abs_dst_folder)
+ else:
+ raise
+
+ # Symlinks are supported => let's create a symlink.
+ if _support_symlinks:
+ src_rel_or_abs = relative_src or abs_src
+ logger.debug(f"Creating pointer from {src_rel_or_abs} to {abs_dst}")
+ try:
+ os.symlink(src_rel_or_abs, abs_dst)
+ return
+ except FileExistsError:
+ if os.path.islink(abs_dst) and os.path.realpath(abs_dst) == os.path.realpath(abs_src):
+ # `abs_dst` already exists and is a symlink to the `abs_src` blob. It is most likely that the file has
+ # been cached twice concurrently (exactly between `os.remove` and `os.symlink`). Do nothing.
+ return
+ else:
+ # Very unlikely to happen. Means a file `dst` has been created exactly between `os.remove` and
+ # `os.symlink` and is not a symlink to the `abs_src` blob file. Raise exception.
+ raise
+ except PermissionError:
+ # Permission error means src and dst are not in the same volume (e.g. download to local dir) and symlink
+ # is supported on both volumes but not between them. Let's just make a hard copy in that case.
+ pass
+
+ # Symlinks are not supported => let's move or copy the file.
+ if new_blob:
+ logger.info(f"Symlink not supported. Moving file from {abs_src} to {abs_dst}")
+ shutil.move(abs_src, abs_dst, copy_function=_copy_no_matter_what)
+ else:
+ logger.info(f"Symlink not supported. Copying file from {abs_src} to {abs_dst}")
+ shutil.copyfile(abs_src, abs_dst)
+
+
+def _cache_commit_hash_for_specific_revision(storage_folder: str, revision: str, commit_hash: str) -> None:
+ """Cache reference between a revision (tag, branch or truncated commit hash) and the corresponding commit hash.
+
+ Does nothing if `revision` is already a proper `commit_hash` or reference is already cached.
+ """
+ if revision != commit_hash:
+ ref_path = Path(storage_folder) / "refs" / revision
+ ref_path.parent.mkdir(parents=True, exist_ok=True)
+ if not ref_path.exists() or commit_hash != ref_path.read_text():
+ # Update ref only if has been updated. Could cause useless error in case
+ # repo is already cached and user doesn't have write access to cache folder.
+ # See https://github.com/huggingface/huggingface_hub/issues/1216.
+ ref_path.write_text(commit_hash)
+
+
+@validate_hf_hub_args
+def repo_folder_name(*, repo_id: str, repo_type: str) -> str:
+ """Return a serialized version of a hf.co repo name and type, safe for disk storage
+ as a single non-nested folder.
+
+ Example: models--julien-c--EsperBERTo-small
+ """
+ # remove all `/` occurrences to correctly convert repo to directory name
+ parts = [f"{repo_type}s", *repo_id.split("/")]
+ return constants.REPO_ID_SEPARATOR.join(parts)
+
+
+def _check_disk_space(expected_size: int, target_dir: Union[str, Path]) -> None:
+ """Check disk usage and log a warning if there is not enough disk space to download the file.
+
+ Args:
+ expected_size (`int`):
+ The expected size of the file in bytes.
+ target_dir (`str`):
+ The directory where the file will be stored after downloading.
+ """
+
+ target_dir = Path(target_dir) # format as `Path`
+ for path in [target_dir] + list(target_dir.parents): # first check target_dir, then each parents one by one
+ try:
+ target_dir_free = shutil.disk_usage(path).free
+ if target_dir_free < expected_size:
+ warnings.warn(
+ "Not enough free disk space to download the file. "
+ f"The expected file size is: {expected_size / 1e6:.2f} MB. "
+ f"The target location {target_dir} only has {target_dir_free / 1e6:.2f} MB free disk space."
+ )
+ return
+ except OSError: # raise on anything: file does not exist or space disk cannot be checked
+ pass
+
+
+@validate_hf_hub_args
+def hf_hub_download(
+ repo_id: str,
+ filename: str,
+ *,
+ subfolder: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ user_agent: Union[Dict, str, None] = None,
+ force_download: bool = False,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
+ token: Union[bool, str, None] = None,
+ local_files_only: bool = False,
+ headers: Optional[Dict[str, str]] = None,
+ endpoint: Optional[str] = None,
+ resume_download: Optional[bool] = None,
+ force_filename: Optional[str] = None,
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+) -> str:
+ """Download a given file if it's not already present in the local cache.
+
+ The new cache file layout looks like this:
+ - The cache directory contains one subfolder per repo_id (namespaced by repo type)
+ - inside each repo folder:
+ - refs is a list of the latest known revision => commit_hash pairs
+ - blobs contains the actual file blobs (identified by their git-sha or sha256, depending on
+ whether they're LFS files or not)
+ - snapshots contains one subfolder per commit, each "commit" contains the subset of the files
+ that have been resolved at that particular commit. Each filename is a symlink to the blob
+ at that particular commit.
+
+ ```
+ [ 96] .
+ └── [ 160] models--julien-c--EsperBERTo-small
+ ├── [ 160] blobs
+ │ ├── [321M] 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ │ ├── [ 398] 7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ │ └── [1.4K] d7edf6bd2a681fb0175f7735299831ee1b22b812
+ ├── [ 96] refs
+ │ └── [ 40] main
+ └── [ 128] snapshots
+ ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
+ │ ├── [ 52] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
+ │ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ └── [ 128] bbc77c8132af1cc5cf678da3f1ddf2de43606d48
+ ├── [ 52] README.md -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ ```
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this
+ option, the `cache_dir` will not be used and a `.cache/huggingface/` folder will be created at the root of `local_dir`
+ to store some metadata related to the downloaded files. While this mechanism is not as robust as the main
+ cache-system, it's optimized for regularly pulling the latest version of a repository.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ subfolder (`str`, *optional*):
+ An optional value corresponding to a folder inside the model repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded file will be placed under this directory.
+ user_agent (`dict`, `str`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in
+ the local cache.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ token (`str`, `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If a string, it's used as the authentication token.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ headers (`dict`, *optional*):
+ Additional headers to be sent with the request.
+
+ Returns:
+ `str`: Local path of file or if networking is off, last version of file cached on disk.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+ [`~utils.LocalEntryNotFoundError`]
+ If network is disabled or unavailable and file is not found in cache.
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `token=True` but the token cannot be found.
+ [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
+ If ETag cannot be determined.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If some parameter value is invalid.
+
+ """
+ if constants.HF_HUB_ETAG_TIMEOUT != constants.DEFAULT_ETAG_TIMEOUT:
+ # Respect environment variable above user value
+ etag_timeout = constants.HF_HUB_ETAG_TIMEOUT
+
+ if force_filename is not None:
+ warnings.warn(
+ "The `force_filename` parameter is deprecated as a new caching system, "
+ "which keeps the filenames as they are on the Hub, is now in place.",
+ FutureWarning,
+ )
+ if resume_download is not None:
+ warnings.warn(
+ "`resume_download` is deprecated and will be removed in version 1.0.0. "
+ "Downloads always resume when possible. "
+ "If you want to force a new download, use `force_download=True`.",
+ FutureWarning,
+ )
+
+ if cache_dir is None:
+ cache_dir = constants.HF_HUB_CACHE
+ if revision is None:
+ revision = constants.DEFAULT_REVISION
+ if isinstance(cache_dir, Path):
+ cache_dir = str(cache_dir)
+ if isinstance(local_dir, Path):
+ local_dir = str(local_dir)
+
+ if subfolder == "":
+ subfolder = None
+ if subfolder is not None:
+ # This is used to create a URL, and not a local path, hence the forward slash.
+ filename = f"{subfolder}/{filename}"
+
+ if repo_type is None:
+ repo_type = "model"
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type: {repo_type}. Accepted repo types are: {str(constants.REPO_TYPES)}")
+
+ hf_headers = build_hf_headers(
+ token=token,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ headers=headers,
+ )
+
+ if local_dir is not None:
+ if local_dir_use_symlinks != "auto":
+ warnings.warn(
+ "`local_dir_use_symlinks` parameter is deprecated and will be ignored. "
+ "The process to download files to a local folder has been updated and do "
+ "not rely on symlinks anymore. You only need to pass a destination folder "
+ "as`local_dir`.\n"
+ "For more details, check out https://huggingface.co/docs/huggingface_hub/main/en/guides/download#download-files-to-local-folder."
+ )
+
+ return _hf_hub_download_to_local_dir(
+ # Destination
+ local_dir=local_dir,
+ # File info
+ repo_id=repo_id,
+ repo_type=repo_type,
+ filename=filename,
+ revision=revision,
+ # HTTP info
+ endpoint=endpoint,
+ etag_timeout=etag_timeout,
+ headers=hf_headers,
+ proxies=proxies,
+ token=token,
+ # Additional options
+ cache_dir=cache_dir,
+ force_download=force_download,
+ local_files_only=local_files_only,
+ )
+ else:
+ return _hf_hub_download_to_cache_dir(
+ # Destination
+ cache_dir=cache_dir,
+ # File info
+ repo_id=repo_id,
+ filename=filename,
+ repo_type=repo_type,
+ revision=revision,
+ # HTTP info
+ endpoint=endpoint,
+ etag_timeout=etag_timeout,
+ headers=hf_headers,
+ proxies=proxies,
+ token=token,
+ # Additional options
+ local_files_only=local_files_only,
+ force_download=force_download,
+ )
+
+
+def _hf_hub_download_to_cache_dir(
+ *,
+ # Destination
+ cache_dir: str,
+ # File info
+ repo_id: str,
+ filename: str,
+ repo_type: str,
+ revision: str,
+ # HTTP info
+ endpoint: Optional[str],
+ etag_timeout: float,
+ headers: Dict[str, str],
+ proxies: Optional[Dict],
+ token: Optional[Union[bool, str]],
+ # Additional options
+ local_files_only: bool,
+ force_download: bool,
+) -> str:
+ """Download a given file to a cache folder, if not already present.
+
+ Method should not be called directly. Please use `hf_hub_download` instead.
+ """
+ locks_dir = os.path.join(cache_dir, ".locks")
+ storage_folder = os.path.join(cache_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type))
+
+ # cross platform transcription of filename, to be used as a local file path.
+ relative_filename = os.path.join(*filename.split("/"))
+ if os.name == "nt":
+ if relative_filename.startswith("..\\") or "\\..\\" in relative_filename:
+ raise ValueError(
+ f"Invalid filename: cannot handle filename '{relative_filename}' on Windows. Please ask the repository"
+ " owner to rename this file."
+ )
+
+ # if user provides a commit_hash and they already have the file on disk, shortcut everything.
+ if REGEX_COMMIT_HASH.match(revision):
+ pointer_path = _get_pointer_path(storage_folder, revision, relative_filename)
+ if os.path.exists(pointer_path) and not force_download:
+ return pointer_path
+
+ # Try to get metadata (etag, commit_hash, url, size) from the server.
+ # If we can't, a HEAD request error is returned.
+ (url_to_download, etag, commit_hash, expected_size, head_call_error) = _get_metadata_or_catch_error(
+ repo_id=repo_id,
+ filename=filename,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=endpoint,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ headers=headers,
+ token=token,
+ local_files_only=local_files_only,
+ storage_folder=storage_folder,
+ relative_filename=relative_filename,
+ )
+
+ # etag can be None for several reasons:
+ # 1. we passed local_files_only.
+ # 2. we don't have a connection
+ # 3. Hub is down (HTTP 500, 503, 504)
+ # 4. repo is not found -for example private or gated- and invalid/missing token sent
+ # 5. Hub is blocked by a firewall or proxy is not set correctly.
+ # => Try to get the last downloaded one from the specified revision.
+ #
+ # If the specified revision is a commit hash, look inside "snapshots".
+ # If the specified revision is a branch or tag, look inside "refs".
+ if head_call_error is not None:
+ # Couldn't make a HEAD call => let's try to find a local file
+ if not force_download:
+ commit_hash = None
+ if REGEX_COMMIT_HASH.match(revision):
+ commit_hash = revision
+ else:
+ ref_path = os.path.join(storage_folder, "refs", revision)
+ if os.path.isfile(ref_path):
+ with open(ref_path) as f:
+ commit_hash = f.read()
+
+ # Return pointer file if exists
+ if commit_hash is not None:
+ pointer_path = _get_pointer_path(storage_folder, commit_hash, relative_filename)
+ if os.path.exists(pointer_path) and not force_download:
+ return pointer_path
+
+ # Otherwise, raise appropriate error
+ _raise_on_head_call_error(head_call_error, force_download, local_files_only)
+
+ # From now on, etag, commit_hash, url and size are not None.
+ assert etag is not None, "etag must have been retrieved from server"
+ assert commit_hash is not None, "commit_hash must have been retrieved from server"
+ assert url_to_download is not None, "file location must have been retrieved from server"
+ assert expected_size is not None, "expected_size must have been retrieved from server"
+ blob_path = os.path.join(storage_folder, "blobs", etag)
+ pointer_path = _get_pointer_path(storage_folder, commit_hash, relative_filename)
+
+ os.makedirs(os.path.dirname(blob_path), exist_ok=True)
+ os.makedirs(os.path.dirname(pointer_path), exist_ok=True)
+
+ # if passed revision is not identical to commit_hash
+ # then revision has to be a branch name or tag name.
+ # In that case store a ref.
+ _cache_commit_hash_for_specific_revision(storage_folder, revision, commit_hash)
+
+ # If file already exists, return it (except if force_download=True)
+ if not force_download:
+ if os.path.exists(pointer_path):
+ return pointer_path
+
+ if os.path.exists(blob_path):
+ # we have the blob already, but not the pointer
+ _create_symlink(blob_path, pointer_path, new_blob=False)
+ return pointer_path
+
+ # Prevent parallel downloads of the same file with a lock.
+ # etag could be duplicated across repos,
+ lock_path = os.path.join(locks_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type), f"{etag}.lock")
+
+ # Some Windows versions do not allow for paths longer than 255 characters.
+ # In this case, we must specify it as an extended path by using the "\\?\" prefix.
+ if os.name == "nt" and len(os.path.abspath(lock_path)) > 255:
+ lock_path = "\\\\?\\" + os.path.abspath(lock_path)
+
+ if os.name == "nt" and len(os.path.abspath(blob_path)) > 255:
+ blob_path = "\\\\?\\" + os.path.abspath(blob_path)
+
+ Path(lock_path).parent.mkdir(parents=True, exist_ok=True)
+ with WeakFileLock(lock_path):
+ _download_to_tmp_and_move(
+ incomplete_path=Path(blob_path + ".incomplete"),
+ destination_path=Path(blob_path),
+ url_to_download=url_to_download,
+ proxies=proxies,
+ headers=headers,
+ expected_size=expected_size,
+ filename=filename,
+ force_download=force_download,
+ )
+ if not os.path.exists(pointer_path):
+ _create_symlink(blob_path, pointer_path, new_blob=True)
+
+ return pointer_path
+
+
+def _hf_hub_download_to_local_dir(
+ *,
+ # Destination
+ local_dir: Union[str, Path],
+ # File info
+ repo_id: str,
+ repo_type: str,
+ filename: str,
+ revision: str,
+ # HTTP info
+ endpoint: Optional[str],
+ etag_timeout: float,
+ headers: Dict[str, str],
+ proxies: Optional[Dict],
+ token: Union[bool, str, None],
+ # Additional options
+ cache_dir: str,
+ force_download: bool,
+ local_files_only: bool,
+) -> str:
+ """Download a given file to a local folder, if not already present.
+
+ Method should not be called directly. Please use `hf_hub_download` instead.
+ """
+ # Some Windows versions do not allow for paths longer than 255 characters.
+ # In this case, we must specify it as an extended path by using the "\\?\" prefix.
+ if os.name == "nt" and len(os.path.abspath(local_dir)) > 255:
+ local_dir = "\\\\?\\" + os.path.abspath(local_dir)
+ local_dir = Path(local_dir)
+ paths = get_local_download_paths(local_dir=local_dir, filename=filename)
+ local_metadata = read_download_metadata(local_dir=local_dir, filename=filename)
+
+ # Local file exists + metadata exists + commit_hash matches => return file
+ if (
+ not force_download
+ and REGEX_COMMIT_HASH.match(revision)
+ and paths.file_path.is_file()
+ and local_metadata is not None
+ and local_metadata.commit_hash == revision
+ ):
+ return str(paths.file_path)
+
+ # Local file doesn't exist or commit_hash doesn't match => we need the etag
+ (url_to_download, etag, commit_hash, expected_size, head_call_error) = _get_metadata_or_catch_error(
+ repo_id=repo_id,
+ filename=filename,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=endpoint,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ headers=headers,
+ token=token,
+ local_files_only=local_files_only,
+ )
+
+ if head_call_error is not None:
+ # No HEAD call but local file exists => default to local file
+ if not force_download and paths.file_path.is_file():
+ logger.warning(
+ f"Couldn't access the Hub to check for update but local file already exists. Defaulting to existing file. (error: {head_call_error})"
+ )
+ return str(paths.file_path)
+ # Otherwise => raise
+ _raise_on_head_call_error(head_call_error, force_download, local_files_only)
+
+ # From now on, etag, commit_hash, url and size are not None.
+ assert etag is not None, "etag must have been retrieved from server"
+ assert commit_hash is not None, "commit_hash must have been retrieved from server"
+ assert url_to_download is not None, "file location must have been retrieved from server"
+ assert expected_size is not None, "expected_size must have been retrieved from server"
+
+ # Local file exists => check if it's up-to-date
+ if not force_download and paths.file_path.is_file():
+ # etag matches => update metadata and return file
+ if local_metadata is not None and local_metadata.etag == etag:
+ write_download_metadata(local_dir=local_dir, filename=filename, commit_hash=commit_hash, etag=etag)
+ return str(paths.file_path)
+
+ # metadata is outdated + etag is a sha256
+ # => means it's an LFS file (large)
+ # => let's compute local hash and compare
+ # => if match, update metadata and return file
+ if local_metadata is None and REGEX_SHA256.match(etag) is not None:
+ with open(paths.file_path, "rb") as f:
+ file_hash = sha_fileobj(f).hex()
+ if file_hash == etag:
+ write_download_metadata(local_dir=local_dir, filename=filename, commit_hash=commit_hash, etag=etag)
+ return str(paths.file_path)
+
+ # Local file doesn't exist or etag isn't a match => retrieve file from remote (or cache)
+
+ # If we are lucky enough, the file is already in the cache => copy it
+ if not force_download:
+ cached_path = try_to_load_from_cache(
+ repo_id=repo_id,
+ filename=filename,
+ cache_dir=cache_dir,
+ revision=commit_hash,
+ repo_type=repo_type,
+ )
+ if isinstance(cached_path, str):
+ with WeakFileLock(paths.lock_path):
+ paths.file_path.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copyfile(cached_path, paths.file_path)
+ write_download_metadata(local_dir=local_dir, filename=filename, commit_hash=commit_hash, etag=etag)
+ return str(paths.file_path)
+
+ # Otherwise, let's download the file!
+ with WeakFileLock(paths.lock_path):
+ paths.file_path.unlink(missing_ok=True) # delete outdated file first
+ _download_to_tmp_and_move(
+ incomplete_path=paths.incomplete_path(etag),
+ destination_path=paths.file_path,
+ url_to_download=url_to_download,
+ proxies=proxies,
+ headers=headers,
+ expected_size=expected_size,
+ filename=filename,
+ force_download=force_download,
+ )
+
+ write_download_metadata(local_dir=local_dir, filename=filename, commit_hash=commit_hash, etag=etag)
+ return str(paths.file_path)
+
+
+@validate_hf_hub_args
+def try_to_load_from_cache(
+ repo_id: str,
+ filename: str,
+ cache_dir: Union[str, Path, None] = None,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+) -> Union[str, _CACHED_NO_EXIST_T, None]:
+ """
+ Explores the cache to return the latest cached file for a given revision if found.
+
+ This function will not raise any exception if the file in not cached.
+
+ Args:
+ cache_dir (`str` or `os.PathLike`):
+ The folder where the cached files lie.
+ repo_id (`str`):
+ The ID of the repo on huggingface.co.
+ filename (`str`):
+ The filename to look for inside `repo_id`.
+ revision (`str`, *optional*):
+ The specific model version to use. Will default to `"main"` if it's not provided and no `commit_hash` is
+ provided either.
+ repo_type (`str`, *optional*):
+ The type of the repository. Will default to `"model"`.
+
+ Returns:
+ `Optional[str]` or `_CACHED_NO_EXIST`:
+ Will return `None` if the file was not cached. Otherwise:
+ - The exact path to the cached file if it's found in the cache
+ - A special value `_CACHED_NO_EXIST` if the file does not exist at the given commit hash and this fact was
+ cached.
+
+ Example:
+
+ ```python
+ from huggingface_hub import try_to_load_from_cache, _CACHED_NO_EXIST
+
+ filepath = try_to_load_from_cache()
+ if isinstance(filepath, str):
+ # file exists and is cached
+ ...
+ elif filepath is _CACHED_NO_EXIST:
+ # non-existence of file is cached
+ ...
+ else:
+ # file is not cached
+ ...
+ ```
+ """
+ if revision is None:
+ revision = "main"
+ if repo_type is None:
+ repo_type = "model"
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type: {repo_type}. Accepted repo types are: {str(constants.REPO_TYPES)}")
+ if cache_dir is None:
+ cache_dir = constants.HF_HUB_CACHE
+
+ object_id = repo_id.replace("/", "--")
+ repo_cache = os.path.join(cache_dir, f"{repo_type}s--{object_id}")
+ if not os.path.isdir(repo_cache):
+ # No cache for this model
+ return None
+
+ refs_dir = os.path.join(repo_cache, "refs")
+ snapshots_dir = os.path.join(repo_cache, "snapshots")
+ no_exist_dir = os.path.join(repo_cache, ".no_exist")
+
+ # Resolve refs (for instance to convert main to the associated commit sha)
+ if os.path.isdir(refs_dir):
+ revision_file = os.path.join(refs_dir, revision)
+ if os.path.isfile(revision_file):
+ with open(revision_file) as f:
+ revision = f.read()
+
+ # Check if file is cached as "no_exist"
+ if os.path.isfile(os.path.join(no_exist_dir, revision, filename)):
+ return _CACHED_NO_EXIST
+
+ # Check if revision folder exists
+ if not os.path.exists(snapshots_dir):
+ return None
+ cached_shas = os.listdir(snapshots_dir)
+ if revision not in cached_shas:
+ # No cache for this revision and we won't try to return a random revision
+ return None
+
+ # Check if file exists in cache
+ cached_file = os.path.join(snapshots_dir, revision, filename)
+ return cached_file if os.path.isfile(cached_file) else None
+
+
+@validate_hf_hub_args
+def get_hf_file_metadata(
+ url: str,
+ token: Union[bool, str, None] = None,
+ proxies: Optional[Dict] = None,
+ timeout: Optional[float] = constants.DEFAULT_REQUEST_TIMEOUT,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ headers: Optional[Dict[str, str]] = None,
+) -> HfFileMetadata:
+ """Fetch metadata of a file versioned on the Hub for a given url.
+
+ Args:
+ url (`str`):
+ File url, for example returned by [`hf_hub_url`].
+ token (`str` or `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If `False` or `None`, no token is provided.
+ - If a string, it's used as the authentication token.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ timeout (`float`, *optional*, defaults to 10):
+ How many seconds to wait for the server to send metadata before giving up.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ user_agent (`dict`, `str`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ headers (`dict`, *optional*):
+ Additional headers to be sent with the request.
+
+ Returns:
+ A [`HfFileMetadata`] object containing metadata such as location, etag, size and
+ commit_hash.
+ """
+ hf_headers = build_hf_headers(
+ token=token,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ headers=headers,
+ )
+ hf_headers["Accept-Encoding"] = "identity" # prevent any compression => we want to know the real size of the file
+
+ # Retrieve metadata
+ r = _request_wrapper(
+ method="HEAD",
+ url=url,
+ headers=hf_headers,
+ allow_redirects=False,
+ follow_relative_redirects=True,
+ proxies=proxies,
+ timeout=timeout,
+ )
+ hf_raise_for_status(r)
+
+ # Return
+ return HfFileMetadata(
+ commit_hash=r.headers.get(constants.HUGGINGFACE_HEADER_X_REPO_COMMIT),
+ # We favor a custom header indicating the etag of the linked resource, and
+ # we fallback to the regular etag header.
+ etag=_normalize_etag(r.headers.get(constants.HUGGINGFACE_HEADER_X_LINKED_ETAG) or r.headers.get("ETag")),
+ # Either from response headers (if redirected) or defaults to request url
+ # Do not use directly `url`, as `_request_wrapper` might have followed relative
+ # redirects.
+ location=r.headers.get("Location") or r.request.url, # type: ignore
+ size=_int_or_none(
+ r.headers.get(constants.HUGGINGFACE_HEADER_X_LINKED_SIZE) or r.headers.get("Content-Length")
+ ),
+ )
+
+
+def _get_metadata_or_catch_error(
+ *,
+ repo_id: str,
+ filename: str,
+ repo_type: str,
+ revision: str,
+ endpoint: Optional[str],
+ proxies: Optional[Dict],
+ etag_timeout: Optional[float],
+ headers: Dict[str, str], # mutated inplace!
+ token: Union[bool, str, None],
+ local_files_only: bool,
+ relative_filename: Optional[str] = None, # only used to store `.no_exists` in cache
+ storage_folder: Optional[str] = None, # only used to store `.no_exists` in cache
+) -> Union[
+ # Either an exception is caught and returned
+ Tuple[None, None, None, None, Exception],
+ # Or the metadata is returned as
+ # `(url_to_download, etag, commit_hash, expected_size, None)`
+ Tuple[str, str, str, int, None],
+]:
+ """Get metadata for a file on the Hub, safely handling network issues.
+
+ Returns either the etag, commit_hash and expected size of the file, or the error
+ raised while fetching the metadata.
+
+ NOTE: This function mutates `headers` inplace! It removes the `authorization` header
+ if the file is a LFS blob and the domain of the url is different from the
+ domain of the location (typically an S3 bucket).
+ """
+ if local_files_only:
+ return (
+ None,
+ None,
+ None,
+ None,
+ OfflineModeIsEnabled(
+ f"Cannot access file since 'local_files_only=True' as been set. (repo_id: {repo_id}, repo_type: {repo_type}, revision: {revision}, filename: {filename})"
+ ),
+ )
+
+ url = hf_hub_url(repo_id, filename, repo_type=repo_type, revision=revision, endpoint=endpoint)
+ url_to_download: str = url
+ etag: Optional[str] = None
+ commit_hash: Optional[str] = None
+ expected_size: Optional[int] = None
+ head_error_call: Optional[Exception] = None
+
+ # Try to get metadata from the server.
+ # Do not raise yet if the file is not found or not accessible.
+ if not local_files_only:
+ try:
+ try:
+ metadata = get_hf_file_metadata(
+ url=url, proxies=proxies, timeout=etag_timeout, headers=headers, token=token
+ )
+ except EntryNotFoundError as http_error:
+ if storage_folder is not None and relative_filename is not None:
+ # Cache the non-existence of the file
+ commit_hash = http_error.response.headers.get(constants.HUGGINGFACE_HEADER_X_REPO_COMMIT)
+ if commit_hash is not None:
+ no_exist_file_path = Path(storage_folder) / ".no_exist" / commit_hash / relative_filename
+ try:
+ no_exist_file_path.parent.mkdir(parents=True, exist_ok=True)
+ no_exist_file_path.touch()
+ except OSError as e:
+ logger.error(
+ f"Could not cache non-existence of file. Will ignore error and continue. Error: {e}"
+ )
+ _cache_commit_hash_for_specific_revision(storage_folder, revision, commit_hash)
+ raise
+
+ # Commit hash must exist
+ commit_hash = metadata.commit_hash
+ if commit_hash is None:
+ raise FileMetadataError(
+ "Distant resource does not seem to be on huggingface.co. It is possible that a configuration issue"
+ " prevents you from downloading resources from https://huggingface.co. Please check your firewall"
+ " and proxy settings and make sure your SSL certificates are updated."
+ )
+
+ # Etag must exist
+ # If we don't have any of those, raise an error.
+ etag = metadata.etag
+ if etag is None:
+ raise FileMetadataError(
+ "Distant resource does not have an ETag, we won't be able to reliably ensure reproducibility."
+ )
+
+ # Size must exist
+ expected_size = metadata.size
+ if expected_size is None:
+ raise FileMetadataError("Distant resource does not have a Content-Length.")
+
+ # In case of a redirect, save an extra redirect on the request.get call,
+ # and ensure we download the exact atomic version even if it changed
+ # between the HEAD and the GET (unlikely, but hey).
+ #
+ # If url domain is different => we are downloading from a CDN => url is signed => don't send auth
+ # If url domain is the same => redirect due to repo rename AND downloading a regular file => keep auth
+ if url != metadata.location:
+ url_to_download = metadata.location
+ if urlparse(url).netloc != urlparse(metadata.location).netloc:
+ # Remove authorization header when downloading a LFS blob
+ headers.pop("authorization", None)
+ except (requests.exceptions.SSLError, requests.exceptions.ProxyError):
+ # Actually raise for those subclasses of ConnectionError
+ raise
+ except (
+ requests.exceptions.ConnectionError,
+ requests.exceptions.Timeout,
+ OfflineModeIsEnabled,
+ ) as error:
+ # Otherwise, our Internet connection is down.
+ # etag is None
+ head_error_call = error
+ except (RevisionNotFoundError, EntryNotFoundError):
+ # The repo was found but the revision or entry doesn't exist on the Hub (never existed or got deleted)
+ raise
+ except requests.HTTPError as error:
+ # Multiple reasons for an http error:
+ # - Repository is private and invalid/missing token sent
+ # - Repository is gated and invalid/missing token sent
+ # - Hub is down (error 500 or 504)
+ # => let's switch to 'local_files_only=True' to check if the files are already cached.
+ # (if it's not the case, the error will be re-raised)
+ head_error_call = error
+ except FileMetadataError as error:
+ # Multiple reasons for a FileMetadataError:
+ # - Wrong network configuration (proxy, firewall, SSL certificates)
+ # - Inconsistency on the Hub
+ # => let's switch to 'local_files_only=True' to check if the files are already cached.
+ # (if it's not the case, the error will be re-raised)
+ head_error_call = error
+
+ if not (local_files_only or etag is not None or head_error_call is not None):
+ raise RuntimeError("etag is empty due to uncovered problems")
+
+ return (url_to_download, etag, commit_hash, expected_size, head_error_call) # type: ignore [return-value]
+
+
+def _raise_on_head_call_error(head_call_error: Exception, force_download: bool, local_files_only: bool) -> NoReturn:
+ """Raise an appropriate error when the HEAD call failed and we cannot locate a local file."""
+ # No head call => we cannot force download.
+ if force_download:
+ if local_files_only:
+ raise ValueError("Cannot pass 'force_download=True' and 'local_files_only=True' at the same time.")
+ elif isinstance(head_call_error, OfflineModeIsEnabled):
+ raise ValueError("Cannot pass 'force_download=True' when offline mode is enabled.") from head_call_error
+ else:
+ raise ValueError("Force download failed due to the above error.") from head_call_error
+
+ # No head call + couldn't find an appropriate file on disk => raise an error.
+ if local_files_only:
+ raise LocalEntryNotFoundError(
+ "Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable"
+ " hf.co look-ups and downloads online, set 'local_files_only' to False."
+ )
+ elif isinstance(head_call_error, (RepositoryNotFoundError, GatedRepoError)) or (
+ isinstance(head_call_error, HfHubHTTPError) and head_call_error.response.status_code == 401
+ ):
+ # Repo not found or gated => let's raise the actual error
+ # Unauthorized => likely a token issue => let's raise the actual error
+ raise head_call_error
+ else:
+ # Otherwise: most likely a connection issue or Hub downtime => let's warn the user
+ raise LocalEntryNotFoundError(
+ "An error happened while trying to locate the file on the Hub and we cannot find the requested files"
+ " in the local cache. Please check your connection and try again or make sure your Internet connection"
+ " is on."
+ ) from head_call_error
+
+
+def _download_to_tmp_and_move(
+ incomplete_path: Path,
+ destination_path: Path,
+ url_to_download: str,
+ proxies: Optional[Dict],
+ headers: Dict[str, str],
+ expected_size: Optional[int],
+ filename: str,
+ force_download: bool,
+) -> None:
+ """Download content from a URL to a destination path.
+
+ Internal logic:
+ - return early if file is already downloaded
+ - resume download if possible (from incomplete file)
+ - do not resume download if `force_download=True` or `HF_HUB_ENABLE_HF_TRANSFER=True`
+ - check disk space before downloading
+ - download content to a temporary file
+ - set correct permissions on temporary file
+ - move the temporary file to the destination path
+
+ Both `incomplete_path` and `destination_path` must be on the same volume to avoid a local copy.
+ """
+ if destination_path.exists() and not force_download:
+ # Do nothing if already exists (except if force_download=True)
+ return
+
+ if incomplete_path.exists() and (force_download or (constants.HF_HUB_ENABLE_HF_TRANSFER and not proxies)):
+ # By default, we will try to resume the download if possible.
+ # However, if the user has set `force_download=True` or if `hf_transfer` is enabled, then we should
+ # not resume the download => delete the incomplete file.
+ message = f"Removing incomplete file '{incomplete_path}'"
+ if force_download:
+ message += " (force_download=True)"
+ elif constants.HF_HUB_ENABLE_HF_TRANSFER and not proxies:
+ message += " (hf_transfer=True)"
+ logger.info(message)
+ incomplete_path.unlink(missing_ok=True)
+
+ with incomplete_path.open("ab") as f:
+ resume_size = f.tell()
+ message = f"Downloading '{filename}' to '{incomplete_path}'"
+ if resume_size > 0 and expected_size is not None:
+ message += f" (resume from {resume_size}/{expected_size})"
+ logger.info(message)
+
+ if expected_size is not None: # might be None if HTTP header not set correctly
+ # Check disk space in both tmp and destination path
+ _check_disk_space(expected_size, incomplete_path.parent)
+ _check_disk_space(expected_size, destination_path.parent)
+
+ http_get(
+ url_to_download,
+ f,
+ proxies=proxies,
+ resume_size=resume_size,
+ headers=headers,
+ expected_size=expected_size,
+ )
+
+ logger.info(f"Download complete. Moving file to {destination_path}")
+ _chmod_and_move(incomplete_path, destination_path)
+
+
+def _int_or_none(value: Optional[str]) -> Optional[int]:
+ try:
+ return int(value) # type: ignore
+ except (TypeError, ValueError):
+ return None
+
+
+def _chmod_and_move(src: Path, dst: Path) -> None:
+ """Set correct permission before moving a blob from tmp directory to cache dir.
+
+ Do not take into account the `umask` from the process as there is no convenient way
+ to get it that is thread-safe.
+
+ See:
+ - About umask: https://docs.python.org/3/library/os.html#os.umask
+ - Thread-safety: https://stackoverflow.com/a/70343066
+ - About solution: https://github.com/huggingface/huggingface_hub/pull/1220#issuecomment-1326211591
+ - Fix issue: https://github.com/huggingface/huggingface_hub/issues/1141
+ - Fix issue: https://github.com/huggingface/huggingface_hub/issues/1215
+ """
+ # Get umask by creating a temporary file in the cached repo folder.
+ tmp_file = dst.parent.parent / f"tmp_{uuid.uuid4()}"
+ try:
+ tmp_file.touch()
+ cache_dir_mode = Path(tmp_file).stat().st_mode
+ os.chmod(str(src), stat.S_IMODE(cache_dir_mode))
+ except OSError as e:
+ logger.warning(
+ f"Could not set the permissions on the file '{src}'. Error: {e}.\nContinuing without setting permissions."
+ )
+ finally:
+ try:
+ tmp_file.unlink()
+ except OSError:
+ # fails if `tmp_file.touch()` failed => do nothing
+ # See https://github.com/huggingface/huggingface_hub/issues/2359
+ pass
+
+ shutil.move(str(src), str(dst), copy_function=_copy_no_matter_what)
+
+
+def _copy_no_matter_what(src: str, dst: str) -> None:
+ """Copy file from src to dst.
+
+ If `shutil.copy2` fails, fallback to `shutil.copyfile`.
+ """
+ try:
+ # Copy file with metadata and permission
+ # Can fail e.g. if dst is an S3 mount
+ shutil.copy2(src, dst)
+ except OSError:
+ # Copy only file content
+ shutil.copyfile(src, dst)
+
+
+def _get_pointer_path(storage_folder: str, revision: str, relative_filename: str) -> str:
+ # Using `os.path.abspath` instead of `Path.resolve()` to avoid resolving symlinks
+ snapshot_path = os.path.join(storage_folder, "snapshots")
+ pointer_path = os.path.join(snapshot_path, revision, relative_filename)
+ if Path(os.path.abspath(snapshot_path)) not in Path(os.path.abspath(pointer_path)).parents:
+ raise ValueError(
+ "Invalid pointer path: cannot create pointer path in snapshot folder if"
+ f" `storage_folder='{storage_folder}'`, `revision='{revision}'` and"
+ f" `relative_filename='{relative_filename}'`."
+ )
+ return pointer_path
diff --git a/env/Lib/site-packages/huggingface_hub/hf_api.py b/env/Lib/site-packages/huggingface_hub/hf_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..49aa816110ada2e42d39785bb208486e569b4d17
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/hf_api.py
@@ -0,0 +1,9645 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import inspect
+import json
+import re
+import struct
+import warnings
+from collections import defaultdict
+from concurrent.futures import Future, ThreadPoolExecutor
+from dataclasses import asdict, dataclass, field
+from datetime import datetime
+from functools import wraps
+from itertools import islice
+from pathlib import Path
+from typing import (
+ Any,
+ BinaryIO,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Literal,
+ Optional,
+ Tuple,
+ TypeVar,
+ Union,
+ overload,
+)
+from urllib.parse import quote
+
+import requests
+from requests.exceptions import HTTPError
+from tqdm.auto import tqdm as base_tqdm
+from tqdm.contrib.concurrent import thread_map
+
+from . import constants
+from ._commit_api import (
+ CommitOperation,
+ CommitOperationAdd,
+ CommitOperationCopy,
+ CommitOperationDelete,
+ _fetch_files_to_copy,
+ _fetch_upload_modes,
+ _prepare_commit_payload,
+ _upload_lfs_files,
+ _warn_on_overwriting_operations,
+)
+from ._inference_endpoints import InferenceEndpoint, InferenceEndpointType
+from ._space_api import SpaceHardware, SpaceRuntime, SpaceStorage, SpaceVariable
+from ._upload_large_folder import upload_large_folder_internal
+from .community import (
+ Discussion,
+ DiscussionComment,
+ DiscussionStatusChange,
+ DiscussionTitleChange,
+ DiscussionWithDetails,
+ deserialize_event,
+)
+from .constants import (
+ DEFAULT_ETAG_TIMEOUT, # noqa: F401 # kept for backward compatibility
+ DEFAULT_REQUEST_TIMEOUT, # noqa: F401 # kept for backward compatibility
+ DEFAULT_REVISION, # noqa: F401 # kept for backward compatibility
+ DISCUSSION_STATUS, # noqa: F401 # kept for backward compatibility
+ DISCUSSION_TYPES, # noqa: F401 # kept for backward compatibility
+ ENDPOINT, # noqa: F401 # kept for backward compatibility
+ INFERENCE_ENDPOINTS_ENDPOINT, # noqa: F401 # kept for backward compatibility
+ REGEX_COMMIT_OID, # noqa: F401 # kept for backward compatibility
+ REPO_TYPE_MODEL, # noqa: F401 # kept for backward compatibility
+ REPO_TYPES, # noqa: F401 # kept for backward compatibility
+ REPO_TYPES_MAPPING, # noqa: F401 # kept for backward compatibility
+ REPO_TYPES_URL_PREFIXES, # noqa: F401 # kept for backward compatibility
+ SAFETENSORS_INDEX_FILE, # noqa: F401 # kept for backward compatibility
+ SAFETENSORS_MAX_HEADER_LENGTH, # noqa: F401 # kept for backward compatibility
+ SAFETENSORS_SINGLE_FILE, # noqa: F401 # kept for backward compatibility
+ SPACES_SDK_TYPES, # noqa: F401 # kept for backward compatibility
+ WEBHOOK_DOMAIN_T, # noqa: F401 # kept for backward compatibility
+ DiscussionStatusFilter, # noqa: F401 # kept for backward compatibility
+ DiscussionTypeFilter, # noqa: F401 # kept for backward compatibility
+)
+from .errors import (
+ BadRequestError,
+ EntryNotFoundError,
+ GatedRepoError,
+ HfHubHTTPError,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+)
+from .file_download import HfFileMetadata, get_hf_file_metadata, hf_hub_url
+from .repocard_data import DatasetCardData, ModelCardData, SpaceCardData
+from .utils import (
+ DEFAULT_IGNORE_PATTERNS,
+ HfFolder, # noqa: F401 # kept for backward compatibility
+ LocalTokenNotFoundError,
+ NotASafetensorsRepoError,
+ SafetensorsFileMetadata,
+ SafetensorsParsingError,
+ SafetensorsRepoMetadata,
+ TensorInfo,
+ build_hf_headers,
+ filter_repo_objects,
+ fix_hf_endpoint_in_url,
+ get_session,
+ get_token,
+ hf_raise_for_status,
+ logging,
+ paginate,
+ parse_datetime,
+ validate_hf_hub_args,
+)
+from .utils import tqdm as hf_tqdm
+from .utils._auth import _get_token_from_environment, _get_token_from_file, _get_token_from_google_colab
+from .utils._deprecation import _deprecate_method
+from .utils._typing import CallableT
+from .utils.endpoint_helpers import _is_emission_within_threshold
+
+
+R = TypeVar("R") # Return type
+CollectionItemType_T = Literal["model", "dataset", "space", "paper"]
+
+ExpandModelProperty_T = Literal[
+ "author",
+ "baseModels",
+ "cardData",
+ "childrenModelCount",
+ "config",
+ "createdAt",
+ "disabled",
+ "downloads",
+ "downloadsAllTime",
+ "gated",
+ "gguf",
+ "inference",
+ "inferenceProviderMapping",
+ "lastModified",
+ "library_name",
+ "likes",
+ "mask_token",
+ "model-index",
+ "pipeline_tag",
+ "private",
+ "resourceGroup",
+ "safetensors",
+ "sha",
+ "siblings",
+ "spaces",
+ "tags",
+ "transformersInfo",
+ "trendingScore",
+ "usedStorage",
+ "widgetData",
+]
+
+ExpandDatasetProperty_T = Literal[
+ "author",
+ "cardData",
+ "citation",
+ "createdAt",
+ "description",
+ "disabled",
+ "downloads",
+ "downloadsAllTime",
+ "gated",
+ "lastModified",
+ "likes",
+ "paperswithcode_id",
+ "private",
+ "resourceGroup",
+ "sha",
+ "siblings",
+ "tags",
+ "trendingScore",
+ "usedStorage",
+]
+
+ExpandSpaceProperty_T = Literal[
+ "author",
+ "cardData",
+ "createdAt",
+ "datasets",
+ "disabled",
+ "lastModified",
+ "likes",
+ "models",
+ "private",
+ "resourceGroup",
+ "runtime",
+ "sdk",
+ "sha",
+ "siblings",
+ "subdomain",
+ "tags",
+ "trendingScore",
+ "usedStorage",
+]
+
+USERNAME_PLACEHOLDER = "hf_user"
+_REGEX_DISCUSSION_URL = re.compile(r".*/discussions/(\d+)$")
+
+_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE = (
+ "\nNote: Creating a commit assumes that the repo already exists on the"
+ " Huggingface Hub. Please use `create_repo` if it's not the case."
+)
+_AUTH_CHECK_NO_REPO_ERROR_MESSAGE = (
+ "\nNote: The repository either does not exist or you do not have access rights."
+ " Please check the repository ID and your access permissions."
+ " If this is a private repository, ensure that your token is correct."
+)
+logger = logging.get_logger(__name__)
+
+
+def repo_type_and_id_from_hf_id(hf_id: str, hub_url: Optional[str] = None) -> Tuple[Optional[str], Optional[str], str]:
+ """
+ Returns the repo type and ID from a huggingface.co URL linking to a
+ repository
+
+ Args:
+ hf_id (`str`):
+ An URL or ID of a repository on the HF hub. Accepted values are:
+
+ - https://huggingface.co///
+ - https://huggingface.co//
+ - hf:////
+ - hf:///
+ - //
+ - /
+ -
+ hub_url (`str`, *optional*):
+ The URL of the HuggingFace Hub, defaults to https://huggingface.co
+
+ Returns:
+ A tuple with three items: repo_type (`str` or `None`), namespace (`str` or
+ `None`) and repo_id (`str`).
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If URL cannot be parsed.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `repo_type` is unknown.
+ """
+ input_hf_id = hf_id
+
+ hub_url = re.sub(r"https?://", "", hub_url if hub_url is not None else constants.ENDPOINT)
+ is_hf_url = hub_url in hf_id and "@" not in hf_id
+
+ HFFS_PREFIX = "hf://"
+ if hf_id.startswith(HFFS_PREFIX): # Remove "hf://" prefix if exists
+ hf_id = hf_id[len(HFFS_PREFIX) :]
+
+ url_segments = hf_id.split("/")
+ is_hf_id = len(url_segments) <= 3
+
+ namespace: Optional[str]
+ if is_hf_url:
+ namespace, repo_id = url_segments[-2:]
+ if namespace == hub_url:
+ namespace = None
+ if len(url_segments) > 2 and hub_url not in url_segments[-3]:
+ repo_type = url_segments[-3]
+ elif namespace in constants.REPO_TYPES_MAPPING:
+ # Mean canonical dataset or model
+ repo_type = constants.REPO_TYPES_MAPPING[namespace]
+ namespace = None
+ else:
+ repo_type = None
+ elif is_hf_id:
+ if len(url_segments) == 3:
+ # Passed // or //
+ repo_type, namespace, repo_id = url_segments[-3:]
+ elif len(url_segments) == 2:
+ if url_segments[0] in constants.REPO_TYPES_MAPPING:
+ # Passed '' or 'datasets/' for a canonical model or dataset
+ repo_type = constants.REPO_TYPES_MAPPING[url_segments[0]]
+ namespace = None
+ repo_id = hf_id.split("/")[-1]
+ else:
+ # Passed / or /
+ namespace, repo_id = hf_id.split("/")[-2:]
+ repo_type = None
+ else:
+ # Passed
+ repo_id = url_segments[0]
+ namespace, repo_type = None, None
+ else:
+ raise ValueError(f"Unable to retrieve user and repo ID from the passed HF ID: {hf_id}")
+
+ # Check if repo type is known (mapping "spaces" => "space" + empty value => `None`)
+ if repo_type in constants.REPO_TYPES_MAPPING:
+ repo_type = constants.REPO_TYPES_MAPPING[repo_type]
+ if repo_type == "":
+ repo_type = None
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Unknown `repo_type`: '{repo_type}' ('{input_hf_id}')")
+
+ return repo_type, namespace, repo_id
+
+
+@dataclass
+class LastCommitInfo(dict):
+ oid: str
+ title: str
+ date: datetime
+
+ def __post_init__(self): # hack to make LastCommitInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class BlobLfsInfo(dict):
+ size: int
+ sha256: str
+ pointer_size: int
+
+ def __post_init__(self): # hack to make BlobLfsInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class BlobSecurityInfo(dict):
+ safe: bool # duplicate information with "status" field, keeping it for backward compatibility
+ status: str
+ av_scan: Optional[Dict]
+ pickle_import_scan: Optional[Dict]
+
+ def __post_init__(self): # hack to make BlogSecurityInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class TransformersInfo(dict):
+ auto_model: str
+ custom_class: Optional[str] = None
+ # possible `pipeline_tag` values: https://github.com/huggingface/huggingface.js/blob/3ee32554b8620644a6287e786b2a83bf5caf559c/packages/tasks/src/pipelines.ts#L72
+ pipeline_tag: Optional[str] = None
+ processor: Optional[str] = None
+
+ def __post_init__(self): # hack to make TransformersInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class SafeTensorsInfo(dict):
+ parameters: Dict[str, int]
+ total: int
+
+ def __post_init__(self): # hack to make SafeTensorsInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class CommitInfo(str):
+ """Data structure containing information about a newly created commit.
+
+ Returned by any method that creates a commit on the Hub: [`create_commit`], [`upload_file`], [`upload_folder`],
+ [`delete_file`], [`delete_folder`]. It inherits from `str` for backward compatibility but using methods specific
+ to `str` is deprecated.
+
+ Attributes:
+ commit_url (`str`):
+ Url where to find the commit.
+
+ commit_message (`str`):
+ The summary (first line) of the commit that has been created.
+
+ commit_description (`str`):
+ Description of the commit that has been created. Can be empty.
+
+ oid (`str`):
+ Commit hash id. Example: `"91c54ad1727ee830252e457677f467be0bfd8a57"`.
+
+ pr_url (`str`, *optional*):
+ Url to the PR that has been created, if any. Populated when `create_pr=True`
+ is passed.
+
+ pr_revision (`str`, *optional*):
+ Revision of the PR that has been created, if any. Populated when
+ `create_pr=True` is passed. Example: `"refs/pr/1"`.
+
+ pr_num (`int`, *optional*):
+ Number of the PR discussion that has been created, if any. Populated when
+ `create_pr=True` is passed. Can be passed as `discussion_num` in
+ [`get_discussion_details`]. Example: `1`.
+
+ repo_url (`RepoUrl`):
+ Repo URL of the commit containing info like repo_id, repo_type, etc.
+
+ _url (`str`, *optional*):
+ Legacy url for `str` compatibility. Can be the url to the uploaded file on the Hub (if returned by
+ [`upload_file`]), to the uploaded folder on the Hub (if returned by [`upload_folder`]) or to the commit on
+ the Hub (if returned by [`create_commit`]). Defaults to `commit_url`. It is deprecated to use this
+ attribute. Please use `commit_url` instead.
+ """
+
+ commit_url: str
+ commit_message: str
+ commit_description: str
+ oid: str
+ pr_url: Optional[str] = None
+
+ # Computed from `commit_url` in `__post_init__`
+ repo_url: RepoUrl = field(init=False)
+
+ # Computed from `pr_url` in `__post_init__`
+ pr_revision: Optional[str] = field(init=False)
+ pr_num: Optional[str] = field(init=False)
+
+ # legacy url for `str` compatibility (ex: url to uploaded file, url to uploaded folder, url to PR, etc.)
+ _url: str = field(repr=False, default=None) # type: ignore # defaults to `commit_url`
+
+ def __new__(cls, *args, commit_url: str, _url: Optional[str] = None, **kwargs):
+ return str.__new__(cls, _url or commit_url)
+
+ def __post_init__(self):
+ """Populate pr-related fields after initialization.
+
+ See https://docs.python.org/3.10/library/dataclasses.html#post-init-processing.
+ """
+ # Repo info
+ self.repo_url = RepoUrl(self.commit_url.split("/commit/")[0])
+
+ # PR info
+ if self.pr_url is not None:
+ self.pr_revision = _parse_revision_from_pr_url(self.pr_url)
+ self.pr_num = int(self.pr_revision.split("/")[-1])
+ else:
+ self.pr_revision = None
+ self.pr_num = None
+
+
+@dataclass
+class AccessRequest:
+ """Data structure containing information about a user access request.
+
+ Attributes:
+ username (`str`):
+ Username of the user who requested access.
+ fullname (`str`):
+ Fullname of the user who requested access.
+ email (`Optional[str]`):
+ Email of the user who requested access.
+ Can only be `None` in the /accepted list if the user was granted access manually.
+ timestamp (`datetime`):
+ Timestamp of the request.
+ status (`Literal["pending", "accepted", "rejected"]`):
+ Status of the request. Can be one of `["pending", "accepted", "rejected"]`.
+ fields (`Dict[str, Any]`, *optional*):
+ Additional fields filled by the user in the gate form.
+ """
+
+ username: str
+ fullname: str
+ email: Optional[str]
+ timestamp: datetime
+ status: Literal["pending", "accepted", "rejected"]
+
+ # Additional fields filled by the user in the gate form
+ fields: Optional[Dict[str, Any]] = None
+
+
+@dataclass
+class WebhookWatchedItem:
+ """Data structure containing information about the items watched by a webhook.
+
+ Attributes:
+ type (`Literal["dataset", "model", "org", "space", "user"]`):
+ Type of the item to be watched. Can be one of `["dataset", "model", "org", "space", "user"]`.
+ name (`str`):
+ Name of the item to be watched. Can be the username, organization name, model name, dataset name or space name.
+ """
+
+ type: Literal["dataset", "model", "org", "space", "user"]
+ name: str
+
+
+@dataclass
+class WebhookInfo:
+ """Data structure containing information about a webhook.
+
+ Attributes:
+ id (`str`):
+ ID of the webhook.
+ url (`str`):
+ URL of the webhook.
+ watched (`List[WebhookWatchedItem]`):
+ List of items watched by the webhook, see [`WebhookWatchedItem`].
+ domains (`List[WEBHOOK_DOMAIN_T]`):
+ List of domains the webhook is watching. Can be one of `["repo", "discussions"]`.
+ secret (`str`, *optional*):
+ Secret of the webhook.
+ disabled (`bool`):
+ Whether the webhook is disabled or not.
+ """
+
+ id: str
+ url: str
+ watched: List[WebhookWatchedItem]
+ domains: List[constants.WEBHOOK_DOMAIN_T]
+ secret: Optional[str]
+ disabled: bool
+
+
+class RepoUrl(str):
+ """Subclass of `str` describing a repo URL on the Hub.
+
+ `RepoUrl` is returned by `HfApi.create_repo`. It inherits from `str` for backward
+ compatibility. At initialization, the URL is parsed to populate properties:
+ - endpoint (`str`)
+ - namespace (`Optional[str]`)
+ - repo_name (`str`)
+ - repo_id (`str`)
+ - repo_type (`Literal["model", "dataset", "space"]`)
+ - url (`str`)
+
+ Args:
+ url (`Any`):
+ String value of the repo url.
+ endpoint (`str`, *optional*):
+ Endpoint of the Hub. Defaults to .
+
+ Example:
+ ```py
+ >>> RepoUrl('https://huggingface.co/gpt2')
+ RepoUrl('https://huggingface.co/gpt2', endpoint='https://huggingface.co', repo_type='model', repo_id='gpt2')
+
+ >>> RepoUrl('https://hub-ci.huggingface.co/datasets/dummy_user/dummy_dataset', endpoint='https://hub-ci.huggingface.co')
+ RepoUrl('https://hub-ci.huggingface.co/datasets/dummy_user/dummy_dataset', endpoint='https://hub-ci.huggingface.co', repo_type='dataset', repo_id='dummy_user/dummy_dataset')
+
+ >>> RepoUrl('hf://datasets/my-user/my-dataset')
+ RepoUrl('hf://datasets/my-user/my-dataset', endpoint='https://huggingface.co', repo_type='dataset', repo_id='user/dataset')
+
+ >>> HfApi.create_repo("dummy_model")
+ RepoUrl('https://huggingface.co/Wauplin/dummy_model', endpoint='https://huggingface.co', repo_type='model', repo_id='Wauplin/dummy_model')
+ ```
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If URL cannot be parsed.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `repo_type` is unknown.
+ """
+
+ def __new__(cls, url: Any, endpoint: Optional[str] = None):
+ url = fix_hf_endpoint_in_url(url, endpoint=endpoint)
+ return super(RepoUrl, cls).__new__(cls, url)
+
+ def __init__(self, url: Any, endpoint: Optional[str] = None) -> None:
+ super().__init__()
+ # Parse URL
+ self.endpoint = endpoint or constants.ENDPOINT
+ repo_type, namespace, repo_name = repo_type_and_id_from_hf_id(self, hub_url=self.endpoint)
+
+ # Populate fields
+ self.namespace = namespace
+ self.repo_name = repo_name
+ self.repo_id = repo_name if namespace is None else f"{namespace}/{repo_name}"
+ self.repo_type = repo_type or constants.REPO_TYPE_MODEL
+ self.url = str(self) # just in case it's needed
+
+ def __repr__(self) -> str:
+ return f"RepoUrl('{self}', endpoint='{self.endpoint}', repo_type='{self.repo_type}', repo_id='{self.repo_id}')"
+
+
+@dataclass
+class RepoSibling:
+ """
+ Contains basic information about a repo file inside a repo on the Hub.
+
+
+
+ All attributes of this class are optional except `rfilename`. This is because only the file names are returned when
+ listing repositories on the Hub (with [`list_models`], [`list_datasets`] or [`list_spaces`]). If you need more
+ information like file size, blob id or lfs details, you must request them specifically from one repo at a time
+ (using [`model_info`], [`dataset_info`] or [`space_info`]) as it adds more constraints on the backend server to
+ retrieve these.
+
+
+
+ Attributes:
+ rfilename (str):
+ file name, relative to the repo root.
+ size (`int`, *optional*):
+ The file's size, in bytes. This attribute is defined when `files_metadata` argument of [`repo_info`] is set
+ to `True`. It's `None` otherwise.
+ blob_id (`str`, *optional*):
+ The file's git OID. This attribute is defined when `files_metadata` argument of [`repo_info`] is set to
+ `True`. It's `None` otherwise.
+ lfs (`BlobLfsInfo`, *optional*):
+ The file's LFS metadata. This attribute is defined when`files_metadata` argument of [`repo_info`] is set to
+ `True` and the file is stored with Git LFS. It's `None` otherwise.
+ """
+
+ rfilename: str
+ size: Optional[int] = None
+ blob_id: Optional[str] = None
+ lfs: Optional[BlobLfsInfo] = None
+
+
+@dataclass
+class RepoFile:
+ """
+ Contains information about a file on the Hub.
+
+ Attributes:
+ path (str):
+ file path relative to the repo root.
+ size (`int`):
+ The file's size, in bytes.
+ blob_id (`str`):
+ The file's git OID.
+ lfs (`BlobLfsInfo`):
+ The file's LFS metadata.
+ last_commit (`LastCommitInfo`, *optional*):
+ The file's last commit metadata. Only defined if [`list_repo_tree`] and [`get_paths_info`]
+ are called with `expand=True`.
+ security (`BlobSecurityInfo`, *optional*):
+ The file's security scan metadata. Only defined if [`list_repo_tree`] and [`get_paths_info`]
+ are called with `expand=True`.
+ """
+
+ path: str
+ size: int
+ blob_id: str
+ lfs: Optional[BlobLfsInfo] = None
+ last_commit: Optional[LastCommitInfo] = None
+ security: Optional[BlobSecurityInfo] = None
+
+ def __init__(self, **kwargs):
+ self.path = kwargs.pop("path")
+ self.size = kwargs.pop("size")
+ self.blob_id = kwargs.pop("oid")
+ lfs = kwargs.pop("lfs", None)
+ if lfs is not None:
+ lfs = BlobLfsInfo(size=lfs["size"], sha256=lfs["oid"], pointer_size=lfs["pointerSize"])
+ self.lfs = lfs
+ last_commit = kwargs.pop("lastCommit", None) or kwargs.pop("last_commit", None)
+ if last_commit is not None:
+ last_commit = LastCommitInfo(
+ oid=last_commit["id"], title=last_commit["title"], date=parse_datetime(last_commit["date"])
+ )
+ self.last_commit = last_commit
+ security = kwargs.pop("securityFileStatus", None)
+ if security is not None:
+ safe = security["status"] == "safe"
+ security = BlobSecurityInfo(
+ safe=safe,
+ status=security["status"],
+ av_scan=security["avScan"],
+ pickle_import_scan=security["pickleImportScan"],
+ )
+ self.security = security
+
+ # backwards compatibility
+ self.rfilename = self.path
+ self.lastCommit = self.last_commit
+
+
+@dataclass
+class RepoFolder:
+ """
+ Contains information about a folder on the Hub.
+
+ Attributes:
+ path (str):
+ folder path relative to the repo root.
+ tree_id (`str`):
+ The folder's git OID.
+ last_commit (`LastCommitInfo`, *optional*):
+ The folder's last commit metadata. Only defined if [`list_repo_tree`] and [`get_paths_info`]
+ are called with `expand=True`.
+ """
+
+ path: str
+ tree_id: str
+ last_commit: Optional[LastCommitInfo] = None
+
+ def __init__(self, **kwargs):
+ self.path = kwargs.pop("path")
+ self.tree_id = kwargs.pop("oid")
+ last_commit = kwargs.pop("lastCommit", None) or kwargs.pop("last_commit", None)
+ if last_commit is not None:
+ last_commit = LastCommitInfo(
+ oid=last_commit["id"], title=last_commit["title"], date=parse_datetime(last_commit["date"])
+ )
+ self.last_commit = last_commit
+
+
+@dataclass
+class InferenceProviderMapping:
+ status: Literal["live", "staging"]
+ provider_id: str
+ task: str
+
+ def __init__(self, **kwargs):
+ self.status = kwargs.pop("status")
+ self.provider_id = kwargs.pop("providerId")
+ self.task = kwargs.pop("task")
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class ModelInfo:
+ """
+ Contains information about a model on the Hub.
+
+
+
+ Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made.
+ In general, the more specific the query, the more information is returned. On the contrary, when listing models
+ using [`list_models`] only a subset of the attributes are returned.
+
+
+
+ Attributes:
+ id (`str`):
+ ID of model.
+ author (`str`, *optional*):
+ Author of the model.
+ sha (`str`, *optional*):
+ Repo SHA at this particular revision.
+ created_at (`datetime`, *optional*):
+ Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`,
+ corresponding to the date when we began to store creation dates.
+ last_modified (`datetime`, *optional*):
+ Date of last commit to the repo.
+ private (`bool`):
+ Is the repo private.
+ disabled (`bool`, *optional*):
+ Is the repo disabled.
+ downloads (`int`):
+ Number of downloads of the model over the last 30 days.
+ downloads_all_time (`int`):
+ Cumulated number of downloads of the model since its creation.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ Is the repo gated.
+ If so, whether there is manual or automatic approval.
+ gguf (`Dict`, *optional*):
+ GGUF information of the model.
+ inference (`Literal["cold", "frozen", "warm"]`, *optional*):
+ Status of the model on the inference API.
+ Warm models are available for immediate use. Cold models will be loaded on first inference call.
+ Frozen models are not available in Inference API.
+ inference_provider_mapping (`Dict`, *optional*):
+ Model's inference provider mapping.
+ likes (`int`):
+ Number of likes of the model.
+ library_name (`str`, *optional*):
+ Library associated with the model.
+ tags (`List[str]`):
+ List of tags of the model. Compared to `card_data.tags`, contains extra tags computed by the Hub
+ (e.g. supported libraries, model's arXiv).
+ pipeline_tag (`str`, *optional*):
+ Pipeline tag associated with the model.
+ mask_token (`str`, *optional*):
+ Mask token used by the model.
+ widget_data (`Any`, *optional*):
+ Widget data associated with the model.
+ model_index (`Dict`, *optional*):
+ Model index for evaluation.
+ config (`Dict`, *optional*):
+ Model configuration.
+ transformers_info (`TransformersInfo`, *optional*):
+ Transformers-specific info (auto class, processor, etc.) associated with the model.
+ trending_score (`int`, *optional*):
+ Trending score of the model.
+ card_data (`ModelCardData`, *optional*):
+ Model Card Metadata as a [`huggingface_hub.repocard_data.ModelCardData`] object.
+ siblings (`List[RepoSibling]`):
+ List of [`huggingface_hub.hf_api.RepoSibling`] objects that constitute the model.
+ spaces (`List[str]`, *optional*):
+ List of spaces using the model.
+ safetensors (`SafeTensorsInfo`, *optional*):
+ Model's safetensors information.
+ security_repo_status (`Dict`, *optional*):
+ Model's security scan status.
+ """
+
+ id: str
+ author: Optional[str]
+ sha: Optional[str]
+ created_at: Optional[datetime]
+ last_modified: Optional[datetime]
+ private: Optional[bool]
+ disabled: Optional[bool]
+ downloads: Optional[int]
+ downloads_all_time: Optional[int]
+ gated: Optional[Literal["auto", "manual", False]]
+ gguf: Optional[Dict]
+ inference: Optional[Literal["warm", "cold", "frozen"]]
+ inference_provider_mapping: Optional[Dict[str, InferenceProviderMapping]]
+ likes: Optional[int]
+ library_name: Optional[str]
+ tags: Optional[List[str]]
+ pipeline_tag: Optional[str]
+ mask_token: Optional[str]
+ card_data: Optional[ModelCardData]
+ widget_data: Optional[Any]
+ model_index: Optional[Dict]
+ config: Optional[Dict]
+ transformers_info: Optional[TransformersInfo]
+ trending_score: Optional[int]
+ siblings: Optional[List[RepoSibling]]
+ spaces: Optional[List[str]]
+ safetensors: Optional[SafeTensorsInfo]
+ security_repo_status: Optional[Dict]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.author = kwargs.pop("author", None)
+ self.sha = kwargs.pop("sha", None)
+ last_modified = kwargs.pop("lastModified", None) or kwargs.pop("last_modified", None)
+ self.last_modified = parse_datetime(last_modified) if last_modified else None
+ created_at = kwargs.pop("createdAt", None) or kwargs.pop("created_at", None)
+ self.created_at = parse_datetime(created_at) if created_at else None
+ self.private = kwargs.pop("private", None)
+ self.gated = kwargs.pop("gated", None)
+ self.disabled = kwargs.pop("disabled", None)
+ self.downloads = kwargs.pop("downloads", None)
+ self.downloads_all_time = kwargs.pop("downloadsAllTime", None)
+ self.likes = kwargs.pop("likes", None)
+ self.library_name = kwargs.pop("library_name", None)
+ self.gguf = kwargs.pop("gguf", None)
+
+ self.inference = kwargs.pop("inference", None)
+ self.inference_provider_mapping = kwargs.pop("inferenceProviderMapping", None)
+ if self.inference_provider_mapping:
+ self.inference_provider_mapping = {
+ provider: InferenceProviderMapping(**value)
+ for provider, value in self.inference_provider_mapping.items()
+ }
+
+ self.tags = kwargs.pop("tags", None)
+ self.pipeline_tag = kwargs.pop("pipeline_tag", None)
+ self.mask_token = kwargs.pop("mask_token", None)
+ self.trending_score = kwargs.pop("trendingScore", None)
+
+ card_data = kwargs.pop("cardData", None) or kwargs.pop("card_data", None)
+ self.card_data = (
+ ModelCardData(**card_data, ignore_metadata_errors=True) if isinstance(card_data, dict) else card_data
+ )
+
+ self.widget_data = kwargs.pop("widgetData", None)
+ self.model_index = kwargs.pop("model-index", None) or kwargs.pop("model_index", None)
+ self.config = kwargs.pop("config", None)
+ transformers_info = kwargs.pop("transformersInfo", None) or kwargs.pop("transformers_info", None)
+ self.transformers_info = TransformersInfo(**transformers_info) if transformers_info else None
+ siblings = kwargs.pop("siblings", None)
+ self.siblings = (
+ [
+ RepoSibling(
+ rfilename=sibling["rfilename"],
+ size=sibling.get("size"),
+ blob_id=sibling.get("blobId"),
+ lfs=(
+ BlobLfsInfo(
+ size=sibling["lfs"]["size"],
+ sha256=sibling["lfs"]["sha256"],
+ pointer_size=sibling["lfs"]["pointerSize"],
+ )
+ if sibling.get("lfs")
+ else None
+ ),
+ )
+ for sibling in siblings
+ ]
+ if siblings is not None
+ else None
+ )
+ self.spaces = kwargs.pop("spaces", None)
+ safetensors = kwargs.pop("safetensors", None)
+ self.safetensors = (
+ SafeTensorsInfo(
+ parameters=safetensors["parameters"],
+ total=safetensors["total"],
+ )
+ if safetensors
+ else None
+ )
+ self.security_repo_status = kwargs.pop("securityRepoStatus", None)
+ # backwards compatibility
+ self.lastModified = self.last_modified
+ self.cardData = self.card_data
+ self.transformersInfo = self.transformers_info
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class DatasetInfo:
+ """
+ Contains information about a dataset on the Hub.
+
+
+
+ Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made.
+ In general, the more specific the query, the more information is returned. On the contrary, when listing datasets
+ using [`list_datasets`] only a subset of the attributes are returned.
+
+
+
+ Attributes:
+ id (`str`):
+ ID of dataset.
+ author (`str`):
+ Author of the dataset.
+ sha (`str`):
+ Repo SHA at this particular revision.
+ created_at (`datetime`, *optional*):
+ Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`,
+ corresponding to the date when we began to store creation dates.
+ last_modified (`datetime`, *optional*):
+ Date of last commit to the repo.
+ private (`bool`):
+ Is the repo private.
+ disabled (`bool`, *optional*):
+ Is the repo disabled.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ Is the repo gated.
+ If so, whether there is manual or automatic approval.
+ downloads (`int`):
+ Number of downloads of the dataset over the last 30 days.
+ downloads_all_time (`int`):
+ Cumulated number of downloads of the model since its creation.
+ likes (`int`):
+ Number of likes of the dataset.
+ tags (`List[str]`):
+ List of tags of the dataset.
+ card_data (`DatasetCardData`, *optional*):
+ Model Card Metadata as a [`huggingface_hub.repocard_data.DatasetCardData`] object.
+ siblings (`List[RepoSibling]`):
+ List of [`huggingface_hub.hf_api.RepoSibling`] objects that constitute the dataset.
+ paperswithcode_id (`str`, *optional*):
+ Papers with code ID of the dataset.
+ trending_score (`int`, *optional*):
+ Trending score of the dataset.
+ """
+
+ id: str
+ author: Optional[str]
+ sha: Optional[str]
+ created_at: Optional[datetime]
+ last_modified: Optional[datetime]
+ private: Optional[bool]
+ gated: Optional[Literal["auto", "manual", False]]
+ disabled: Optional[bool]
+ downloads: Optional[int]
+ downloads_all_time: Optional[int]
+ likes: Optional[int]
+ paperswithcode_id: Optional[str]
+ tags: Optional[List[str]]
+ trending_score: Optional[int]
+ card_data: Optional[DatasetCardData]
+ siblings: Optional[List[RepoSibling]]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.author = kwargs.pop("author", None)
+ self.sha = kwargs.pop("sha", None)
+ created_at = kwargs.pop("createdAt", None) or kwargs.pop("created_at", None)
+ self.created_at = parse_datetime(created_at) if created_at else None
+ last_modified = kwargs.pop("lastModified", None) or kwargs.pop("last_modified", None)
+ self.last_modified = parse_datetime(last_modified) if last_modified else None
+ self.private = kwargs.pop("private", None)
+ self.gated = kwargs.pop("gated", None)
+ self.disabled = kwargs.pop("disabled", None)
+ self.downloads = kwargs.pop("downloads", None)
+ self.downloads_all_time = kwargs.pop("downloadsAllTime", None)
+ self.likes = kwargs.pop("likes", None)
+ self.paperswithcode_id = kwargs.pop("paperswithcode_id", None)
+ self.tags = kwargs.pop("tags", None)
+ self.trending_score = kwargs.pop("trendingScore", None)
+
+ card_data = kwargs.pop("cardData", None) or kwargs.pop("card_data", None)
+ self.card_data = (
+ DatasetCardData(**card_data, ignore_metadata_errors=True) if isinstance(card_data, dict) else card_data
+ )
+ siblings = kwargs.pop("siblings", None)
+ self.siblings = (
+ [
+ RepoSibling(
+ rfilename=sibling["rfilename"],
+ size=sibling.get("size"),
+ blob_id=sibling.get("blobId"),
+ lfs=(
+ BlobLfsInfo(
+ size=sibling["lfs"]["size"],
+ sha256=sibling["lfs"]["sha256"],
+ pointer_size=sibling["lfs"]["pointerSize"],
+ )
+ if sibling.get("lfs")
+ else None
+ ),
+ )
+ for sibling in siblings
+ ]
+ if siblings is not None
+ else None
+ )
+
+ # backwards compatibility
+ self.lastModified = self.last_modified
+ self.cardData = self.card_data
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class SpaceInfo:
+ """
+ Contains information about a Space on the Hub.
+
+
+
+ Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made.
+ In general, the more specific the query, the more information is returned. On the contrary, when listing spaces
+ using [`list_spaces`] only a subset of the attributes are returned.
+
+
+
+ Attributes:
+ id (`str`):
+ ID of the Space.
+ author (`str`, *optional*):
+ Author of the Space.
+ sha (`str`, *optional*):
+ Repo SHA at this particular revision.
+ created_at (`datetime`, *optional*):
+ Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`,
+ corresponding to the date when we began to store creation dates.
+ last_modified (`datetime`, *optional*):
+ Date of last commit to the repo.
+ private (`bool`):
+ Is the repo private.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ Is the repo gated.
+ If so, whether there is manual or automatic approval.
+ disabled (`bool`, *optional*):
+ Is the Space disabled.
+ host (`str`, *optional*):
+ Host URL of the Space.
+ subdomain (`str`, *optional*):
+ Subdomain of the Space.
+ likes (`int`):
+ Number of likes of the Space.
+ tags (`List[str]`):
+ List of tags of the Space.
+ siblings (`List[RepoSibling]`):
+ List of [`huggingface_hub.hf_api.RepoSibling`] objects that constitute the Space.
+ card_data (`SpaceCardData`, *optional*):
+ Space Card Metadata as a [`huggingface_hub.repocard_data.SpaceCardData`] object.
+ runtime (`SpaceRuntime`, *optional*):
+ Space runtime information as a [`huggingface_hub.hf_api.SpaceRuntime`] object.
+ sdk (`str`, *optional*):
+ SDK used by the Space.
+ models (`List[str]`, *optional*):
+ List of models used by the Space.
+ datasets (`List[str]`, *optional*):
+ List of datasets used by the Space.
+ trending_score (`int`, *optional*):
+ Trending score of the Space.
+ """
+
+ id: str
+ author: Optional[str]
+ sha: Optional[str]
+ created_at: Optional[datetime]
+ last_modified: Optional[datetime]
+ private: Optional[bool]
+ gated: Optional[Literal["auto", "manual", False]]
+ disabled: Optional[bool]
+ host: Optional[str]
+ subdomain: Optional[str]
+ likes: Optional[int]
+ sdk: Optional[str]
+ tags: Optional[List[str]]
+ siblings: Optional[List[RepoSibling]]
+ trending_score: Optional[int]
+ card_data: Optional[SpaceCardData]
+ runtime: Optional[SpaceRuntime]
+ models: Optional[List[str]]
+ datasets: Optional[List[str]]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.author = kwargs.pop("author", None)
+ self.sha = kwargs.pop("sha", None)
+ created_at = kwargs.pop("createdAt", None) or kwargs.pop("created_at", None)
+ self.created_at = parse_datetime(created_at) if created_at else None
+ last_modified = kwargs.pop("lastModified", None) or kwargs.pop("last_modified", None)
+ self.last_modified = parse_datetime(last_modified) if last_modified else None
+ self.private = kwargs.pop("private", None)
+ self.gated = kwargs.pop("gated", None)
+ self.disabled = kwargs.pop("disabled", None)
+ self.host = kwargs.pop("host", None)
+ self.subdomain = kwargs.pop("subdomain", None)
+ self.likes = kwargs.pop("likes", None)
+ self.sdk = kwargs.pop("sdk", None)
+ self.tags = kwargs.pop("tags", None)
+ self.trending_score = kwargs.pop("trendingScore", None)
+ card_data = kwargs.pop("cardData", None) or kwargs.pop("card_data", None)
+ self.card_data = (
+ SpaceCardData(**card_data, ignore_metadata_errors=True) if isinstance(card_data, dict) else card_data
+ )
+ siblings = kwargs.pop("siblings", None)
+ self.siblings = (
+ [
+ RepoSibling(
+ rfilename=sibling["rfilename"],
+ size=sibling.get("size"),
+ blob_id=sibling.get("blobId"),
+ lfs=(
+ BlobLfsInfo(
+ size=sibling["lfs"]["size"],
+ sha256=sibling["lfs"]["sha256"],
+ pointer_size=sibling["lfs"]["pointerSize"],
+ )
+ if sibling.get("lfs")
+ else None
+ ),
+ )
+ for sibling in siblings
+ ]
+ if siblings is not None
+ else None
+ )
+ runtime = kwargs.pop("runtime", None)
+ self.runtime = SpaceRuntime(runtime) if runtime else None
+ self.models = kwargs.pop("models", None)
+ self.datasets = kwargs.pop("datasets", None)
+
+ # backwards compatibility
+ self.lastModified = self.last_modified
+ self.cardData = self.card_data
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class CollectionItem:
+ """
+ Contains information about an item of a Collection (model, dataset, Space or paper).
+
+ Attributes:
+ item_object_id (`str`):
+ Unique ID of the item in the collection.
+ item_id (`str`):
+ ID of the underlying object on the Hub. Can be either a repo_id or a paper id
+ e.g. `"jbilcke-hf/ai-comic-factory"`, `"2307.09288"`.
+ item_type (`str`):
+ Type of the underlying object. Can be one of `"model"`, `"dataset"`, `"space"` or `"paper"`.
+ position (`int`):
+ Position of the item in the collection.
+ note (`str`, *optional*):
+ Note associated with the item, as plain text.
+ """
+
+ item_object_id: str # id in database
+ item_id: str # repo_id or paper id
+ item_type: str
+ position: int
+ note: Optional[str] = None
+
+ def __init__(
+ self, _id: str, id: str, type: CollectionItemType_T, position: int, note: Optional[Dict] = None, **kwargs
+ ) -> None:
+ self.item_object_id: str = _id # id in database
+ self.item_id: str = id # repo_id or paper id
+ self.item_type: CollectionItemType_T = type
+ self.position: int = position
+ self.note: str = note["text"] if note is not None else None
+
+
+@dataclass
+class Collection:
+ """
+ Contains information about a Collection on the Hub.
+
+ Attributes:
+ slug (`str`):
+ Slug of the collection. E.g. `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ title (`str`):
+ Title of the collection. E.g. `"Recent models"`.
+ owner (`str`):
+ Owner of the collection. E.g. `"TheBloke"`.
+ items (`List[CollectionItem]`):
+ List of items in the collection.
+ last_updated (`datetime`):
+ Date of the last update of the collection.
+ position (`int`):
+ Position of the collection in the list of collections of the owner.
+ private (`bool`):
+ Whether the collection is private or not.
+ theme (`str`):
+ Theme of the collection. E.g. `"green"`.
+ upvotes (`int`):
+ Number of upvotes of the collection.
+ description (`str`, *optional*):
+ Description of the collection, as plain text.
+ url (`str`):
+ (property) URL of the collection on the Hub.
+ """
+
+ slug: str
+ title: str
+ owner: str
+ items: List[CollectionItem]
+ last_updated: datetime
+ position: int
+ private: bool
+ theme: str
+ upvotes: int
+ description: Optional[str] = None
+
+ def __init__(self, **kwargs) -> None:
+ self.slug = kwargs.pop("slug")
+ self.title = kwargs.pop("title")
+ self.owner = kwargs.pop("owner")
+ self.items = [CollectionItem(**item) for item in kwargs.pop("items")]
+ self.last_updated = parse_datetime(kwargs.pop("lastUpdated"))
+ self.position = kwargs.pop("position")
+ self.private = kwargs.pop("private")
+ self.theme = kwargs.pop("theme")
+ self.upvotes = kwargs.pop("upvotes")
+ self.description = kwargs.pop("description", None)
+ endpoint = kwargs.pop("endpoint", None)
+ if endpoint is None:
+ endpoint = constants.ENDPOINT
+ self._url = f"{endpoint}/collections/{self.slug}"
+
+ @property
+ def url(self) -> str:
+ """Returns the URL of the collection on the Hub."""
+ return self._url
+
+
+@dataclass
+class GitRefInfo:
+ """
+ Contains information about a git reference for a repo on the Hub.
+
+ Attributes:
+ name (`str`):
+ Name of the reference (e.g. tag name or branch name).
+ ref (`str`):
+ Full git ref on the Hub (e.g. `"refs/heads/main"` or `"refs/tags/v1.0"`).
+ target_commit (`str`):
+ OID of the target commit for the ref (e.g. `"e7da7f221d5bf496a48136c0cd264e630fe9fcc8"`)
+ """
+
+ name: str
+ ref: str
+ target_commit: str
+
+
+@dataclass
+class GitRefs:
+ """
+ Contains information about all git references for a repo on the Hub.
+
+ Object is returned by [`list_repo_refs`].
+
+ Attributes:
+ branches (`List[GitRefInfo]`):
+ A list of [`GitRefInfo`] containing information about branches on the repo.
+ converts (`List[GitRefInfo]`):
+ A list of [`GitRefInfo`] containing information about "convert" refs on the repo.
+ Converts are refs used (internally) to push preprocessed data in Dataset repos.
+ tags (`List[GitRefInfo]`):
+ A list of [`GitRefInfo`] containing information about tags on the repo.
+ pull_requests (`List[GitRefInfo]`, *optional*):
+ A list of [`GitRefInfo`] containing information about pull requests on the repo.
+ Only returned if `include_prs=True` is set.
+ """
+
+ branches: List[GitRefInfo]
+ converts: List[GitRefInfo]
+ tags: List[GitRefInfo]
+ pull_requests: Optional[List[GitRefInfo]] = None
+
+
+@dataclass
+class GitCommitInfo:
+ """
+ Contains information about a git commit for a repo on the Hub. Check out [`list_repo_commits`] for more details.
+
+ Attributes:
+ commit_id (`str`):
+ OID of the commit (e.g. `"e7da7f221d5bf496a48136c0cd264e630fe9fcc8"`)
+ authors (`List[str]`):
+ List of authors of the commit.
+ created_at (`datetime`):
+ Datetime when the commit was created.
+ title (`str`):
+ Title of the commit. This is a free-text value entered by the authors.
+ message (`str`):
+ Description of the commit. This is a free-text value entered by the authors.
+ formatted_title (`str`):
+ Title of the commit formatted as HTML. Only returned if `formatted=True` is set.
+ formatted_message (`str`):
+ Description of the commit formatted as HTML. Only returned if `formatted=True` is set.
+ """
+
+ commit_id: str
+
+ authors: List[str]
+ created_at: datetime
+ title: str
+ message: str
+
+ formatted_title: Optional[str]
+ formatted_message: Optional[str]
+
+
+@dataclass
+class UserLikes:
+ """
+ Contains information about a user likes on the Hub.
+
+ Attributes:
+ user (`str`):
+ Name of the user for which we fetched the likes.
+ total (`int`):
+ Total number of likes.
+ datasets (`List[str]`):
+ List of datasets liked by the user (as repo_ids).
+ models (`List[str]`):
+ List of models liked by the user (as repo_ids).
+ spaces (`List[str]`):
+ List of spaces liked by the user (as repo_ids).
+ """
+
+ # Metadata
+ user: str
+ total: int
+
+ # User likes
+ datasets: List[str]
+ models: List[str]
+ spaces: List[str]
+
+
+@dataclass
+class Organization:
+ """
+ Contains information about an organization on the Hub.
+
+ Attributes:
+ avatar_url (`str`):
+ URL of the organization's avatar.
+ name (`str`):
+ Name of the organization on the Hub (unique).
+ fullname (`str`):
+ Organization's full name.
+ """
+
+ avatar_url: str
+ name: str
+ fullname: str
+
+ def __init__(self, **kwargs) -> None:
+ self.avatar_url = kwargs.pop("avatarUrl", "")
+ self.name = kwargs.pop("name", "")
+ self.fullname = kwargs.pop("fullname", "")
+
+ # forward compatibility
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class User:
+ """
+ Contains information about a user on the Hub.
+
+ Attributes:
+ username (`str`):
+ Name of the user on the Hub (unique).
+ fullname (`str`):
+ User's full name.
+ avatar_url (`str`):
+ URL of the user's avatar.
+ details (`str`, *optional*):
+ User's details.
+ is_following (`bool`, *optional*):
+ Whether the authenticated user is following this user.
+ is_pro (`bool`, *optional*):
+ Whether the user is a pro user.
+ num_models (`int`, *optional*):
+ Number of models created by the user.
+ num_datasets (`int`, *optional*):
+ Number of datasets created by the user.
+ num_spaces (`int`, *optional*):
+ Number of spaces created by the user.
+ num_discussions (`int`, *optional*):
+ Number of discussions initiated by the user.
+ num_papers (`int`, *optional*):
+ Number of papers authored by the user.
+ num_upvotes (`int`, *optional*):
+ Number of upvotes received by the user.
+ num_likes (`int`, *optional*):
+ Number of likes given by the user.
+ num_following (`int`, *optional*):
+ Number of users this user is following.
+ num_followers (`int`, *optional*):
+ Number of users following this user.
+ orgs (list of [`Organization`]):
+ List of organizations the user is part of.
+ """
+
+ # Metadata
+ username: str
+ fullname: str
+ avatar_url: str
+ details: Optional[str] = None
+ is_following: Optional[bool] = None
+ is_pro: Optional[bool] = None
+ num_models: Optional[int] = None
+ num_datasets: Optional[int] = None
+ num_spaces: Optional[int] = None
+ num_discussions: Optional[int] = None
+ num_papers: Optional[int] = None
+ num_upvotes: Optional[int] = None
+ num_likes: Optional[int] = None
+ num_following: Optional[int] = None
+ num_followers: Optional[int] = None
+ orgs: List[Organization] = field(default_factory=list)
+
+ def __init__(self, **kwargs) -> None:
+ self.username = kwargs.pop("user", "")
+ self.fullname = kwargs.pop("fullname", "")
+ self.avatar_url = kwargs.pop("avatarUrl", "")
+ self.is_following = kwargs.pop("isFollowing", None)
+ self.is_pro = kwargs.pop("isPro", None)
+ self.details = kwargs.pop("details", None)
+ self.num_models = kwargs.pop("numModels", None)
+ self.num_datasets = kwargs.pop("numDatasets", None)
+ self.num_spaces = kwargs.pop("numSpaces", None)
+ self.num_discussions = kwargs.pop("numDiscussions", None)
+ self.num_papers = kwargs.pop("numPapers", None)
+ self.num_upvotes = kwargs.pop("numUpvotes", None)
+ self.num_likes = kwargs.pop("numLikes", None)
+ self.num_following = kwargs.pop("numFollowing", None)
+ self.num_followers = kwargs.pop("numFollowers", None)
+ self.user_type = kwargs.pop("type", None)
+ self.orgs = [Organization(**org) for org in kwargs.pop("orgs", [])]
+
+ # forward compatibility
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class PaperInfo:
+ """
+ Contains information about a paper on the Hub.
+
+ Attributes:
+ id (`str`):
+ arXiv paper ID.
+ authors (`List[str]`, **optional**):
+ Names of paper authors
+ published_at (`datetime`, **optional**):
+ Date paper published.
+ title (`str`, **optional**):
+ Title of the paper.
+ summary (`str`, **optional**):
+ Summary of the paper.
+ upvotes (`int`, **optional**):
+ Number of upvotes for the paper on the Hub.
+ discussion_id (`str`, **optional**):
+ Discussion ID for the paper on the Hub.
+ source (`str`, **optional**):
+ Source of the paper.
+ comments (`int`, **optional**):
+ Number of comments for the paper on the Hub.
+ submitted_at (`datetime`, **optional**):
+ Date paper appeared in daily papers on the Hub.
+ submitted_by (`User`, **optional**):
+ Information about who submitted the daily paper.
+ """
+
+ id: str
+ authors: Optional[List[str]]
+ published_at: Optional[datetime]
+ title: Optional[str]
+ summary: Optional[str]
+ upvotes: Optional[int]
+ discussion_id: Optional[str]
+ source: Optional[str]
+ comments: Optional[int]
+ submitted_at: Optional[datetime]
+ submitted_by: Optional[User]
+
+ def __init__(self, **kwargs) -> None:
+ paper = kwargs.pop("paper", {})
+ self.id = kwargs.pop("id", None) or paper.pop("id", None)
+ authors = paper.pop("authors", None) or kwargs.pop("authors", None)
+ self.authors = [author.pop("name", None) for author in authors] if authors else None
+ published_at = paper.pop("publishedAt", None) or kwargs.pop("publishedAt", None)
+ self.published_at = parse_datetime(published_at) if published_at else None
+ self.title = kwargs.pop("title", None)
+ self.source = kwargs.pop("source", None)
+ self.summary = paper.pop("summary", None) or kwargs.pop("summary", None)
+ self.upvotes = paper.pop("upvotes", None) or kwargs.pop("upvotes", None)
+ self.discussion_id = paper.pop("discussionId", None) or kwargs.pop("discussionId", None)
+ self.comments = kwargs.pop("numComments", 0)
+ submitted_at = kwargs.pop("publishedAt", None) or kwargs.pop("submittedOnDailyAt", None)
+ self.submitted_at = parse_datetime(submitted_at) if submitted_at else None
+ submitted_by = kwargs.pop("submittedBy", None) or kwargs.pop("submittedOnDailyBy", None)
+ self.submitted_by = User(**submitted_by) if submitted_by else None
+
+ # forward compatibility
+ self.__dict__.update(**kwargs)
+
+
+def future_compatible(fn: CallableT) -> CallableT:
+ """Wrap a method of `HfApi` to handle `run_as_future=True`.
+
+ A method flagged as "future_compatible" will be called in a thread if `run_as_future=True` and return a
+ `concurrent.futures.Future` instance. Otherwise, it will be called normally and return the result.
+ """
+ sig = inspect.signature(fn)
+ args_params = list(sig.parameters)[1:] # remove "self" from list
+
+ @wraps(fn)
+ def _inner(self, *args, **kwargs):
+ # Get `run_as_future` value if provided (default to False)
+ if "run_as_future" in kwargs:
+ run_as_future = kwargs["run_as_future"]
+ kwargs["run_as_future"] = False # avoid recursion error
+ else:
+ run_as_future = False
+ for param, value in zip(args_params, args):
+ if param == "run_as_future":
+ run_as_future = value
+ break
+
+ # Call the function in a thread if `run_as_future=True`
+ if run_as_future:
+ return self.run_as_future(fn, self, *args, **kwargs)
+
+ # Otherwise, call the function normally
+ return fn(self, *args, **kwargs)
+
+ _inner.is_future_compatible = True # type: ignore
+ return _inner # type: ignore
+
+
+class HfApi:
+ """
+ Client to interact with the Hugging Face Hub via HTTP.
+
+ The client is initialized with some high-level settings used in all requests
+ made to the Hub (HF endpoint, authentication, user agents...). Using the `HfApi`
+ client is preferred but not mandatory as all of its public methods are exposed
+ directly at the root of `huggingface_hub`.
+
+ Args:
+ endpoint (`str`, *optional*):
+ Endpoint of the Hub. Defaults to .
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request. Will be added to
+ the user-agent header. Example: `"transformers"`.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request. Will be added
+ to the user-agent header. Example: `"4.24.0"`.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string. It will
+ be completed with information about the installed packages.
+ headers (`dict`, *optional*):
+ Additional headers to be sent with each request. Example: `{"X-My-Header": "value"}`.
+ Headers passed here are taking precedence over the default headers.
+ """
+
+ def __init__(
+ self,
+ endpoint: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ headers: Optional[Dict[str, str]] = None,
+ ) -> None:
+ self.endpoint = endpoint if endpoint is not None else constants.ENDPOINT
+ self.token = token
+ self.library_name = library_name
+ self.library_version = library_version
+ self.user_agent = user_agent
+ self.headers = headers
+ self._thread_pool: Optional[ThreadPoolExecutor] = None
+
+ def run_as_future(self, fn: Callable[..., R], *args, **kwargs) -> Future[R]:
+ """
+ Run a method in the background and return a Future instance.
+
+ The main goal is to run methods without blocking the main thread (e.g. to push data during a training).
+ Background jobs are queued to preserve order but are not ran in parallel. If you need to speed-up your scripts
+ by parallelizing lots of call to the API, you must setup and use your own [ThreadPoolExecutor](https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor).
+
+ Note: Most-used methods like [`upload_file`], [`upload_folder`] and [`create_commit`] have a `run_as_future: bool`
+ argument to directly call them in the background. This is equivalent to calling `api.run_as_future(...)` on them
+ but less verbose.
+
+ Args:
+ fn (`Callable`):
+ The method to run in the background.
+ *args, **kwargs:
+ Arguments with which the method will be called.
+
+ Return:
+ `Future`: a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects) instance to
+ get the result of the task.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> future = api.run_as_future(api.whoami) # instant
+ >>> future.done()
+ False
+ >>> future.result() # wait until complete and return result
+ (...)
+ >>> future.done()
+ True
+ ```
+ """
+ if self._thread_pool is None:
+ self._thread_pool = ThreadPoolExecutor(max_workers=1)
+ self._thread_pool
+ return self._thread_pool.submit(fn, *args, **kwargs)
+
+ @validate_hf_hub_args
+ def whoami(self, token: Union[bool, str, None] = None) -> Dict:
+ """
+ Call HF API to know "whoami".
+
+ Args:
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ # Get the effective token using the helper function get_token
+ effective_token = token or self.token or get_token() or True
+ r = get_session().get(
+ f"{self.endpoint}/api/whoami-v2",
+ headers=self._build_hf_headers(token=effective_token),
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as e:
+ error_message = "Invalid user token."
+ # Check which token is the effective one and generate the error message accordingly
+ if effective_token == _get_token_from_google_colab():
+ error_message += " The token from Google Colab vault is invalid. Please update it from the UI."
+ elif effective_token == _get_token_from_environment():
+ error_message += (
+ " The token from HF_TOKEN environment variable is invalid. "
+ "Note that HF_TOKEN takes precedence over `huggingface-cli login`."
+ )
+ elif effective_token == _get_token_from_file():
+ error_message += " The token stored is invalid. Please run `huggingface-cli login` to update it."
+ raise HTTPError(error_message, request=e.request, response=e.response) from e
+ return r.json()
+
+ @_deprecate_method(
+ version="1.0",
+ message=(
+ "Permissions are more complex than when `get_token_permission` was first introduced. "
+ "OAuth and fine-grain tokens allows for more detailed permissions. "
+ "If you need to know the permissions associated with a token, please use `whoami` and check the `'auth'` key."
+ ),
+ )
+ def get_token_permission(
+ self, token: Union[bool, str, None] = None
+ ) -> Literal["read", "write", "fineGrained", None]:
+ """
+ Check if a given `token` is valid and return its permissions.
+
+
+
+ This method is deprecated and will be removed in version 1.0. Permissions are more complex than when
+ `get_token_permission` was first introduced. OAuth and fine-grain tokens allows for more detailed permissions.
+ If you need to know the permissions associated with a token, please use `whoami` and check the `'auth'` key.
+
+
+
+ For more details about tokens, please refer to https://huggingface.co/docs/hub/security-tokens#what-are-user-access-tokens.
+
+ Args:
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Literal["read", "write", "fineGrained", None]`: Permission granted by the token ("read" or "write"). Returns `None` if no
+ token passed, if token is invalid or if role is not returned by the server. This typically happens when the token is an OAuth token.
+ """
+ try:
+ return self.whoami(token=token)["auth"]["accessToken"]["role"]
+ except (LocalTokenNotFoundError, HTTPError, KeyError):
+ return None
+
+ def get_model_tags(self) -> Dict:
+ """
+ List all valid model tags as a nested namespace object
+ """
+ path = f"{self.endpoint}/api/models-tags-by-type"
+ r = get_session().get(path)
+ hf_raise_for_status(r)
+ return r.json()
+
+ def get_dataset_tags(self) -> Dict:
+ """
+ List all valid dataset tags as a nested namespace object.
+ """
+ path = f"{self.endpoint}/api/datasets-tags-by-type"
+ r = get_session().get(path)
+ hf_raise_for_status(r)
+ return r.json()
+
+ @validate_hf_hub_args
+ def list_models(
+ self,
+ *,
+ # Search-query parameter
+ filter: Union[str, Iterable[str], None] = None,
+ author: Optional[str] = None,
+ gated: Optional[bool] = None,
+ inference: Optional[Literal["cold", "frozen", "warm"]] = None,
+ library: Optional[Union[str, List[str]]] = None,
+ language: Optional[Union[str, List[str]]] = None,
+ model_name: Optional[str] = None,
+ task: Optional[Union[str, List[str]]] = None,
+ trained_dataset: Optional[Union[str, List[str]]] = None,
+ tags: Optional[Union[str, List[str]]] = None,
+ search: Optional[str] = None,
+ pipeline_tag: Optional[str] = None,
+ emissions_thresholds: Optional[Tuple[float, float]] = None,
+ # Sorting and pagination parameters
+ sort: Union[Literal["last_modified"], str, None] = None,
+ direction: Optional[Literal[-1]] = None,
+ limit: Optional[int] = None,
+ # Additional data to fetch
+ expand: Optional[List[ExpandModelProperty_T]] = None,
+ full: Optional[bool] = None,
+ cardData: bool = False,
+ fetch_config: bool = False,
+ token: Union[bool, str, None] = None,
+ ) -> Iterable[ModelInfo]:
+ """
+ List models hosted on the Huggingface Hub, given some filters.
+
+ Args:
+ filter (`str` or `Iterable[str]`, *optional*):
+ A string or list of string to filter models on the Hub.
+ author (`str`, *optional*):
+ A string which identify the author (user or organization) of the
+ returned models.
+ gated (`bool`, *optional*):
+ A boolean to filter models on the Hub that are gated or not. By default, all models are returned.
+ If `gated=True` is passed, only gated models are returned.
+ If `gated=False` is passed, only non-gated models are returned.
+ inference (`Literal["cold", "frozen", "warm"]`, *optional*):
+ A string to filter models on the Hub by their state on the Inference API.
+ Warm models are available for immediate use. Cold models will be loaded on first inference call.
+ Frozen models are not available in Inference API.
+ library (`str` or `List`, *optional*):
+ A string or list of strings of foundational libraries models were
+ originally trained from, such as pytorch, tensorflow, or allennlp.
+ language (`str` or `List`, *optional*):
+ A string or list of strings of languages, both by name and country
+ code, such as "en" or "English"
+ model_name (`str`, *optional*):
+ A string that contain complete or partial names for models on the
+ Hub, such as "bert" or "bert-base-cased"
+ task (`str` or `List`, *optional*):
+ A string or list of strings of tasks models were designed for, such
+ as: "fill-mask" or "automatic-speech-recognition"
+ trained_dataset (`str` or `List`, *optional*):
+ A string tag or a list of string tags of the trained dataset for a
+ model on the Hub.
+ tags (`str` or `List`, *optional*):
+ A string tag or a list of tags to filter models on the Hub by, such
+ as `text-generation` or `spacy`.
+ search (`str`, *optional*):
+ A string that will be contained in the returned model ids.
+ pipeline_tag (`str`, *optional*):
+ A string pipeline tag to filter models on the Hub by, such as `summarization`.
+ emissions_thresholds (`Tuple`, *optional*):
+ A tuple of two ints or floats representing a minimum and maximum
+ carbon footprint to filter the resulting models with in grams.
+ sort (`Literal["last_modified"]` or `str`, *optional*):
+ The key with which to sort the resulting models. Possible values are "last_modified", "trending_score",
+ "created_at", "downloads" and "likes".
+ direction (`Literal[-1]` or `int`, *optional*):
+ Direction in which to sort. The value `-1` sorts by descending
+ order while all other values sort by ascending order.
+ limit (`int`, *optional*):
+ The limit on the number of models fetched. Leaving this option
+ to `None` fetches all models.
+ expand (`List[ExpandModelProperty_T]`, *optional*):
+ List properties to return in the response. When used, only the properties in the list will be returned.
+ This parameter cannot be used if `full`, `cardData` or `fetch_config` are passed.
+ Possible values are `"author"`, `"baseModels"`, `"cardData"`, `"childrenModelCount"`, `"config"`, `"createdAt"`, `"disabled"`, `"downloads"`, `"downloadsAllTime"`, `"gated"`, `"gguf"`, `"inference"`, `"inferenceProviderMapping"`, `"lastModified"`, `"library_name"`, `"likes"`, `"mask_token"`, `"model-index"`, `"pipeline_tag"`, `"private"`, `"safetensors"`, `"sha"`, `"siblings"`, `"spaces"`, `"tags"`, `"transformersInfo"`, `"trendingScore"`, `"widgetData"`, `"usedStorage"` and `"resourceGroup"`.
+ full (`bool`, *optional*):
+ Whether to fetch all model data, including the `last_modified`,
+ the `sha`, the files and the `tags`. This is set to `True` by
+ default when using a filter.
+ cardData (`bool`, *optional*):
+ Whether to grab the metadata for the model as well. Can contain
+ useful information such as carbon emissions, metrics, and
+ datasets trained on.
+ fetch_config (`bool`, *optional*):
+ Whether to fetch the model configs as well. This is not included
+ in `full` due to its size.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+
+ Returns:
+ `Iterable[ModelInfo]`: an iterable of [`huggingface_hub.hf_api.ModelInfo`] objects.
+
+ Example usage with the `filter` argument:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ # List all models
+ >>> api.list_models()
+
+ # List only the text classification models
+ >>> api.list_models(filter="text-classification")
+
+ # List only models from the AllenNLP library
+ >>> api.list_models(filter="allennlp")
+ ```
+
+ Example usage with the `search` argument:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ # List all models with "bert" in their name
+ >>> api.list_models(search="bert")
+
+ # List all models with "bert" in their name made by google
+ >>> api.list_models(search="bert", author="google")
+ ```
+ """
+ if expand and (full or cardData or fetch_config):
+ raise ValueError("`expand` cannot be used if `full`, `cardData` or `fetch_config` are passed.")
+
+ if emissions_thresholds is not None and cardData is None:
+ raise ValueError("`emissions_thresholds` were passed without setting `cardData=True`.")
+
+ path = f"{self.endpoint}/api/models"
+ headers = self._build_hf_headers(token=token)
+ params: Dict[str, Any] = {}
+
+ # Build the filter list
+ filter_list: List[str] = []
+ if filter:
+ filter_list.extend([filter] if isinstance(filter, str) else filter)
+ if library:
+ filter_list.extend([library] if isinstance(library, str) else library)
+ if task:
+ filter_list.extend([task] if isinstance(task, str) else task)
+ if trained_dataset:
+ if isinstance(trained_dataset, str):
+ trained_dataset = [trained_dataset]
+ for dataset in trained_dataset:
+ if not dataset.startswith("dataset:"):
+ dataset = f"dataset:{dataset}"
+ filter_list.append(dataset)
+ if language:
+ filter_list.extend([language] if isinstance(language, str) else language)
+ if tags:
+ filter_list.extend([tags] if isinstance(tags, str) else tags)
+ if len(filter_list) > 0:
+ params["filter"] = filter_list
+
+ # Handle other query params
+ if author:
+ params["author"] = author
+ if gated is not None:
+ params["gated"] = gated
+ if inference is not None:
+ params["inference"] = inference
+ if pipeline_tag:
+ params["pipeline_tag"] = pipeline_tag
+ search_list = []
+ if model_name:
+ search_list.append(model_name)
+ if search:
+ search_list.append(search)
+ if len(search_list) > 0:
+ params["search"] = search_list
+ if sort is not None:
+ params["sort"] = (
+ "lastModified"
+ if sort == "last_modified"
+ else "trendingScore"
+ if sort == "trending_score"
+ else "createdAt"
+ if sort == "created_at"
+ else sort
+ )
+ if direction is not None:
+ params["direction"] = direction
+ if limit is not None:
+ params["limit"] = limit
+
+ # Request additional data
+ if full:
+ params["full"] = True
+ if fetch_config:
+ params["config"] = True
+ if cardData:
+ params["cardData"] = True
+ if expand:
+ params["expand"] = expand
+
+ # `items` is a generator
+ items = paginate(path, params=params, headers=headers)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+ for item in items:
+ if "siblings" not in item:
+ item["siblings"] = None
+ model_info = ModelInfo(**item)
+ if emissions_thresholds is None or _is_emission_within_threshold(model_info, *emissions_thresholds):
+ yield model_info
+
+ @validate_hf_hub_args
+ def list_datasets(
+ self,
+ *,
+ # Search-query parameter
+ filter: Union[str, Iterable[str], None] = None,
+ author: Optional[str] = None,
+ benchmark: Optional[Union[str, List[str]]] = None,
+ dataset_name: Optional[str] = None,
+ gated: Optional[bool] = None,
+ language_creators: Optional[Union[str, List[str]]] = None,
+ language: Optional[Union[str, List[str]]] = None,
+ multilinguality: Optional[Union[str, List[str]]] = None,
+ size_categories: Optional[Union[str, List[str]]] = None,
+ tags: Optional[Union[str, List[str]]] = None,
+ task_categories: Optional[Union[str, List[str]]] = None,
+ task_ids: Optional[Union[str, List[str]]] = None,
+ search: Optional[str] = None,
+ # Sorting and pagination parameters
+ sort: Optional[Union[Literal["last_modified"], str]] = None,
+ direction: Optional[Literal[-1]] = None,
+ limit: Optional[int] = None,
+ # Additional data to fetch
+ expand: Optional[List[ExpandDatasetProperty_T]] = None,
+ full: Optional[bool] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Iterable[DatasetInfo]:
+ """
+ List datasets hosted on the Huggingface Hub, given some filters.
+
+ Args:
+ filter (`str` or `Iterable[str]`, *optional*):
+ A string or list of string to filter datasets on the hub.
+ author (`str`, *optional*):
+ A string which identify the author of the returned datasets.
+ benchmark (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by their official benchmark.
+ dataset_name (`str`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by its name, such as `SQAC` or `wikineural`
+ gated (`bool`, *optional*):
+ A boolean to filter datasets on the Hub that are gated or not. By default, all datasets are returned.
+ If `gated=True` is passed, only gated datasets are returned.
+ If `gated=False` is passed, only non-gated datasets are returned.
+ language_creators (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub with how the data was curated, such as `crowdsourced` or
+ `machine_generated`.
+ language (`str` or `List`, *optional*):
+ A string or list of strings representing a two-character language to
+ filter datasets by on the Hub.
+ multilinguality (`str` or `List`, *optional*):
+ A string or list of strings representing a filter for datasets that
+ contain multiple languages.
+ size_categories (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by the size of the dataset such as `100K>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ # List all datasets
+ >>> api.list_datasets()
+
+
+ # List only the text classification datasets
+ >>> api.list_datasets(filter="task_categories:text-classification")
+
+
+ # List only the datasets in russian for language modeling
+ >>> api.list_datasets(
+ ... filter=("language:ru", "task_ids:language-modeling")
+ ... )
+
+ # List FiftyOne datasets (identified by the tag "fiftyone" in dataset card)
+ >>> api.list_datasets(tags="fiftyone")
+ ```
+
+ Example usage with the `search` argument:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ # List all datasets with "text" in their name
+ >>> api.list_datasets(search="text")
+
+ # List all datasets with "text" in their name made by google
+ >>> api.list_datasets(search="text", author="google")
+ ```
+ """
+ if expand and full:
+ raise ValueError("`expand` cannot be used if `full` is passed.")
+
+ path = f"{self.endpoint}/api/datasets"
+ headers = self._build_hf_headers(token=token)
+ params: Dict[str, Any] = {}
+
+ # Build `filter` list
+ filter_list = []
+ if filter is not None:
+ if isinstance(filter, str):
+ filter_list.append(filter)
+ else:
+ filter_list.extend(filter)
+ for key, value in (
+ ("benchmark", benchmark),
+ ("language_creators", language_creators),
+ ("language", language),
+ ("multilinguality", multilinguality),
+ ("size_categories", size_categories),
+ ("task_categories", task_categories),
+ ("task_ids", task_ids),
+ ):
+ if value:
+ if isinstance(value, str):
+ value = [value]
+ for value_item in value:
+ if not value_item.startswith(f"{key}:"):
+ data = f"{key}:{value_item}"
+ filter_list.append(data)
+ if tags is not None:
+ filter_list.extend([tags] if isinstance(tags, str) else tags)
+ if len(filter_list) > 0:
+ params["filter"] = filter_list
+
+ # Handle other query params
+ if author:
+ params["author"] = author
+ if gated is not None:
+ params["gated"] = gated
+ search_list = []
+ if dataset_name:
+ search_list.append(dataset_name)
+ if search:
+ search_list.append(search)
+ if len(search_list) > 0:
+ params["search"] = search_list
+ if sort is not None:
+ params["sort"] = (
+ "lastModified"
+ if sort == "last_modified"
+ else "trendingScore"
+ if sort == "trending_score"
+ else "createdAt"
+ if sort == "created_at"
+ else sort
+ )
+ if direction is not None:
+ params["direction"] = direction
+ if limit is not None:
+ params["limit"] = limit
+
+ # Request additional data
+ if expand:
+ params["expand"] = expand
+ if full:
+ params["full"] = True
+
+ items = paginate(path, params=params, headers=headers)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+ for item in items:
+ if "siblings" not in item:
+ item["siblings"] = None
+ yield DatasetInfo(**item)
+
+ @validate_hf_hub_args
+ def list_spaces(
+ self,
+ *,
+ # Search-query parameter
+ filter: Union[str, Iterable[str], None] = None,
+ author: Optional[str] = None,
+ search: Optional[str] = None,
+ datasets: Union[str, Iterable[str], None] = None,
+ models: Union[str, Iterable[str], None] = None,
+ linked: bool = False,
+ # Sorting and pagination parameters
+ sort: Union[Literal["last_modified"], str, None] = None,
+ direction: Optional[Literal[-1]] = None,
+ limit: Optional[int] = None,
+ # Additional data to fetch
+ expand: Optional[List[ExpandSpaceProperty_T]] = None,
+ full: Optional[bool] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Iterable[SpaceInfo]:
+ """
+ List spaces hosted on the Huggingface Hub, given some filters.
+
+ Args:
+ filter (`str` or `Iterable`, *optional*):
+ A string tag or list of tags that can be used to identify Spaces on the Hub.
+ author (`str`, *optional*):
+ A string which identify the author of the returned Spaces.
+ search (`str`, *optional*):
+ A string that will be contained in the returned Spaces.
+ datasets (`str` or `Iterable`, *optional*):
+ Whether to return Spaces that make use of a dataset.
+ The name of a specific dataset can be passed as a string.
+ models (`str` or `Iterable`, *optional*):
+ Whether to return Spaces that make use of a model.
+ The name of a specific model can be passed as a string.
+ linked (`bool`, *optional*):
+ Whether to return Spaces that make use of either a model or a dataset.
+ sort (`Literal["last_modified"]` or `str`, *optional*):
+ The key with which to sort the resulting models. Possible values are "last_modified", "trending_score",
+ "created_at" and "likes".
+ direction (`Literal[-1]` or `int`, *optional*):
+ Direction in which to sort. The value `-1` sorts by descending
+ order while all other values sort by ascending order.
+ limit (`int`, *optional*):
+ The limit on the number of Spaces fetched. Leaving this option
+ to `None` fetches all Spaces.
+ expand (`List[ExpandSpaceProperty_T]`, *optional*):
+ List properties to return in the response. When used, only the properties in the list will be returned.
+ This parameter cannot be used if `full` is passed.
+ Possible values are `"author"`, `"cardData"`, `"datasets"`, `"disabled"`, `"lastModified"`, `"createdAt"`, `"likes"`, `"models"`, `"private"`, `"runtime"`, `"sdk"`, `"siblings"`, `"sha"`, `"subdomain"`, `"tags"`, `"trendingScore"`, `"usedStorage"` and `"resourceGroup"`.
+ full (`bool`, *optional*):
+ Whether to fetch all Spaces data, including the `last_modified`, `siblings`
+ and `card_data` fields.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[SpaceInfo]`: an iterable of [`huggingface_hub.hf_api.SpaceInfo`] objects.
+ """
+ if expand and full:
+ raise ValueError("`expand` cannot be used if `full` is passed.")
+
+ path = f"{self.endpoint}/api/spaces"
+ headers = self._build_hf_headers(token=token)
+ params: Dict[str, Any] = {}
+ if filter is not None:
+ params["filter"] = filter
+ if author is not None:
+ params["author"] = author
+ if search is not None:
+ params["search"] = search
+ if sort is not None:
+ params["sort"] = (
+ "lastModified"
+ if sort == "last_modified"
+ else "trendingScore"
+ if sort == "trending_score"
+ else "createdAt"
+ if sort == "created_at"
+ else sort
+ )
+ if direction is not None:
+ params["direction"] = direction
+ if limit is not None:
+ params["limit"] = limit
+ if linked:
+ params["linked"] = True
+ if datasets is not None:
+ params["datasets"] = datasets
+ if models is not None:
+ params["models"] = models
+
+ # Request additional data
+ if expand:
+ params["expand"] = expand
+ if full:
+ params["full"] = True
+
+ items = paginate(path, params=params, headers=headers)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+ for item in items:
+ if "siblings" not in item:
+ item["siblings"] = None
+ yield SpaceInfo(**item)
+
+ @validate_hf_hub_args
+ def unlike(
+ self,
+ repo_id: str,
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Unlike a given repo on the Hub (e.g. remove from favorite list).
+
+ To prevent spam usage, it is not possible to `like` a repository from a script.
+
+ See also [`list_liked_repos`].
+
+ Args:
+ repo_id (`str`):
+ The repository to unlike. Example: `"user/my-cool-model"`.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if unliking a dataset or space, `None` or
+ `"model"` if unliking a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import list_liked_repos, unlike
+ >>> "gpt2" in list_liked_repos().models # we assume you have already liked gpt2
+ True
+ >>> unlike("gpt2")
+ >>> "gpt2" in list_liked_repos().models
+ False
+ ```
+ """
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ response = get_session().delete(
+ url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/like", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def list_liked_repos(
+ self,
+ user: Optional[str] = None,
+ *,
+ token: Union[bool, str, None] = None,
+ ) -> UserLikes:
+ """
+ List all public repos liked by a user on huggingface.co.
+
+ This list is public so token is optional. If `user` is not passed, it defaults to
+ the logged in user.
+
+ See also [`unlike`].
+
+ Args:
+ user (`str`, *optional*):
+ Name of the user for which you want to fetch the likes.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`UserLikes`]: object containing the user name and 3 lists of repo ids (1 for
+ models, 1 for datasets and 1 for Spaces).
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `user` is not passed and no token found (either from argument or from machine).
+
+ Example:
+ ```python
+ >>> from huggingface_hub import list_liked_repos
+
+ >>> likes = list_liked_repos("julien-c")
+
+ >>> likes.user
+ "julien-c"
+
+ >>> likes.models
+ ["osanseviero/streamlit_1.15", "Xhaheen/ChatGPT_HF", ...]
+ ```
+ """
+ # User is either provided explicitly or retrieved from current token.
+ if user is None:
+ me = self.whoami(token=token)
+ if me["type"] == "user":
+ user = me["name"]
+ else:
+ raise ValueError(
+ "Cannot list liked repos. You must provide a 'user' as input or be logged in as a user."
+ )
+
+ path = f"{self.endpoint}/api/users/{user}/likes"
+ headers = self._build_hf_headers(token=token)
+
+ likes = list(paginate(path, params={}, headers=headers))
+ # Looping over a list of items similar to:
+ # {
+ # 'createdAt': '2021-09-09T21:53:27.000Z',
+ # 'repo': {
+ # 'name': 'PaddlePaddle/PaddleOCR',
+ # 'type': 'space'
+ # }
+ # }
+ # Let's loop 3 times over the received list. Less efficient but more straightforward to read.
+ return UserLikes(
+ user=user,
+ total=len(likes),
+ models=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "model"],
+ datasets=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "dataset"],
+ spaces=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "space"],
+ )
+
+ @validate_hf_hub_args
+ def list_repo_likers(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Iterable[User]:
+ """
+ List all users who liked a given repo on the hugging Face Hub.
+
+ See also [`list_liked_repos`].
+
+ Args:
+ repo_id (`str`):
+ The repository to retrieve . Example: `"user/my-cool-model"`.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns:
+ `Iterable[User]`: an iterable of [`huggingface_hub.hf_api.User`] objects.
+ """
+
+ # Construct the API endpoint
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/likers"
+ for liker in paginate(path, params={}, headers=self._build_hf_headers(token=token)):
+ yield User(username=liker["user"], fullname=liker["fullname"], avatar_url=liker["avatarUrl"])
+
+ @validate_hf_hub_args
+ def model_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ timeout: Optional[float] = None,
+ securityStatus: Optional[bool] = None,
+ files_metadata: bool = False,
+ expand: Optional[List[ExpandModelProperty_T]] = None,
+ token: Union[bool, str, None] = None,
+ ) -> ModelInfo:
+ """
+ Get info on one specific model on huggingface.co
+
+ Model can be private if you pass an acceptable token or are logged in.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the model repository from which to get the
+ information.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ securityStatus (`bool`, *optional*):
+ Whether to retrieve the security status from the model
+ repository as well. The security status will be returned in the `security_repo_status` field.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ expand (`List[ExpandModelProperty_T]`, *optional*):
+ List properties to return in the response. When used, only the properties in the list will be returned.
+ This parameter cannot be used if `securityStatus` or `files_metadata` are passed.
+ Possible values are `"author"`, `"baseModels"`, `"cardData"`, `"childrenModelCount"`, `"config"`, `"createdAt"`, `"disabled"`, `"downloads"`, `"downloadsAllTime"`, `"gated"`, `"gguf"`, `"inference"`, `"inferenceProviderMapping"`, `"lastModified"`, `"library_name"`, `"likes"`, `"mask_token"`, `"model-index"`, `"pipeline_tag"`, `"private"`, `"safetensors"`, `"sha"`, `"siblings"`, `"spaces"`, `"tags"`, `"transformersInfo"`, `"trendingScore"`, `"widgetData"`, `"usedStorage"` and `"resourceGroup"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`huggingface_hub.hf_api.ModelInfo`]: The model repository information.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ if expand and (securityStatus or files_metadata):
+ raise ValueError("`expand` cannot be used if `securityStatus` or `files_metadata` are set.")
+
+ headers = self._build_hf_headers(token=token)
+ path = (
+ f"{self.endpoint}/api/models/{repo_id}"
+ if revision is None
+ else (f"{self.endpoint}/api/models/{repo_id}/revision/{quote(revision, safe='')}")
+ )
+ params: Dict = {}
+ if securityStatus:
+ params["securityStatus"] = True
+ if files_metadata:
+ params["blobs"] = True
+ if expand:
+ params["expand"] = expand
+ r = get_session().get(path, headers=headers, timeout=timeout, params=params)
+ hf_raise_for_status(r)
+ data = r.json()
+ return ModelInfo(**data)
+
+ @validate_hf_hub_args
+ def dataset_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ timeout: Optional[float] = None,
+ files_metadata: bool = False,
+ expand: Optional[List[ExpandDatasetProperty_T]] = None,
+ token: Union[bool, str, None] = None,
+ ) -> DatasetInfo:
+ """
+ Get info on one specific dataset on huggingface.co.
+
+ Dataset can be private if you pass an acceptable token.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the dataset repository from which to get the
+ information.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ expand (`List[ExpandDatasetProperty_T]`, *optional*):
+ List properties to return in the response. When used, only the properties in the list will be returned.
+ This parameter cannot be used if `files_metadata` is passed.
+ Possible values are `"author"`, `"cardData"`, `"citation"`, `"createdAt"`, `"disabled"`, `"description"`, `"downloads"`, `"downloadsAllTime"`, `"gated"`, `"lastModified"`, `"likes"`, `"paperswithcode_id"`, `"private"`, `"siblings"`, `"sha"`, `"tags"`, `"trendingScore"`,`"usedStorage"` and `"resourceGroup"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`hf_api.DatasetInfo`]: The dataset repository information.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ if expand and files_metadata:
+ raise ValueError("`expand` cannot be used if `files_metadata` is set.")
+
+ headers = self._build_hf_headers(token=token)
+ path = (
+ f"{self.endpoint}/api/datasets/{repo_id}"
+ if revision is None
+ else (f"{self.endpoint}/api/datasets/{repo_id}/revision/{quote(revision, safe='')}")
+ )
+ params: Dict = {}
+ if files_metadata:
+ params["blobs"] = True
+ if expand:
+ params["expand"] = expand
+
+ r = get_session().get(path, headers=headers, timeout=timeout, params=params)
+ hf_raise_for_status(r)
+ data = r.json()
+ return DatasetInfo(**data)
+
+ @validate_hf_hub_args
+ def space_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ timeout: Optional[float] = None,
+ files_metadata: bool = False,
+ expand: Optional[List[ExpandSpaceProperty_T]] = None,
+ token: Union[bool, str, None] = None,
+ ) -> SpaceInfo:
+ """
+ Get info on one specific Space on huggingface.co.
+
+ Space can be private if you pass an acceptable token.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the space repository from which to get the
+ information.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ expand (`List[ExpandSpaceProperty_T]`, *optional*):
+ List properties to return in the response. When used, only the properties in the list will be returned.
+ This parameter cannot be used if `full` is passed.
+ Possible values are `"author"`, `"cardData"`, `"createdAt"`, `"datasets"`, `"disabled"`, `"lastModified"`, `"likes"`, `"models"`, `"private"`, `"runtime"`, `"sdk"`, `"siblings"`, `"sha"`, `"subdomain"`, `"tags"`, `"trendingScore"`, `"usedStorage"` and `"resourceGroup"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`~hf_api.SpaceInfo`]: The space repository information.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ if expand and files_metadata:
+ raise ValueError("`expand` cannot be used if `files_metadata` is set.")
+
+ headers = self._build_hf_headers(token=token)
+ path = (
+ f"{self.endpoint}/api/spaces/{repo_id}"
+ if revision is None
+ else (f"{self.endpoint}/api/spaces/{repo_id}/revision/{quote(revision, safe='')}")
+ )
+ params: Dict = {}
+ if files_metadata:
+ params["blobs"] = True
+ if expand:
+ params["expand"] = expand
+
+ r = get_session().get(path, headers=headers, timeout=timeout, params=params)
+ hf_raise_for_status(r)
+ data = r.json()
+ return SpaceInfo(**data)
+
+ @validate_hf_hub_args
+ def repo_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ timeout: Optional[float] = None,
+ files_metadata: bool = False,
+ expand: Optional[Union[ExpandModelProperty_T, ExpandDatasetProperty_T, ExpandSpaceProperty_T]] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Union[ModelInfo, DatasetInfo, SpaceInfo]:
+ """
+ Get the info object for a given repo of a given type.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the
+ information.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ expand (`ExpandModelProperty_T` or `ExpandDatasetProperty_T` or `ExpandSpaceProperty_T`, *optional*):
+ List properties to return in the response. When used, only the properties in the list will be returned.
+ This parameter cannot be used if `files_metadata` is passed.
+ For an exhaustive list of available properties, check out [`model_info`], [`dataset_info`] or [`space_info`].
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Union[SpaceInfo, DatasetInfo, ModelInfo]`: The repository information, as a
+ [`huggingface_hub.hf_api.DatasetInfo`], [`huggingface_hub.hf_api.ModelInfo`]
+ or [`huggingface_hub.hf_api.SpaceInfo`] object.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ if repo_type is None or repo_type == "model":
+ method = self.model_info
+ elif repo_type == "dataset":
+ method = self.dataset_info # type: ignore
+ elif repo_type == "space":
+ method = self.space_info # type: ignore
+ else:
+ raise ValueError("Unsupported repo type.")
+ return method(
+ repo_id,
+ revision=revision,
+ token=token,
+ timeout=timeout,
+ expand=expand, # type: ignore[arg-type]
+ files_metadata=files_metadata,
+ )
+
+ @validate_hf_hub_args
+ def repo_exists(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> bool:
+ """
+ Checks if a repository exists on the Hugging Face Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ True if the repository exists, False otherwise.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import repo_exists
+ >>> repo_exists("google/gemma-7b")
+ True
+ >>> repo_exists("google/not-a-repo")
+ False
+ ```
+ """
+ try:
+ self.repo_info(repo_id=repo_id, repo_type=repo_type, token=token)
+ return True
+ except GatedRepoError:
+ return True # we don't have access but it exists
+ except RepositoryNotFoundError:
+ return False
+
+ @validate_hf_hub_args
+ def revision_exists(
+ self,
+ repo_id: str,
+ revision: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> bool:
+ """
+ Checks if a specific revision exists on a repo on the Hugging Face Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`):
+ The revision of the repository to check.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ True if the repository and the revision exists, False otherwise.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import revision_exists
+ >>> revision_exists("google/gemma-7b", "float16")
+ True
+ >>> revision_exists("google/gemma-7b", "not-a-revision")
+ False
+ ```
+ """
+ try:
+ self.repo_info(repo_id=repo_id, revision=revision, repo_type=repo_type, token=token)
+ return True
+ except RevisionNotFoundError:
+ return False
+ except RepositoryNotFoundError:
+ return False
+
+ @validate_hf_hub_args
+ def file_exists(
+ self,
+ repo_id: str,
+ filename: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> bool:
+ """
+ Checks if a file exists in a repository on the Hugging Face Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ filename (`str`):
+ The name of the file to check, for example:
+ `"config.json"`
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the information. Defaults to `"main"` branch.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ True if the file exists, False otherwise.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import file_exists
+ >>> file_exists("bigcode/starcoder", "config.json")
+ True
+ >>> file_exists("bigcode/starcoder", "not-a-file")
+ False
+ >>> file_exists("bigcode/not-a-repo", "config.json")
+ False
+ ```
+ """
+ url = hf_hub_url(
+ repo_id=repo_id, repo_type=repo_type, revision=revision, filename=filename, endpoint=self.endpoint
+ )
+ try:
+ if token is None:
+ token = self.token
+ get_hf_file_metadata(url, token=token)
+ return True
+ except GatedRepoError: # raise specifically on gated repo
+ raise
+ except (RepositoryNotFoundError, EntryNotFoundError, RevisionNotFoundError):
+ return False
+
+ @validate_hf_hub_args
+ def list_repo_files(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> List[str]:
+ """
+ Get the list of files in a given repo.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the information.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to
+ a model. Default is `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `List[str]`: the list of files in a given repository.
+ """
+ return [
+ f.rfilename
+ for f in self.list_repo_tree(
+ repo_id=repo_id, recursive=True, revision=revision, repo_type=repo_type, token=token
+ )
+ if isinstance(f, RepoFile)
+ ]
+
+ @validate_hf_hub_args
+ def list_repo_tree(
+ self,
+ repo_id: str,
+ path_in_repo: Optional[str] = None,
+ *,
+ recursive: bool = False,
+ expand: bool = False,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> Iterable[Union[RepoFile, RepoFolder]]:
+ """
+ List a repo tree's files and folders and get information about them.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ path_in_repo (`str`, *optional*):
+ Relative path of the tree (folder) in the repo, for example:
+ `"checkpoints/1fec34a/results"`. Will default to the root tree (folder) of the repository.
+ recursive (`bool`, *optional*, defaults to `False`):
+ Whether to list tree's files and folders recursively.
+ expand (`bool`, *optional*, defaults to `False`):
+ Whether to fetch more information about the tree's files and folders (e.g. last commit and files' security scan results). This
+ operation is more expensive for the server so only 50 results are returned per page (instead of 1000).
+ As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it
+ takes to get the results.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the tree. Defaults to `"main"` branch.
+ repo_type (`str`, *optional*):
+ The type of the repository from which to get the tree (`"model"`, `"dataset"` or `"space"`.
+ Defaults to `"model"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[Union[RepoFile, RepoFolder]]`:
+ The information about the tree's files and folders, as an iterable of [`RepoFile`] and [`RepoFolder`] objects. The order of the files and folders is
+ not guaranteed.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+ [`~utils.EntryNotFoundError`]:
+ If the tree (folder) does not exist (error 404) on the repo.
+
+ Examples:
+
+ Get information about a repo's tree.
+ ```py
+ >>> from huggingface_hub import list_repo_tree
+ >>> repo_tree = list_repo_tree("lysandre/arxiv-nlp")
+ >>> repo_tree
+
+ >>> list(repo_tree)
+ [
+ RepoFile(path='.gitattributes', size=391, blob_id='ae8c63daedbd4206d7d40126955d4e6ab1c80f8f', lfs=None, last_commit=None, security=None),
+ RepoFile(path='README.md', size=391, blob_id='43bd404b159de6fba7c2f4d3264347668d43af25', lfs=None, last_commit=None, security=None),
+ RepoFile(path='config.json', size=554, blob_id='2f9618c3a19b9a61add74f70bfb121335aeef666', lfs=None, last_commit=None, security=None),
+ RepoFile(
+ path='flax_model.msgpack', size=497764107, blob_id='8095a62ccb4d806da7666fcda07467e2d150218e',
+ lfs={'size': 497764107, 'sha256': 'd88b0d6a6ff9c3f8151f9d3228f57092aaea997f09af009eefd7373a77b5abb9', 'pointer_size': 134}, last_commit=None, security=None
+ ),
+ RepoFile(path='merges.txt', size=456318, blob_id='226b0752cac7789c48f0cb3ec53eda48b7be36cc', lfs=None, last_commit=None, security=None),
+ RepoFile(
+ path='pytorch_model.bin', size=548123560, blob_id='64eaa9c526867e404b68f2c5d66fd78e27026523',
+ lfs={'size': 548123560, 'sha256': '9be78edb5b928eba33aa88f431551348f7466ba9f5ef3daf1d552398722a5436', 'pointer_size': 134}, last_commit=None, security=None
+ ),
+ RepoFile(path='vocab.json', size=898669, blob_id='b00361fece0387ca34b4b8b8539ed830d644dbeb', lfs=None, last_commit=None, security=None)]
+ ]
+ ```
+
+ Get even more information about a repo's tree (last commit and files' security scan results)
+ ```py
+ >>> from huggingface_hub import list_repo_tree
+ >>> repo_tree = list_repo_tree("prompthero/openjourney-v4", expand=True)
+ >>> list(repo_tree)
+ [
+ RepoFolder(
+ path='feature_extractor',
+ tree_id='aa536c4ea18073388b5b0bc791057a7296a00398',
+ last_commit={
+ 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190',
+ 'title': 'Upload diffusers weights (#48)',
+ 'date': datetime.datetime(2023, 3, 21, 9, 5, 27, tzinfo=datetime.timezone.utc)
+ }
+ ),
+ RepoFolder(
+ path='safety_checker',
+ tree_id='65aef9d787e5557373fdf714d6c34d4fcdd70440',
+ last_commit={
+ 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190',
+ 'title': 'Upload diffusers weights (#48)',
+ 'date': datetime.datetime(2023, 3, 21, 9, 5, 27, tzinfo=datetime.timezone.utc)
+ }
+ ),
+ RepoFile(
+ path='model_index.json',
+ size=582,
+ blob_id='d3d7c1e8c3e78eeb1640b8e2041ee256e24c9ee1',
+ lfs=None,
+ last_commit={
+ 'oid': 'b195ed2d503f3eb29637050a886d77bd81d35f0e',
+ 'title': 'Fix deprecation warning by changing `CLIPFeatureExtractor` to `CLIPImageProcessor`. (#54)',
+ 'date': datetime.datetime(2023, 5, 15, 21, 41, 59, tzinfo=datetime.timezone.utc)
+ },
+ security={
+ 'safe': True,
+ 'av_scan': {'virusFound': False, 'virusNames': None},
+ 'pickle_import_scan': None
+ }
+ )
+ ...
+ ]
+ ```
+ """
+ repo_type = repo_type or constants.REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else constants.DEFAULT_REVISION
+ headers = self._build_hf_headers(token=token)
+
+ encoded_path_in_repo = "/" + quote(path_in_repo, safe="") if path_in_repo else ""
+ tree_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tree/{revision}{encoded_path_in_repo}"
+ for path_info in paginate(path=tree_url, headers=headers, params={"recursive": recursive, "expand": expand}):
+ yield (RepoFile(**path_info) if path_info["type"] == "file" else RepoFolder(**path_info))
+
+ @validate_hf_hub_args
+ def list_repo_refs(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ include_pull_requests: bool = False,
+ token: Union[str, bool, None] = None,
+ ) -> GitRefs:
+ """
+ Get the list of refs of a given repo (both tags and branches).
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if listing refs from a dataset or a Space,
+ `None` or `"model"` if listing from a model. Default is `None`.
+ include_pull_requests (`bool`, *optional*):
+ Whether to include refs from pull requests in the list. Defaults to `False`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> api.list_repo_refs("gpt2")
+ GitRefs(branches=[GitRefInfo(name='main', ref='refs/heads/main', target_commit='e7da7f221d5bf496a48136c0cd264e630fe9fcc8')], converts=[], tags=[])
+
+ >>> api.list_repo_refs("bigcode/the-stack", repo_type='dataset')
+ GitRefs(
+ branches=[
+ GitRefInfo(name='main', ref='refs/heads/main', target_commit='18edc1591d9ce72aa82f56c4431b3c969b210ae3'),
+ GitRefInfo(name='v1.1.a1', ref='refs/heads/v1.1.a1', target_commit='f9826b862d1567f3822d3d25649b0d6d22ace714')
+ ],
+ converts=[],
+ tags=[
+ GitRefInfo(name='v1.0', ref='refs/tags/v1.0', target_commit='c37a8cd1e382064d8aced5e05543c5f7753834da')
+ ]
+ )
+ ```
+
+ Returns:
+ [`GitRefs`]: object containing all information about branches and tags for a
+ repo on the Hub.
+ """
+ repo_type = repo_type or constants.REPO_TYPE_MODEL
+ response = get_session().get(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/refs",
+ headers=self._build_hf_headers(token=token),
+ params={"include_prs": 1} if include_pull_requests else {},
+ )
+ hf_raise_for_status(response)
+ data = response.json()
+
+ def _format_as_git_ref_info(item: Dict) -> GitRefInfo:
+ return GitRefInfo(name=item["name"], ref=item["ref"], target_commit=item["targetCommit"])
+
+ return GitRefs(
+ branches=[_format_as_git_ref_info(item) for item in data["branches"]],
+ converts=[_format_as_git_ref_info(item) for item in data["converts"]],
+ tags=[_format_as_git_ref_info(item) for item in data["tags"]],
+ pull_requests=[_format_as_git_ref_info(item) for item in data["pullRequests"]]
+ if include_pull_requests
+ else None,
+ )
+
+ @validate_hf_hub_args
+ def list_repo_commits(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ revision: Optional[str] = None,
+ formatted: bool = False,
+ ) -> List[GitCommitInfo]:
+ """
+ Get the list of commits of a given revision for a repo on the Hub.
+
+ Commits are sorted by date (last commit first).
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if listing commits from a dataset or a Space, `None` or `"model"` if
+ listing from a model. Default is `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ formatted (`bool`):
+ Whether to return the HTML-formatted title and description of the commits. Defaults to False.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+
+ # Commits are sorted by date (last commit first)
+ >>> initial_commit = api.list_repo_commits("gpt2")[-1]
+
+ # Initial commit is always a system commit containing the `.gitattributes` file.
+ >>> initial_commit
+ GitCommitInfo(
+ commit_id='9b865efde13a30c13e0a33e536cf3e4a5a9d71d8',
+ authors=['system'],
+ created_at=datetime.datetime(2019, 2, 18, 10, 36, 15, tzinfo=datetime.timezone.utc),
+ title='initial commit',
+ message='',
+ formatted_title=None,
+ formatted_message=None
+ )
+
+ # Create an empty branch by deriving from initial commit
+ >>> api.create_branch("gpt2", "new_empty_branch", revision=initial_commit.commit_id)
+ ```
+
+ Returns:
+ List[[`GitCommitInfo`]]: list of objects containing information about the commits for a repo on the Hub.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+ """
+ repo_type = repo_type or constants.REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else constants.DEFAULT_REVISION
+
+ # Paginate over results and return the list of commits.
+ return [
+ GitCommitInfo(
+ commit_id=item["id"],
+ authors=[author["user"] for author in item["authors"]],
+ created_at=parse_datetime(item["date"]),
+ title=item["title"],
+ message=item["message"],
+ formatted_title=item.get("formatted", {}).get("title"),
+ formatted_message=item.get("formatted", {}).get("message"),
+ )
+ for item in paginate(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/commits/{revision}",
+ headers=self._build_hf_headers(token=token),
+ params={"expand[]": "formatted"} if formatted else {},
+ )
+ ]
+
+ @validate_hf_hub_args
+ def get_paths_info(
+ self,
+ repo_id: str,
+ paths: Union[List[str], str],
+ *,
+ expand: bool = False,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> List[Union[RepoFile, RepoFolder]]:
+ """
+ Get information about a repo's paths.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ paths (`Union[List[str], str]`, *optional*):
+ The paths to get information about. If a path do not exist, it is ignored without raising
+ an exception.
+ expand (`bool`, *optional*, defaults to `False`):
+ Whether to fetch more information about the paths (e.g. last commit and files' security scan results). This
+ operation is more expensive for the server so only 50 results are returned per page (instead of 1000).
+ As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it
+ takes to get the results.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the information. Defaults to `"main"` branch.
+ repo_type (`str`, *optional*):
+ The type of the repository from which to get the information (`"model"`, `"dataset"` or `"space"`.
+ Defaults to `"model"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `List[Union[RepoFile, RepoFolder]]`:
+ The information about the paths, as a list of [`RepoFile`] and [`RepoFolder`] objects.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import get_paths_info
+ >>> paths_info = get_paths_info("allenai/c4", ["README.md", "en"], repo_type="dataset")
+ >>> paths_info
+ [
+ RepoFile(path='README.md', size=2379, blob_id='f84cb4c97182890fc1dbdeaf1a6a468fd27b4fff', lfs=None, last_commit=None, security=None),
+ RepoFolder(path='en', tree_id='dc943c4c40f53d02b31ced1defa7e5f438d5862e', last_commit=None)
+ ]
+ ```
+ """
+ repo_type = repo_type or constants.REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else constants.DEFAULT_REVISION
+ headers = self._build_hf_headers(token=token)
+
+ response = get_session().post(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/paths-info/{revision}",
+ data={
+ "paths": paths if isinstance(paths, list) else [paths],
+ "expand": expand,
+ },
+ headers=headers,
+ )
+ hf_raise_for_status(response)
+ paths_info = response.json()
+ return [
+ RepoFile(**path_info) if path_info["type"] == "file" else RepoFolder(**path_info)
+ for path_info in paths_info
+ ]
+
+ @validate_hf_hub_args
+ def super_squash_history(
+ self,
+ repo_id: str,
+ *,
+ branch: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> None:
+ """Squash commit history on a branch for a repo on the Hub.
+
+ Squashing the repo history is useful when you know you'll make hundreds of commits and you don't want to
+ clutter the history. Squashing commits can only be performed from the head of a branch.
+
+
+
+ Once squashed, the commit history cannot be retrieved. This is a non-revertible operation.
+
+
+
+
+
+ Once the history of a branch has been squashed, it is not possible to merge it back into another branch since
+ their history will have diverged.
+
+
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ branch (`str`, *optional*):
+ The branch to squash. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The commit message to use for the squashed commit.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if listing commits from a dataset or a Space, `None` or `"model"` if
+ listing from a model. Default is `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If the branch to squash cannot be found.
+ [`~utils.BadRequestError`]:
+ If invalid reference for a branch. You cannot squash history on tags.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+
+ # Create repo
+ >>> repo_id = api.create_repo("test-squash").repo_id
+
+ # Make a lot of commits.
+ >>> api.upload_file(repo_id=repo_id, path_in_repo="file.txt", path_or_fileobj=b"content")
+ >>> api.upload_file(repo_id=repo_id, path_in_repo="lfs.bin", path_or_fileobj=b"content")
+ >>> api.upload_file(repo_id=repo_id, path_in_repo="file.txt", path_or_fileobj=b"another_content")
+
+ # Squash history
+ >>> api.super_squash_history(repo_id=repo_id)
+ ```
+ """
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError("Invalid repo type")
+ if branch is None:
+ branch = constants.DEFAULT_REVISION
+
+ # Prepare request
+ url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/super-squash/{quote(branch, safe='')}"
+ headers = self._build_hf_headers(token=token)
+ commit_message = commit_message or f"Super-squash branch '{branch}' using huggingface_hub"
+
+ # Super-squash
+ response = get_session().post(url=url, headers=headers, json={"message": commit_message})
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def create_repo(
+ self,
+ repo_id: str,
+ *,
+ token: Union[str, bool, None] = None,
+ private: Optional[bool] = None,
+ repo_type: Optional[str] = None,
+ exist_ok: bool = False,
+ resource_group_id: Optional[str] = None,
+ space_sdk: Optional[str] = None,
+ space_hardware: Optional[SpaceHardware] = None,
+ space_storage: Optional[SpaceStorage] = None,
+ space_sleep_time: Optional[int] = None,
+ space_secrets: Optional[List[Dict[str, str]]] = None,
+ space_variables: Optional[List[Dict[str, str]]] = None,
+ ) -> RepoUrl:
+ """Create an empty repo on the HuggingFace Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ private (`bool`, *optional*):
+ Whether to make the repo private. If `None` (default), the repo will be public unless the organization's default is private. This value is ignored if the repo already exists.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if repo already exists.
+ resource_group_id (`str`, *optional*):
+ Resource group in which to create the repo. Resource groups is only available for organizations and
+ allow to define which members of the organization can access the resource. The ID of a resource group
+ can be found in the URL of the resource's page on the Hub (e.g. `"66670e5163145ca562cb1988"`).
+ To learn more about resource groups, see https://huggingface.co/docs/hub/en/security-resource-groups.
+ space_sdk (`str`, *optional*):
+ Choice of SDK to use if repo_type is "space". Can be "streamlit", "gradio", "docker", or "static".
+ space_hardware (`SpaceHardware` or `str`, *optional*):
+ Choice of Hardware if repo_type is "space". See [`SpaceHardware`] for a complete list.
+ space_storage (`SpaceStorage` or `str`, *optional*):
+ Choice of persistent storage tier. Example: `"small"`. See [`SpaceStorage`] for a complete list.
+ space_sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ space_secrets (`List[Dict[str, str]]`, *optional*):
+ A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+ space_variables (`List[Dict[str, str]]`, *optional*):
+ A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables.
+
+ Returns:
+ [`RepoUrl`]: URL to the newly created repo. Value is a subclass of `str` containing
+ attributes like `endpoint`, `repo_type` and `repo_id`.
+ """
+ organization, name = repo_id.split("/") if "/" in repo_id else (None, repo_id)
+
+ path = f"{self.endpoint}/api/repos/create"
+
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ json: Dict[str, Any] = {"name": name, "organization": organization}
+ if private is not None:
+ json["private"] = private
+ if repo_type is not None:
+ json["type"] = repo_type
+ if repo_type == "space":
+ if space_sdk is None:
+ raise ValueError(
+ "No space_sdk provided. `create_repo` expects space_sdk to be one"
+ f" of {constants.SPACES_SDK_TYPES} when repo_type is 'space'`"
+ )
+ if space_sdk not in constants.SPACES_SDK_TYPES:
+ raise ValueError(f"Invalid space_sdk. Please choose one of {constants.SPACES_SDK_TYPES}.")
+ json["sdk"] = space_sdk
+
+ if space_sdk is not None and repo_type != "space":
+ warnings.warn("Ignoring provided space_sdk because repo_type is not 'space'.")
+
+ function_args = [
+ "space_hardware",
+ "space_storage",
+ "space_sleep_time",
+ "space_secrets",
+ "space_variables",
+ ]
+ json_keys = ["hardware", "storageTier", "sleepTimeSeconds", "secrets", "variables"]
+ values = [space_hardware, space_storage, space_sleep_time, space_secrets, space_variables]
+
+ if repo_type == "space":
+ json.update({k: v for k, v in zip(json_keys, values) if v is not None})
+ else:
+ provided_space_args = [key for key, value in zip(function_args, values) if value is not None]
+
+ if provided_space_args:
+ warnings.warn(f"Ignoring provided {', '.join(provided_space_args)} because repo_type is not 'space'.")
+
+ if getattr(self, "_lfsmultipartthresh", None):
+ # Testing purposes only.
+ # See https://github.com/huggingface/huggingface_hub/pull/733/files#r820604472
+ json["lfsmultipartthresh"] = self._lfsmultipartthresh # type: ignore
+
+ if resource_group_id is not None:
+ json["resourceGroupId"] = resource_group_id
+
+ headers = self._build_hf_headers(token=token)
+ while True:
+ r = get_session().post(path, headers=headers, json=json)
+ if r.status_code == 409 and "Cannot create repo: another conflicting operation is in progress" in r.text:
+ # Since https://github.com/huggingface/moon-landing/pull/7272 (private repo), it is not possible to
+ # concurrently create repos on the Hub for a same user. This is rarely an issue, except when running
+ # tests. To avoid any inconvenience, we retry to create the repo for this specific error.
+ # NOTE: This could have being fixed directly in the tests but adding it here should fixed CIs for all
+ # dependent libraries.
+ # NOTE: If a fix is implemented server-side, we should be able to remove this retry mechanism.
+ logger.debug("Create repo failed due to a concurrency issue. Retrying...")
+ continue
+ break
+
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exist_ok and err.response.status_code == 409:
+ # Repo already exists and `exist_ok=True`
+ pass
+ elif exist_ok and err.response.status_code == 403:
+ # No write permission on the namespace but repo might already exist
+ try:
+ self.repo_info(repo_id=repo_id, repo_type=repo_type, token=token)
+ if repo_type is None or repo_type == constants.REPO_TYPE_MODEL:
+ return RepoUrl(f"{self.endpoint}/{repo_id}")
+ return RepoUrl(f"{self.endpoint}/{repo_type}/{repo_id}")
+ except HfHubHTTPError:
+ raise err
+ else:
+ raise
+
+ d = r.json()
+ return RepoUrl(d["url"], endpoint=self.endpoint)
+
+ @validate_hf_hub_args
+ def delete_repo(
+ self,
+ repo_id: str,
+ *,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ missing_ok: bool = False,
+ ) -> None:
+ """
+ Delete a repo from the HuggingFace Hub. CAUTION: this is irreversible.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model.
+ missing_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if repo does not exist.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]
+ If the repository to delete from cannot be found and `missing_ok` is set to False (default).
+ """
+ organization, name = repo_id.split("/") if "/" in repo_id else (None, repo_id)
+
+ path = f"{self.endpoint}/api/repos/delete"
+
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ json = {"name": name, "organization": organization}
+ if repo_type is not None:
+ json["type"] = repo_type
+
+ headers = self._build_hf_headers(token=token)
+ r = get_session().delete(path, headers=headers, json=json)
+ try:
+ hf_raise_for_status(r)
+ except RepositoryNotFoundError:
+ if not missing_ok:
+ raise
+
+ @_deprecate_method(version="0.32", message="Please use `update_repo_settings` instead.")
+ @validate_hf_hub_args
+ def update_repo_visibility(
+ self,
+ repo_id: str,
+ private: bool = False,
+ *,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> Dict[str, bool]:
+ """Update the visibility setting of a repository.
+
+ Deprecated. Use `update_repo_settings` instead.
+
+ Args:
+ repo_id (`str`, *optional*):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ private (`bool`, *optional*, defaults to `False`):
+ Whether the repository should be private.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns:
+ The HTTP response in json.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL # default repo type
+
+ r = get_session().put(
+ url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/settings",
+ headers=self._build_hf_headers(token=token),
+ json={"private": private},
+ )
+ hf_raise_for_status(r)
+ return r.json()
+
+ @validate_hf_hub_args
+ def update_repo_settings(
+ self,
+ repo_id: str,
+ *,
+ gated: Optional[Literal["auto", "manual", False]] = None,
+ private: Optional[bool] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Update the settings of a repository, including gated access and visibility.
+
+ To give more control over how repos are used, the Hub allows repo authors to enable
+ access requests for their repos, and also to set the visibility of the repo to private.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a /.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ The gated status for the repository. If set to `None` (default), the `gated` setting of the repository won't be updated.
+ * "auto": The repository is gated, and access requests are automatically approved or denied based on predefined criteria.
+ * "manual": The repository is gated, and access requests require manual approval.
+ * False : The repository is not gated, and anyone can access it.
+ private (`bool`, *optional*):
+ Whether the repository should be private.
+ token (`Union[str, bool, None]`, *optional*):
+ A valid user access token (string). Defaults to the locally saved token,
+ which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass False.
+ repo_type (`str`, *optional*):
+ The type of the repository to update settings from (`"model"`, `"dataset"` or `"space"`).
+ Defaults to `"model"`.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If gated is not one of "auto", "manual", or False.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If repo_type is not one of the values in constants.REPO_TYPES.
+ [`~utils.HfHubHTTPError`]:
+ If the request to the Hugging Face Hub API fails.
+ [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ """
+
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL # default repo type
+
+ # Check if both gated and private are None
+ if gated is None and private is None:
+ raise ValueError("At least one of 'gated' or 'private' must be provided.")
+
+ # Build headers
+ headers = self._build_hf_headers(token=token)
+
+ # Prepare the JSON payload for the PUT request
+ payload: Dict = {}
+
+ if gated is not None:
+ if gated not in ["auto", "manual", False]:
+ raise ValueError(f"Invalid gated status, must be one of 'auto', 'manual', or False. Got '{gated}'.")
+ payload["gated"] = gated
+
+ if private is not None:
+ payload["private"] = private
+
+ r = get_session().put(
+ url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/settings",
+ headers=headers,
+ json=payload,
+ )
+ hf_raise_for_status(r)
+
+ def move_repo(
+ self,
+ from_id: str,
+ to_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ):
+ """
+ Moving a repository from namespace1/repo_name1 to namespace2/repo_name2
+
+ Note there are certain limitations. For more information about moving
+ repositories, please see
+ https://hf.co/docs/hub/repositories-settings#renaming-or-transferring-a-repo.
+
+ Args:
+ from_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`. Original repository identifier.
+ to_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`. Final repository identifier.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if len(from_id.split("/")) != 2:
+ raise ValueError(f"Invalid repo_id: {from_id}. It should have a namespace (:namespace:/:repo_name:)")
+
+ if len(to_id.split("/")) != 2:
+ raise ValueError(f"Invalid repo_id: {to_id}. It should have a namespace (:namespace:/:repo_name:)")
+
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL # Hub won't accept `None`.
+
+ json = {"fromRepo": from_id, "toRepo": to_id, "type": repo_type}
+
+ path = f"{self.endpoint}/api/repos/move"
+ headers = self._build_hf_headers(token=token)
+ r = get_session().post(path, headers=headers, json=json)
+ try:
+ hf_raise_for_status(r)
+ except HfHubHTTPError as e:
+ e.append_to_message(
+ "\nFor additional documentation please see"
+ " https://hf.co/docs/hub/repositories-settings#renaming-or-transferring-a-repo."
+ )
+ raise
+
+ @overload
+ def create_commit( # type: ignore
+ self,
+ repo_id: str,
+ operations: Iterable[CommitOperation],
+ *,
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[False] = ...,
+ ) -> CommitInfo: ...
+
+ @overload
+ def create_commit(
+ self,
+ repo_id: str,
+ operations: Iterable[CommitOperation],
+ *,
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[CommitInfo]: ...
+
+ @validate_hf_hub_args
+ @future_compatible
+ def create_commit(
+ self,
+ repo_id: str,
+ operations: Iterable[CommitOperation],
+ *,
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ parent_commit: Optional[str] = None,
+ run_as_future: bool = False,
+ ) -> Union[CommitInfo, Future[CommitInfo]]:
+ """
+ Creates a commit in the given repo, deleting & uploading files as needed.
+
+
+
+ The input list of `CommitOperation` will be mutated during the commit process. Do not reuse the same objects
+ for multiple commits.
+
+
+
+
+
+ `create_commit` assumes that the repo already exists on the Hub. If you get a
+ Client error 404, please make sure you are authenticated and that `repo_id` and
+ `repo_type` are set correctly. If repo does not exist, create it first using
+ [`~hf_api.create_repo`].
+
+
+
+
+
+ `create_commit` is limited to 25k LFS files and a 1GB payload for regular files.
+
+
+
+ Args:
+ repo_id (`str`):
+ The repository in which the commit will be created, for example:
+ `"username/custom_transformers"`
+
+ operations (`Iterable` of [`~hf_api.CommitOperation`]):
+ An iterable of operations to include in the commit, either:
+
+ - [`~hf_api.CommitOperationAdd`] to upload a file
+ - [`~hf_api.CommitOperationDelete`] to delete a file
+ - [`~hf_api.CommitOperationCopy`] to copy a file
+
+ Operation objects will be mutated to include information relative to the upload. Do not reuse the
+ same objects for multiple commits.
+
+ commit_message (`str`):
+ The summary (first line) of the commit that will be created.
+
+ commit_description (`str`, *optional*):
+ The description of the commit that will be created
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+
+ num_threads (`int`, *optional*):
+ Number of concurrent threads for uploading files. Defaults to 5.
+ Setting it to 2 means at most 2 files will be uploaded concurrently.
+
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string.
+ Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`,
+ the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr`
+ is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit`
+ ensures the repo has not changed before committing the changes, and can be especially useful
+ if the repo is updated / committed to concurrently.
+ run_as_future (`bool`, *optional*):
+ Whether or not to run this method in the background. Background jobs are run sequentially without
+ blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects)
+ object. Defaults to `False`.
+
+ Returns:
+ [`CommitInfo`] or `Future`:
+ Instance of [`CommitInfo`] containing information about the newly created commit (commit hash, commit
+ url, pr url, commit message,...). If `run_as_future=True` is passed, returns a Future object which will
+ contain the result when executed.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If commit message is empty.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If parent commit is not a valid commit OID.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If a README.md file with an invalid metadata section is committed. In this case, the commit will fail
+ early, before trying to upload any file.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `create_pr` is `True` and revision is neither `None` nor `"main"`.
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ """
+ if parent_commit is not None and not constants.REGEX_COMMIT_OID.fullmatch(parent_commit):
+ raise ValueError(
+ f"`parent_commit` is not a valid commit OID. It must match the following regex: {constants.REGEX_COMMIT_OID}"
+ )
+
+ if commit_message is None or len(commit_message) == 0:
+ raise ValueError("`commit_message` can't be empty, please pass a value.")
+
+ commit_description = commit_description if commit_description is not None else ""
+ repo_type = repo_type if repo_type is not None else constants.REPO_TYPE_MODEL
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ unquoted_revision = revision or constants.DEFAULT_REVISION
+ revision = quote(unquoted_revision, safe="")
+ create_pr = create_pr if create_pr is not None else False
+
+ headers = self._build_hf_headers(token=token)
+
+ operations = list(operations)
+ additions = [op for op in operations if isinstance(op, CommitOperationAdd)]
+ copies = [op for op in operations if isinstance(op, CommitOperationCopy)]
+ nb_additions = len(additions)
+ nb_copies = len(copies)
+ nb_deletions = len(operations) - nb_additions - nb_copies
+
+ for addition in additions:
+ if addition._is_committed:
+ raise ValueError(
+ f"CommitOperationAdd {addition} has already being committed and cannot be reused. Please create a"
+ " new CommitOperationAdd object if you want to create a new commit."
+ )
+
+ if repo_type != "dataset":
+ for addition in additions:
+ if addition.path_in_repo.endswith((".arrow", ".parquet")):
+ warnings.warn(
+ f"It seems that you are about to commit a data file ({addition.path_in_repo}) to a {repo_type}"
+ " repository. You are sure this is intended? If you are trying to upload a dataset, please"
+ " set `repo_type='dataset'` or `--repo-type=dataset` in a CLI."
+ )
+
+ logger.debug(
+ f"About to commit to the hub: {len(additions)} addition(s), {len(copies)} copie(s) and"
+ f" {nb_deletions} deletion(s)."
+ )
+
+ # If updating a README.md file, make sure the metadata format is valid
+ # It's better to fail early than to fail after all the files have been uploaded.
+ for addition in additions:
+ if addition.path_in_repo == "README.md":
+ with addition.as_file() as file:
+ content = file.read().decode()
+ self._validate_yaml(content, repo_type=repo_type, token=token)
+ # Skip other additions after `README.md` has been processed
+ break
+
+ # If updating twice the same file or update then delete a file in a single commit
+ _warn_on_overwriting_operations(operations)
+
+ self.preupload_lfs_files(
+ repo_id=repo_id,
+ additions=additions,
+ token=token,
+ repo_type=repo_type,
+ revision=unquoted_revision, # first-class methods take unquoted revision
+ create_pr=create_pr,
+ num_threads=num_threads,
+ free_memory=False, # do not remove `CommitOperationAdd.path_or_fileobj` on LFS files for "normal" users
+ )
+
+ files_to_copy = _fetch_files_to_copy(
+ copies=copies,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=headers,
+ revision=unquoted_revision,
+ endpoint=self.endpoint,
+ )
+ # Remove no-op operations (files that have not changed)
+ operations_without_no_op = []
+ for operation in operations:
+ if (
+ isinstance(operation, CommitOperationAdd)
+ and operation._remote_oid is not None
+ and operation._remote_oid == operation._local_oid
+ ):
+ # File already exists on the Hub and has not changed: we can skip it.
+ logger.debug(f"Skipping upload for '{operation.path_in_repo}' as the file has not changed.")
+ continue
+ if (
+ isinstance(operation, CommitOperationCopy)
+ and operation._dest_oid is not None
+ and operation._dest_oid == operation._src_oid
+ ):
+ # Source and destination files are identical - skip
+ logger.debug(
+ f"Skipping copy for '{operation.src_path_in_repo}' -> '{operation.path_in_repo}' as the content of the source file is the same as the destination file."
+ )
+ continue
+ operations_without_no_op.append(operation)
+ if len(operations) != len(operations_without_no_op):
+ logger.info(
+ f"Removing {len(operations) - len(operations_without_no_op)} file(s) from commit that have not changed."
+ )
+
+ # Return early if empty commit
+ if len(operations_without_no_op) == 0:
+ logger.warning("No files have been modified since last commit. Skipping to prevent empty commit.")
+
+ # Get latest commit info
+ try:
+ info = self.repo_info(repo_id=repo_id, repo_type=repo_type, revision=unquoted_revision, token=token)
+ except RepositoryNotFoundError as e:
+ e.append_to_message(_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE)
+ raise
+
+ # Return commit info based on latest commit
+ url_prefix = self.endpoint
+ if repo_type is not None and repo_type != constants.REPO_TYPE_MODEL:
+ url_prefix = f"{url_prefix}/{repo_type}s"
+ return CommitInfo(
+ commit_url=f"{url_prefix}/{repo_id}/commit/{info.sha}",
+ commit_message=commit_message,
+ commit_description=commit_description,
+ oid=info.sha, # type: ignore[arg-type]
+ )
+
+ commit_payload = _prepare_commit_payload(
+ operations=operations,
+ files_to_copy=files_to_copy,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ parent_commit=parent_commit,
+ )
+ commit_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/commit/{revision}"
+
+ def _payload_as_ndjson() -> Iterable[bytes]:
+ for item in commit_payload:
+ yield json.dumps(item).encode()
+ yield b"\n"
+
+ headers = {
+ # See https://github.com/huggingface/huggingface_hub/issues/1085#issuecomment-1265208073
+ "Content-Type": "application/x-ndjson",
+ **headers,
+ }
+ data = b"".join(_payload_as_ndjson())
+ params = {"create_pr": "1"} if create_pr else None
+
+ try:
+ commit_resp = get_session().post(url=commit_url, headers=headers, data=data, params=params)
+ hf_raise_for_status(commit_resp, endpoint_name="commit")
+ except RepositoryNotFoundError as e:
+ e.append_to_message(_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE)
+ raise
+ except EntryNotFoundError as e:
+ if nb_deletions > 0 and "A file with this name doesn't exist" in str(e):
+ e.append_to_message(
+ "\nMake sure to differentiate file and folder paths in delete"
+ " operations with a trailing '/' or using `is_folder=True/False`."
+ )
+ raise
+
+ # Mark additions as committed (cannot be reused in another commit)
+ for addition in additions:
+ addition._is_committed = True
+
+ commit_data = commit_resp.json()
+ return CommitInfo(
+ commit_url=commit_data["commitUrl"],
+ commit_message=commit_message,
+ commit_description=commit_description,
+ oid=commit_data["commitOid"],
+ pr_url=commit_data["pullRequestUrl"] if create_pr else None,
+ )
+
+ def preupload_lfs_files(
+ self,
+ repo_id: str,
+ additions: Iterable[CommitOperationAdd],
+ *,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ free_memory: bool = True,
+ gitignore_content: Optional[str] = None,
+ ):
+ """Pre-upload LFS files to S3 in preparation on a future commit.
+
+ This method is useful if you are generating the files to upload on-the-fly and you don't want to store them
+ in memory before uploading them all at once.
+
+
+
+ This is a power-user method. You shouldn't need to call it directly to make a normal commit.
+ Use [`create_commit`] directly instead.
+
+
+
+
+
+ Commit operations will be mutated during the process. In particular, the attached `path_or_fileobj` will be
+ removed after the upload to save memory (and replaced by an empty `bytes` object). Do not reuse the same
+ objects except to pass them to [`create_commit`]. If you don't want to remove the attached content from the
+ commit operation object, pass `free_memory=False`.
+
+
+
+ Args:
+ repo_id (`str`):
+ The repository in which you will commit the files, for example: `"username/custom_transformers"`.
+
+ operations (`Iterable` of [`CommitOperationAdd`]):
+ The list of files to upload. Warning: the objects in this list will be mutated to include information
+ relative to the upload. Do not reuse the same objects for multiple commits.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ The type of repository to upload to (e.g. `"model"` -default-, `"dataset"` or `"space"`).
+
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+
+ create_pr (`boolean`, *optional*):
+ Whether or not you plan to create a Pull Request with that commit. Defaults to `False`.
+
+ num_threads (`int`, *optional*):
+ Number of concurrent threads for uploading files. Defaults to 5.
+ Setting it to 2 means at most 2 files will be uploaded concurrently.
+
+ gitignore_content (`str`, *optional*):
+ The content of the `.gitignore` file to know which files should be ignored. The order of priority
+ is to first check if `gitignore_content` is passed, then check if the `.gitignore` file is present
+ in the list of files to commit and finally default to the `.gitignore` file already hosted on the Hub
+ (if any).
+
+ Example:
+ ```py
+ >>> from huggingface_hub import CommitOperationAdd, preupload_lfs_files, create_commit, create_repo
+
+ >>> repo_id = create_repo("test_preupload").repo_id
+
+ # Generate and preupload LFS files one by one
+ >>> operations = [] # List of all `CommitOperationAdd` objects that will be generated
+ >>> for i in range(5):
+ ... content = ... # generate binary content
+ ... addition = CommitOperationAdd(path_in_repo=f"shard_{i}_of_5.bin", path_or_fileobj=content)
+ ... preupload_lfs_files(repo_id, additions=[addition]) # upload + free memory
+ ... operations.append(addition)
+
+ # Create commit
+ >>> create_commit(repo_id, operations=operations, commit_message="Commit all shards")
+ ```
+ """
+ repo_type = repo_type if repo_type is not None else constants.REPO_TYPE_MODEL
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ revision = quote(revision, safe="") if revision is not None else constants.DEFAULT_REVISION
+ create_pr = create_pr if create_pr is not None else False
+ headers = self._build_hf_headers(token=token)
+
+ # Check if a `gitignore` file is being committed to the Hub.
+ additions = list(additions)
+ if gitignore_content is None:
+ for addition in additions:
+ if addition.path_in_repo == ".gitignore":
+ with addition.as_file() as f:
+ gitignore_content = f.read().decode()
+ break
+
+ # Filter out already uploaded files
+ new_additions = [addition for addition in additions if not addition._is_uploaded]
+
+ # Check which new files are LFS
+ try:
+ _fetch_upload_modes(
+ additions=new_additions,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=headers,
+ revision=revision,
+ endpoint=self.endpoint,
+ create_pr=create_pr or False,
+ gitignore_content=gitignore_content,
+ )
+ except RepositoryNotFoundError as e:
+ e.append_to_message(_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE)
+ raise
+
+ # Filter out regular files
+ new_lfs_additions = [addition for addition in new_additions if addition._upload_mode == "lfs"]
+
+ # Filter out files listed in .gitignore
+ new_lfs_additions_to_upload = []
+ for addition in new_lfs_additions:
+ if addition._should_ignore:
+ logger.debug(f"Skipping upload for LFS file '{addition.path_in_repo}' (ignored by gitignore file).")
+ else:
+ new_lfs_additions_to_upload.append(addition)
+ if len(new_lfs_additions) != len(new_lfs_additions_to_upload):
+ logger.info(
+ f"Skipped upload for {len(new_lfs_additions) - len(new_lfs_additions_to_upload)} LFS file(s) "
+ "(ignored by gitignore file)."
+ )
+
+ # Upload new LFS files
+ _upload_lfs_files(
+ additions=new_lfs_additions_to_upload,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=headers,
+ endpoint=self.endpoint,
+ num_threads=num_threads,
+ # If `create_pr`, we don't want to check user permission on the revision as users with read permission
+ # should still be able to create PRs even if they don't have write permission on the target branch of the
+ # PR (i.e. `revision`).
+ revision=revision if not create_pr else None,
+ )
+ for addition in new_lfs_additions_to_upload:
+ addition._is_uploaded = True
+ if free_memory:
+ addition.path_or_fileobj = b""
+
+ @overload
+ def upload_file( # type: ignore
+ self,
+ *,
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO],
+ path_in_repo: str,
+ repo_id: str,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[False] = ...,
+ ) -> CommitInfo: ...
+
+ @overload
+ def upload_file(
+ self,
+ *,
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO],
+ path_in_repo: str,
+ repo_id: str,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[CommitInfo]: ...
+
+ @validate_hf_hub_args
+ @future_compatible
+ def upload_file(
+ self,
+ *,
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO],
+ path_in_repo: str,
+ repo_id: str,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ run_as_future: bool = False,
+ ) -> Union[CommitInfo, Future[CommitInfo]]:
+ """
+ Upload a local file (up to 50 GB) to the given repo. The upload is done
+ through a HTTP post request, and doesn't require git or git-lfs to be
+ installed.
+
+ Args:
+ path_or_fileobj (`str`, `Path`, `bytes`, or `IO`):
+ Path to a file on the local machine or binary data stream /
+ fileobj / buffer.
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example:
+ `"checkpoints/1fec34a/weights.bin"`
+ repo_id (`str`):
+ The repository to which the file will be uploaded, for example:
+ `"username/custom_transformers"`
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit
+ commit_description (`str` *optional*)
+ The description of the generated commit
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ run_as_future (`bool`, *optional*):
+ Whether or not to run this method in the background. Background jobs are run sequentially without
+ blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects)
+ object. Defaults to `False`.
+
+
+ Returns:
+ [`CommitInfo`] or `Future`:
+ Instance of [`CommitInfo`] containing information about the newly created commit (commit hash, commit
+ url, pr url, commit message,...). If `run_as_future=True` is passed, returns a Future object which will
+ contain the result when executed.
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+
+
+
+ `upload_file` assumes that the repo already exists on the Hub. If you get a
+ Client error 404, please make sure you are authenticated and that `repo_id` and
+ `repo_type` are set correctly. If repo does not exist, create it first using
+ [`~hf_api.create_repo`].
+
+
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import upload_file
+
+ >>> with open("./local/filepath", "rb") as fobj:
+ ... upload_file(
+ ... path_or_fileobj=fileobj,
+ ... path_in_repo="remote/file/path.h5",
+ ... repo_id="username/my-dataset",
+ ... repo_type="dataset",
+ ... token="my_token",
+ ... )
+ "https://huggingface.co/datasets/username/my-dataset/blob/main/remote/file/path.h5"
+
+ >>> upload_file(
+ ... path_or_fileobj=".\\\\local\\\\file\\\\path",
+ ... path_in_repo="remote/file/path.h5",
+ ... repo_id="username/my-model",
+ ... token="my_token",
+ ... )
+ "https://huggingface.co/username/my-model/blob/main/remote/file/path.h5"
+
+ >>> upload_file(
+ ... path_or_fileobj=".\\\\local\\\\file\\\\path",
+ ... path_in_repo="remote/file/path.h5",
+ ... repo_id="username/my-model",
+ ... token="my_token",
+ ... create_pr=True,
+ ... )
+ "https://huggingface.co/username/my-model/blob/refs%2Fpr%2F1/remote/file/path.h5"
+ ```
+ """
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+
+ commit_message = (
+ commit_message if commit_message is not None else f"Upload {path_in_repo} with huggingface_hub"
+ )
+ operation = CommitOperationAdd(
+ path_or_fileobj=path_or_fileobj,
+ path_in_repo=path_in_repo,
+ )
+
+ commit_info = self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ operations=[operation],
+ commit_message=commit_message,
+ commit_description=commit_description,
+ token=token,
+ revision=revision,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ if commit_info.pr_url is not None:
+ revision = quote(_parse_revision_from_pr_url(commit_info.pr_url), safe="")
+ if repo_type in constants.REPO_TYPES_URL_PREFIXES:
+ repo_id = constants.REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+ revision = revision if revision is not None else constants.DEFAULT_REVISION
+
+ return CommitInfo(
+ commit_url=commit_info.commit_url,
+ commit_message=commit_info.commit_message,
+ commit_description=commit_info.commit_description,
+ oid=commit_info.oid,
+ pr_url=commit_info.pr_url,
+ # Similar to `hf_hub_url` but it's "blob" instead of "resolve"
+ # TODO: remove this in v1.0
+ _url=f"{self.endpoint}/{repo_id}/blob/{revision}/{path_in_repo}",
+ )
+
+ @overload
+ def upload_folder( # type: ignore
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ run_as_future: Literal[False] = ...,
+ ) -> CommitInfo: ...
+
+ @overload
+ def upload_folder( # type: ignore
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[CommitInfo]: ...
+
+ @validate_hf_hub_args
+ @future_compatible
+ def upload_folder(
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ run_as_future: bool = False,
+ ) -> Union[CommitInfo, Future[CommitInfo]]:
+ """
+ Upload a local folder to the given repo. The upload is done through a HTTP requests, and doesn't require git or
+ git-lfs to be installed.
+
+ The structure of the folder will be preserved. Files with the same name already present in the repository will
+ be overwritten. Others will be left untouched.
+
+ Use the `allow_patterns` and `ignore_patterns` arguments to specify which files to upload. These parameters
+ accept either a single pattern or a list of patterns. Patterns are Standard Wildcards (globbing patterns) as
+ documented [here](https://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm). If both `allow_patterns` and
+ `ignore_patterns` are provided, both constraints apply. By default, all files from the folder are uploaded.
+
+ Use the `delete_patterns` argument to specify remote files you want to delete. Input type is the same as for
+ `allow_patterns` (see above). If `path_in_repo` is also provided, the patterns are matched against paths
+ relative to this folder. For example, `upload_folder(..., path_in_repo="experiment", delete_patterns="logs/*")`
+ will delete any remote file under `./experiment/logs/`. Note that the `.gitattributes` file will not be deleted
+ even if it matches the patterns.
+
+ Any `.git/` folder present in any subdirectory will be ignored. However, please be aware that the `.gitignore`
+ file is not taken into account.
+
+ Uses `HfApi.create_commit` under the hood.
+
+ Args:
+ repo_id (`str`):
+ The repository to which the file will be uploaded, for example:
+ `"username/custom_transformers"`
+ folder_path (`str` or `Path`):
+ Path to the folder to upload on the local file system
+ path_in_repo (`str`, *optional*):
+ Relative path of the directory in the repo, for example:
+ `"checkpoints/1fec34a/results"`. Will default to the root folder of the repository.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to:
+ `f"Upload {path_in_repo} with huggingface_hub"`
+ commit_description (`str` *optional*):
+ The description of the generated commit
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not
+ set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened
+ against this branch. If `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are uploaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not uploaded.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo while committing
+ new files. This is useful if you don't know which files have already been uploaded.
+ Note: to avoid discrepancies the `.gitattributes` file is not deleted even if it matches the pattern.
+ run_as_future (`bool`, *optional*):
+ Whether or not to run this method in the background. Background jobs are run sequentially without
+ blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects)
+ object. Defaults to `False`.
+
+ Returns:
+ [`CommitInfo`] or `Future`:
+ Instance of [`CommitInfo`] containing information about the newly created commit (commit hash, commit
+ url, pr url, commit message,...). If `run_as_future=True` is passed, returns a Future object which will
+ contain the result when executed.
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+
+
+
+
+
+ `upload_folder` assumes that the repo already exists on the Hub. If you get a Client error 404, please make
+ sure you are authenticated and that `repo_id` and `repo_type` are set correctly. If repo does not exist, create
+ it first using [`~hf_api.create_repo`].
+
+
+
+
+
+ When dealing with a large folder (thousands of files or hundreds of GB), we recommend using [`~hf_api.upload_large_folder`] instead.
+
+
+
+ Example:
+
+ ```python
+ # Upload checkpoints folder except the log files
+ >>> upload_folder(
+ ... folder_path="local/checkpoints",
+ ... path_in_repo="remote/experiment/checkpoints",
+ ... repo_id="username/my-dataset",
+ ... repo_type="datasets",
+ ... token="my_token",
+ ... ignore_patterns="**/logs/*.txt",
+ ... )
+ # "https://huggingface.co/datasets/username/my-dataset/tree/main/remote/experiment/checkpoints"
+
+ # Upload checkpoints folder including logs while deleting existing logs from the repo
+ # Useful if you don't know exactly which log files have already being pushed
+ >>> upload_folder(
+ ... folder_path="local/checkpoints",
+ ... path_in_repo="remote/experiment/checkpoints",
+ ... repo_id="username/my-dataset",
+ ... repo_type="datasets",
+ ... token="my_token",
+ ... delete_patterns="**/logs/*.txt",
+ ... )
+ "https://huggingface.co/datasets/username/my-dataset/tree/main/remote/experiment/checkpoints"
+
+ # Upload checkpoints folder while creating a PR
+ >>> upload_folder(
+ ... folder_path="local/checkpoints",
+ ... path_in_repo="remote/experiment/checkpoints",
+ ... repo_id="username/my-dataset",
+ ... repo_type="datasets",
+ ... token="my_token",
+ ... create_pr=True,
+ ... )
+ "https://huggingface.co/datasets/username/my-dataset/tree/refs%2Fpr%2F1/remote/experiment/checkpoints"
+
+ ```
+ """
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+
+ # By default, upload folder to the root directory in repo.
+ if path_in_repo is None:
+ path_in_repo = ""
+
+ # Do not upload .git folder
+ if ignore_patterns is None:
+ ignore_patterns = []
+ elif isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+ ignore_patterns += DEFAULT_IGNORE_PATTERNS
+
+ delete_operations = self._prepare_folder_deletions(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=constants.DEFAULT_REVISION if create_pr else revision,
+ token=token,
+ path_in_repo=path_in_repo,
+ delete_patterns=delete_patterns,
+ )
+ add_operations = self._prepare_upload_folder_additions(
+ folder_path,
+ path_in_repo,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ token=token,
+ repo_type=repo_type,
+ )
+
+ # Optimize operations: if some files will be overwritten, we don't need to delete them first
+ if len(add_operations) > 0:
+ added_paths = set(op.path_in_repo for op in add_operations)
+ delete_operations = [
+ delete_op for delete_op in delete_operations if delete_op.path_in_repo not in added_paths
+ ]
+ commit_operations = delete_operations + add_operations
+
+ commit_message = commit_message or "Upload folder using huggingface_hub"
+
+ commit_info = self.create_commit(
+ repo_type=repo_type,
+ repo_id=repo_id,
+ operations=commit_operations,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ token=token,
+ revision=revision,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ # Create url to uploaded folder (for legacy return value)
+ if create_pr and commit_info.pr_url is not None:
+ revision = quote(_parse_revision_from_pr_url(commit_info.pr_url), safe="")
+ if repo_type in constants.REPO_TYPES_URL_PREFIXES:
+ repo_id = constants.REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+ revision = revision if revision is not None else constants.DEFAULT_REVISION
+
+ return CommitInfo(
+ commit_url=commit_info.commit_url,
+ commit_message=commit_info.commit_message,
+ commit_description=commit_info.commit_description,
+ oid=commit_info.oid,
+ pr_url=commit_info.pr_url,
+ # Similar to `hf_hub_url` but it's "tree" instead of "resolve"
+ # TODO: remove this in v1.0
+ _url=f"{self.endpoint}/{repo_id}/tree/{revision}/{path_in_repo}",
+ )
+
+ @validate_hf_hub_args
+ def delete_file(
+ self,
+ path_in_repo: str,
+ repo_id: str,
+ *,
+ token: Union[str, bool, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ) -> CommitInfo:
+ """
+ Deletes a file in the given repo.
+
+ Args:
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example:
+ `"checkpoints/1fec34a/weights.bin"`
+ repo_id (`str`):
+ The repository from which the file will be deleted, for example:
+ `"username/custom_transformers"`
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the file is in a dataset or
+ space, `None` or `"model"` if in a model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to
+ `f"Delete {path_in_repo} with huggingface_hub"`.
+ commit_description (`str` *optional*)
+ The description of the generated commit
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ - [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+
+
+
+ """
+ commit_message = (
+ commit_message if commit_message is not None else f"Delete {path_in_repo} with huggingface_hub"
+ )
+
+ operations = [CommitOperationDelete(path_in_repo=path_in_repo)]
+
+ return self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ operations=operations,
+ revision=revision,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ @validate_hf_hub_args
+ def delete_files(
+ self,
+ repo_id: str,
+ delete_patterns: List[str],
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ) -> CommitInfo:
+ """
+ Delete files from a repository on the Hub.
+
+ If a folder path is provided, the entire folder is deleted as well as
+ all files it contained.
+
+ Args:
+ repo_id (`str`):
+ The repository from which the folder will be deleted, for example:
+ `"username/custom_transformers"`
+ delete_patterns (`List[str]`):
+ List of files or folders to delete. Each string can either be
+ a file path, a folder path or a Unix shell-style wildcard.
+ E.g. `["file.txt", "folder/", "data/*.parquet"]`
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ to the stored token.
+ repo_type (`str`, *optional*):
+ Type of the repo to delete files from. Can be `"model"`,
+ `"dataset"` or `"space"`. Defaults to `"model"`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary (first line) of the generated commit. Defaults to
+ `f"Delete files using huggingface_hub"`.
+ commit_description (`str` *optional*)
+ The description of the generated commit.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ """
+ operations = self._prepare_folder_deletions(
+ repo_id=repo_id, repo_type=repo_type, delete_patterns=delete_patterns, path_in_repo="", revision=revision
+ )
+
+ if commit_message is None:
+ commit_message = f"Delete files {' '.join(delete_patterns)} with huggingface_hub"
+
+ return self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ operations=operations,
+ revision=revision,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ @validate_hf_hub_args
+ def delete_folder(
+ self,
+ path_in_repo: str,
+ repo_id: str,
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ) -> CommitInfo:
+ """
+ Deletes a folder in the given repo.
+
+ Simple wrapper around [`create_commit`] method.
+
+ Args:
+ path_in_repo (`str`):
+ Relative folder path in the repo, for example: `"checkpoints/1fec34a"`.
+ repo_id (`str`):
+ The repository from which the folder will be deleted, for example:
+ `"username/custom_transformers"`
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ to the stored token.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the folder is in a dataset or
+ space, `None` or `"model"` if in a model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to
+ `f"Delete folder {path_in_repo} with huggingface_hub"`.
+ commit_description (`str` *optional*)
+ The description of the generated commit.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ """
+ return self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ operations=[CommitOperationDelete(path_in_repo=path_in_repo, is_folder=True)],
+ revision=revision,
+ commit_message=(
+ commit_message if commit_message is not None else f"Delete folder {path_in_repo} with huggingface_hub"
+ ),
+ commit_description=commit_description,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ def upload_large_folder(
+ self,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ *,
+ repo_type: str, # Repo type is required!
+ revision: Optional[str] = None,
+ private: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ num_workers: Optional[int] = None,
+ print_report: bool = True,
+ print_report_every: int = 60,
+ ) -> None:
+ """Upload a large folder to the Hub in the most resilient way possible.
+
+ Several workers are started to upload files in an optimized way. Before being committed to a repo, files must be
+ hashed and be pre-uploaded if they are LFS files. Workers will perform these tasks for each file in the folder.
+ At each step, some metadata information about the upload process is saved in the folder under `.cache/.huggingface/`
+ to be able to resume the process if interrupted. The whole process might result in several commits.
+
+ Args:
+ repo_id (`str`):
+ The repository to which the file will be uploaded.
+ E.g. `"HuggingFaceTB/smollm-corpus"`.
+ folder_path (`str` or `Path`):
+ Path to the folder to upload on the local file system.
+ repo_type (`str`):
+ Type of the repository. Must be one of `"model"`, `"dataset"` or `"space"`.
+ Unlike in all other `HfApi` methods, `repo_type` is explicitly required here. This is to avoid
+ any mistake when uploading a large folder to the Hub, and therefore prevent from having to re-upload
+ everything.
+ revision (`str`, `optional`):
+ The branch to commit to. If not provided, the `main` branch will be used.
+ private (`bool`, `optional`):
+ Whether the repository should be private.
+ If `None` (default), the repo will be public unless the organization's default is private.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are uploaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not uploaded.
+ num_workers (`int`, *optional*):
+ Number of workers to start. Defaults to `os.cpu_count() - 2` (minimum 2).
+ A higher number of workers may speed up the process if your machine allows it. However, on machines with a
+ slower connection, it is recommended to keep the number of workers low to ensure better resumability.
+ Indeed, partially uploaded files will have to be completely re-uploaded if the process is interrupted.
+ print_report (`bool`, *optional*):
+ Whether to print a report of the upload progress. Defaults to True.
+ Report is printed to `sys.stdout` every X seconds (60 by defaults) and overwrites the previous report.
+ print_report_every (`int`, *optional*):
+ Frequency at which the report is printed. Defaults to 60 seconds.
+
+
+
+ A few things to keep in mind:
+ - Repository limits still apply: https://huggingface.co/docs/hub/repositories-recommendations
+ - Do not start several processes in parallel.
+ - You can interrupt and resume the process at any time.
+ - Do not upload the same folder to several repositories. If you need to do so, you must delete the local `.cache/.huggingface/` folder first.
+
+
+
+
+
+ While being much more robust to upload large folders, `upload_large_folder` is more limited than [`upload_folder`] feature-wise. In practice:
+ - you cannot set a custom `path_in_repo`. If you want to upload to a subfolder, you need to set the proper structure locally.
+ - you cannot set a custom `commit_message` and `commit_description` since multiple commits are created.
+ - you cannot delete from the repo while uploading. Please make a separate commit first.
+ - you cannot create a PR directly. Please create a PR first (from the UI or using [`create_pull_request`]) and then commit to it by passing `revision`.
+
+
+
+ **Technical details:**
+
+ `upload_large_folder` process is as follow:
+ 1. (Check parameters and setup.)
+ 2. Create repo if missing.
+ 3. List local files to upload.
+ 4. Start workers. Workers can perform the following tasks:
+ - Hash a file.
+ - Get upload mode (regular or LFS) for a list of files.
+ - Pre-upload an LFS file.
+ - Commit a bunch of files.
+ Once a worker finishes a task, it will move on to the next task based on the priority list (see below) until
+ all files are uploaded and committed.
+ 5. While workers are up, regularly print a report to sys.stdout.
+
+ Order of priority:
+ 1. Commit if more than 5 minutes since last commit attempt (and at least 1 file).
+ 2. Commit if at least 150 files are ready to commit.
+ 3. Get upload mode if at least 10 files have been hashed.
+ 4. Pre-upload LFS file if at least 1 file and no worker is pre-uploading.
+ 5. Hash file if at least 1 file and no worker is hashing.
+ 6. Get upload mode if at least 1 file and no worker is getting upload mode.
+ 7. Pre-upload LFS file if at least 1 file (exception: if hf_transfer is enabled, only 1 worker can preupload LFS at a time).
+ 8. Hash file if at least 1 file to hash.
+ 9. Get upload mode if at least 1 file to get upload mode.
+ 10. Commit if at least 1 file to commit and at least 1 min since last commit attempt.
+ 11. Commit if at least 1 file to commit and all other queues are empty.
+
+ Special rules:
+ - If `hf_transfer` is enabled, only 1 LFS uploader at a time. Otherwise the CPU would be bloated by `hf_transfer`.
+ - Only one worker can commit at a time.
+ - If no tasks are available, the worker waits for 10 seconds before checking again.
+ """
+ return upload_large_folder_internal(
+ self,
+ repo_id=repo_id,
+ folder_path=folder_path,
+ repo_type=repo_type,
+ revision=revision,
+ private=private,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ num_workers=num_workers,
+ print_report=print_report,
+ print_report_every=print_report_every,
+ )
+
+ @validate_hf_hub_args
+ def get_hf_file_metadata(
+ self,
+ *,
+ url: str,
+ token: Union[bool, str, None] = None,
+ proxies: Optional[Dict] = None,
+ timeout: Optional[float] = constants.DEFAULT_REQUEST_TIMEOUT,
+ ) -> HfFileMetadata:
+ """Fetch metadata of a file versioned on the Hub for a given url.
+
+ Args:
+ url (`str`):
+ File url, for example returned by [`hf_hub_url`].
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to `requests.request`.
+ timeout (`float`, *optional*, defaults to 10):
+ How many seconds to wait for the server to send metadata before giving up.
+
+ Returns:
+ A [`HfFileMetadata`] object containing metadata such as location, etag, size and commit_hash.
+ """
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+
+ return get_hf_file_metadata(
+ url=url,
+ token=token,
+ proxies=proxies,
+ timeout=timeout,
+ library_name=self.library_name,
+ library_version=self.library_version,
+ user_agent=self.user_agent,
+ )
+
+ @validate_hf_hub_args
+ def hf_hub_download(
+ self,
+ repo_id: str,
+ filename: str,
+ *,
+ subfolder: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ force_download: bool = False,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
+ token: Union[bool, str, None] = None,
+ local_files_only: bool = False,
+ # Deprecated args
+ resume_download: Optional[bool] = None,
+ force_filename: Optional[str] = None,
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ ) -> str:
+ """Download a given file if it's not already present in the local cache.
+
+ The new cache file layout looks like this:
+ - The cache directory contains one subfolder per repo_id (namespaced by repo type)
+ - inside each repo folder:
+ - refs is a list of the latest known revision => commit_hash pairs
+ - blobs contains the actual file blobs (identified by their git-sha or sha256, depending on
+ whether they're LFS files or not)
+ - snapshots contains one subfolder per commit, each "commit" contains the subset of the files
+ that have been resolved at that particular commit. Each filename is a symlink to the blob
+ at that particular commit.
+
+ ```
+ [ 96] .
+ └── [ 160] models--julien-c--EsperBERTo-small
+ ├── [ 160] blobs
+ │ ├── [321M] 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ │ ├── [ 398] 7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ │ └── [1.4K] d7edf6bd2a681fb0175f7735299831ee1b22b812
+ ├── [ 96] refs
+ │ └── [ 40] main
+ └── [ 128] snapshots
+ ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
+ │ ├── [ 52] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
+ │ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ └── [ 128] bbc77c8132af1cc5cf678da3f1ddf2de43606d48
+ ├── [ 52] README.md -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ ```
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this
+ option, the `cache_dir` will not be used and a `.cache/huggingface/` folder will be created at the root of `local_dir`
+ to store some metadata related to the downloaded files. While this mechanism is not as robust as the main
+ cache-system, it's optimized for regularly pulling the latest version of a repository.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ subfolder (`str`, *optional*):
+ An optional value corresponding to a folder inside the repository.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded file will be placed under this directory.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in
+ the local cache.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+
+ Returns:
+ `str`: Local path of file or if networking is off, last version of file cached on disk.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+ [`~utils.LocalEntryNotFoundError`]
+ If network is disabled or unavailable and file is not found in cache.
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `token=True` but the token cannot be found.
+ [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
+ If ETag cannot be determined.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If some parameter value is invalid.
+ """
+ from .file_download import hf_hub_download
+
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+
+ return hf_hub_download(
+ repo_id=repo_id,
+ filename=filename,
+ subfolder=subfolder,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=self.endpoint,
+ library_name=self.library_name,
+ library_version=self.library_version,
+ cache_dir=cache_dir,
+ local_dir=local_dir,
+ local_dir_use_symlinks=local_dir_use_symlinks,
+ user_agent=self.user_agent,
+ force_download=force_download,
+ force_filename=force_filename,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ token=token,
+ headers=self.headers,
+ local_files_only=local_files_only,
+ )
+
+ @validate_hf_hub_args
+ def snapshot_download(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = constants.DEFAULT_ETAG_TIMEOUT,
+ force_download: bool = False,
+ token: Union[bool, str, None] = None,
+ local_files_only: bool = False,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ max_workers: int = 8,
+ tqdm_class: Optional[base_tqdm] = None,
+ # Deprecated args
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ resume_download: Optional[bool] = None,
+ ) -> str:
+ """Download repo files.
+
+ Download a whole snapshot of a repo's files at the specified revision. This is useful when you want all files from
+ a repo, because you don't know which ones you will need a priori. All files are nested inside a folder in order
+ to keep their actual filename relative to that folder. You can also filter which files to download using
+ `allow_patterns` and `ignore_patterns`.
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this
+ option, the `cache_dir` will not be used and a `.cache/huggingface/` folder will be created at the root of `local_dir`
+ to store some metadata related to the downloaded files.While this mechanism is not as robust as the main
+ cache-system, it's optimized for regularly pulling the latest version of a repository.
+
+ An alternative would be to clone the repo but this requires git and git-lfs to be installed and properly
+ configured. It is also not possible to filter which files to download when cloning a repository using git.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded files will be placed under this directory.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in the local cache.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are downloaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not downloaded.
+ max_workers (`int`, *optional*):
+ Number of concurrent threads to download files (1 thread = 1 file download).
+ Defaults to 8.
+ tqdm_class (`tqdm`, *optional*):
+ If provided, overwrites the default behavior for the progress bar. Passed
+ argument must inherit from `tqdm.auto.tqdm` or at least mimic its behavior.
+ Note that the `tqdm_class` is not passed to each individual download.
+ Defaults to the custom HF progress bar that can be disabled by setting
+ `HF_HUB_DISABLE_PROGRESS_BARS` environment variable.
+
+ Returns:
+ `str`: folder path of the repo snapshot.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `token=True` and the token cannot be found.
+ [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) if
+ ETag cannot be determined.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid.
+ """
+ from ._snapshot_download import snapshot_download
+
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+
+ return snapshot_download(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=self.endpoint,
+ cache_dir=cache_dir,
+ local_dir=local_dir,
+ local_dir_use_symlinks=local_dir_use_symlinks,
+ library_name=self.library_name,
+ library_version=self.library_version,
+ user_agent=self.user_agent,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ force_download=force_download,
+ token=token,
+ local_files_only=local_files_only,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ max_workers=max_workers,
+ tqdm_class=tqdm_class,
+ )
+
+ def get_safetensors_metadata(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> SafetensorsRepoMetadata:
+ """
+ Parse metadata for a safetensors repo on the Hub.
+
+ We first check if the repo has a single safetensors file or a sharded safetensors repo. If it's a single
+ safetensors file, we parse the metadata from this file. If it's a sharded safetensors repo, we parse the
+ metadata from the index file and then parse the metadata from each shard.
+
+ To parse metadata from a single safetensors file, use [`parse_safetensors_file_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a
+ model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to fetch the file from. Can be a branch name, a tag, or a commit hash. Defaults to the
+ head of the `"main"` branch.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`SafetensorsRepoMetadata`]: information related to safetensors repo.
+
+ Raises:
+ [`NotASafetensorsRepoError`]
+ If the repo is not a safetensors repo i.e. doesn't have either a
+ `model.safetensors` or a `model.safetensors.index.json` file.
+ [`SafetensorsParsingError`]
+ If a safetensors file header couldn't be parsed correctly.
+
+ Example:
+ ```py
+ # Parse repo with single weights file
+ >>> metadata = get_safetensors_metadata("bigscience/bloomz-560m")
+ >>> metadata
+ SafetensorsRepoMetadata(
+ metadata=None,
+ sharded=False,
+ weight_map={'h.0.input_layernorm.bias': 'model.safetensors', ...},
+ files_metadata={'model.safetensors': SafetensorsFileMetadata(...)}
+ )
+ >>> metadata.files_metadata["model.safetensors"].metadata
+ {'format': 'pt'}
+
+ # Parse repo with sharded model
+ >>> metadata = get_safetensors_metadata("bigscience/bloom")
+ Parse safetensors files: 100%|██████████████████████████████████████████| 72/72 [00:12<00:00, 5.78it/s]
+ >>> metadata
+ SafetensorsRepoMetadata(metadata={'total_size': 352494542848}, sharded=True, weight_map={...}, files_metadata={...})
+ >>> len(metadata.files_metadata)
+ 72 # All safetensors files have been fetched
+
+ # Parse repo with sharded model
+ >>> get_safetensors_metadata("runwayml/stable-diffusion-v1-5")
+ NotASafetensorsRepoError: 'runwayml/stable-diffusion-v1-5' is not a safetensors repo. Couldn't find 'model.safetensors.index.json' or 'model.safetensors' files.
+ ```
+ """
+ if self.file_exists( # Single safetensors file => non-sharded model
+ repo_id=repo_id,
+ filename=constants.SAFETENSORS_SINGLE_FILE,
+ repo_type=repo_type,
+ revision=revision,
+ token=token,
+ ):
+ file_metadata = self.parse_safetensors_file_metadata(
+ repo_id=repo_id,
+ filename=constants.SAFETENSORS_SINGLE_FILE,
+ repo_type=repo_type,
+ revision=revision,
+ token=token,
+ )
+ return SafetensorsRepoMetadata(
+ metadata=None,
+ sharded=False,
+ weight_map={
+ tensor_name: constants.SAFETENSORS_SINGLE_FILE for tensor_name in file_metadata.tensors.keys()
+ },
+ files_metadata={constants.SAFETENSORS_SINGLE_FILE: file_metadata},
+ )
+ elif self.file_exists( # Multiple safetensors files => sharded with index
+ repo_id=repo_id,
+ filename=constants.SAFETENSORS_INDEX_FILE,
+ repo_type=repo_type,
+ revision=revision,
+ token=token,
+ ):
+ # Fetch index
+ index_file = self.hf_hub_download(
+ repo_id=repo_id,
+ filename=constants.SAFETENSORS_INDEX_FILE,
+ repo_type=repo_type,
+ revision=revision,
+ token=token,
+ )
+ with open(index_file) as f:
+ index = json.load(f)
+
+ weight_map = index.get("weight_map", {})
+
+ # Fetch metadata per shard
+ files_metadata = {}
+
+ def _parse(filename: str) -> None:
+ files_metadata[filename] = self.parse_safetensors_file_metadata(
+ repo_id=repo_id, filename=filename, repo_type=repo_type, revision=revision, token=token
+ )
+
+ thread_map(
+ _parse,
+ set(weight_map.values()),
+ desc="Parse safetensors files",
+ tqdm_class=hf_tqdm,
+ )
+
+ return SafetensorsRepoMetadata(
+ metadata=index.get("metadata", None),
+ sharded=True,
+ weight_map=weight_map,
+ files_metadata=files_metadata,
+ )
+ else:
+ # Not a safetensors repo
+ raise NotASafetensorsRepoError(
+ f"'{repo_id}' is not a safetensors repo. Couldn't find '{constants.SAFETENSORS_INDEX_FILE}' or '{constants.SAFETENSORS_SINGLE_FILE}' files."
+ )
+
+ def parse_safetensors_file_metadata(
+ self,
+ repo_id: str,
+ filename: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> SafetensorsFileMetadata:
+ """
+ Parse metadata from a safetensors file on the Hub.
+
+ To parse metadata from all safetensors files in a repo at once, use [`get_safetensors_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a
+ model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to fetch the file from. Can be a branch name, a tag, or a commit hash. Defaults to the
+ head of the `"main"` branch.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`SafetensorsFileMetadata`]: information related to a safetensors file.
+
+ Raises:
+ [`NotASafetensorsRepoError`]:
+ If the repo is not a safetensors repo i.e. doesn't have either a
+ `model.safetensors` or a `model.safetensors.index.json` file.
+ [`SafetensorsParsingError`]:
+ If a safetensors file header couldn't be parsed correctly.
+ """
+ url = hf_hub_url(
+ repo_id=repo_id, filename=filename, repo_type=repo_type, revision=revision, endpoint=self.endpoint
+ )
+ _headers = self._build_hf_headers(token=token)
+
+ # 1. Fetch first 100kb
+ # Empirically, 97% of safetensors files have a metadata size < 100kb (over the top 1000 models on the Hub).
+ # We assume fetching 100kb is faster than making 2 GET requests. Therefore we always fetch the first 100kb to
+ # avoid the 2nd GET in most cases.
+ # See https://github.com/huggingface/huggingface_hub/pull/1855#discussion_r1404286419.
+ response = get_session().get(url, headers={**_headers, "range": "bytes=0-100000"})
+ hf_raise_for_status(response)
+
+ # 2. Parse metadata size
+ metadata_size = struct.unpack(" constants.SAFETENSORS_MAX_HEADER_LENGTH:
+ raise SafetensorsParsingError(
+ f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
+ f"'{revision or constants.DEFAULT_REVISION}'): safetensors header is too big. Maximum supported size is "
+ f"{constants.SAFETENSORS_MAX_HEADER_LENGTH} bytes (got {metadata_size})."
+ )
+
+ # 3.a. Get metadata from payload
+ if metadata_size <= 100000:
+ metadata_as_bytes = response.content[8 : 8 + metadata_size]
+ else: # 3.b. Request full metadata
+ response = get_session().get(url, headers={**_headers, "range": f"bytes=8-{metadata_size + 7}"})
+ hf_raise_for_status(response)
+ metadata_as_bytes = response.content
+
+ # 4. Parse json header
+ try:
+ metadata_as_dict = json.loads(metadata_as_bytes.decode(errors="ignore"))
+ except json.JSONDecodeError as e:
+ raise SafetensorsParsingError(
+ f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
+ f"'{revision or constants.DEFAULT_REVISION}'): header is not json-encoded string. Please make sure this is a "
+ "correctly formatted safetensors file."
+ ) from e
+
+ try:
+ return SafetensorsFileMetadata(
+ metadata=metadata_as_dict.get("__metadata__", {}),
+ tensors={
+ key: TensorInfo(
+ dtype=tensor["dtype"],
+ shape=tensor["shape"],
+ data_offsets=tuple(tensor["data_offsets"]), # type: ignore
+ )
+ for key, tensor in metadata_as_dict.items()
+ if key != "__metadata__"
+ },
+ )
+ except (KeyError, IndexError) as e:
+ raise SafetensorsParsingError(
+ f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
+ f"'{revision or constants.DEFAULT_REVISION}'): header format not recognized. Please make sure this is a correctly"
+ " formatted safetensors file."
+ ) from e
+
+ @validate_hf_hub_args
+ def create_branch(
+ self,
+ repo_id: str,
+ *,
+ branch: str,
+ revision: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ exist_ok: bool = False,
+ ) -> None:
+ """
+ Create a new branch for a repo on the Hub, starting from the specified revision (defaults to `main`).
+ To find a revision suiting your needs, you can use [`list_repo_refs`] or [`list_repo_commits`].
+
+ Args:
+ repo_id (`str`):
+ The repository in which the branch will be created.
+ Example: `"user/my-cool-model"`.
+
+ branch (`str`):
+ The name of the branch to create.
+
+ revision (`str`, *optional*):
+ The git revision to create the branch from. It can be a branch name or
+ the OID/SHA of a commit, as a hexadecimal string. Defaults to the head
+ of the `"main"` branch.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if creating a branch on a dataset or
+ space, `None` or `"model"` if tagging a model. Default is `None`.
+
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if branch already exists.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.BadRequestError`]:
+ If invalid reference for a branch. Ex: `refs/pr/5` or 'refs/foo/bar'.
+ [`~utils.HfHubHTTPError`]:
+ If the branch already exists on the repo (error 409) and `exist_ok` is
+ set to `False`.
+ """
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ branch = quote(branch, safe="")
+
+ # Prepare request
+ branch_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/branch/{branch}"
+ headers = self._build_hf_headers(token=token)
+ payload = {}
+ if revision is not None:
+ payload["startingPoint"] = revision
+
+ # Create branch
+ response = get_session().post(url=branch_url, headers=headers, json=payload)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ if exist_ok and e.response.status_code == 409:
+ return
+ elif exist_ok and e.response.status_code == 403:
+ # No write permission on the namespace but branch might already exist
+ try:
+ refs = self.list_repo_refs(repo_id=repo_id, repo_type=repo_type, token=token)
+ for branch_ref in refs.branches:
+ if branch_ref.name == branch:
+ return # Branch already exists => do not raise
+ except HfHubHTTPError:
+ pass # We raise the original error if the branch does not exist
+ raise
+
+ @validate_hf_hub_args
+ def delete_branch(
+ self,
+ repo_id: str,
+ *,
+ branch: str,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Delete a branch from a repo on the Hub.
+
+ Args:
+ repo_id (`str`):
+ The repository in which a branch will be deleted.
+ Example: `"user/my-cool-model"`.
+
+ branch (`str`):
+ The name of the branch to delete.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if creating a branch on a dataset or
+ space, `None` or `"model"` if tagging a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.HfHubHTTPError`]:
+ If trying to delete a protected branch. Ex: `main` cannot be deleted.
+ [`~utils.HfHubHTTPError`]:
+ If trying to delete a branch that does not exist.
+
+ """
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ branch = quote(branch, safe="")
+
+ # Prepare request
+ branch_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/branch/{branch}"
+ headers = self._build_hf_headers(token=token)
+
+ # Delete branch
+ response = get_session().delete(url=branch_url, headers=headers)
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def create_tag(
+ self,
+ repo_id: str,
+ *,
+ tag: str,
+ tag_message: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ exist_ok: bool = False,
+ ) -> None:
+ """
+ Tag a given commit of a repo on the Hub.
+
+ Args:
+ repo_id (`str`):
+ The repository in which a commit will be tagged.
+ Example: `"user/my-cool-model"`.
+
+ tag (`str`):
+ The name of the tag to create.
+
+ tag_message (`str`, *optional*):
+ The description of the tag to create.
+
+ revision (`str`, *optional*):
+ The git revision to tag. It can be a branch name or the OID/SHA of a
+ commit, as a hexadecimal string. Shorthands (7 first characters) are
+ also supported. Defaults to the head of the `"main"` branch.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if tagging a dataset or
+ space, `None` or `"model"` if tagging a model. Default is
+ `None`.
+
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if tag already exists.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+ [`~utils.HfHubHTTPError`]:
+ If the branch already exists on the repo (error 409) and `exist_ok` is
+ set to `False`.
+ """
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else constants.DEFAULT_REVISION
+
+ # Prepare request
+ tag_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tag/{revision}"
+ headers = self._build_hf_headers(token=token)
+ payload = {"tag": tag}
+ if tag_message is not None:
+ payload["message"] = tag_message
+
+ # Tag
+ response = get_session().post(url=tag_url, headers=headers, json=payload)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ if not (e.response.status_code == 409 and exist_ok):
+ raise
+
+ @validate_hf_hub_args
+ def delete_tag(
+ self,
+ repo_id: str,
+ *,
+ tag: str,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Delete a tag from a repo on the Hub.
+
+ Args:
+ repo_id (`str`):
+ The repository in which a tag will be deleted.
+ Example: `"user/my-cool-model"`.
+
+ tag (`str`):
+ The name of the tag to delete.
+
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if tagging a dataset or space, `None` or
+ `"model"` if tagging a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If tag is not found.
+ """
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ tag = quote(tag, safe="")
+
+ # Prepare request
+ tag_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tag/{tag}"
+ headers = self._build_hf_headers(token=token)
+
+ # Un-tag
+ response = get_session().delete(url=tag_url, headers=headers)
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def get_full_repo_name(
+ self,
+ model_id: str,
+ *,
+ organization: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ):
+ """
+ Returns the repository name for a given model ID and optional
+ organization.
+
+ Args:
+ model_id (`str`):
+ The name of the model.
+ organization (`str`, *optional*):
+ If passed, the repository name will be in the organization
+ namespace instead of the user namespace.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `str`: The repository name in the user's namespace
+ ({username}/{model_id}) if no organization is passed, and under the
+ organization namespace ({organization}/{model_id}) otherwise.
+ """
+ if organization is None:
+ if "/" in model_id:
+ username = model_id.split("/")[0]
+ else:
+ username = self.whoami(token=token)["name"] # type: ignore
+ return f"{username}/{model_id}"
+ else:
+ return f"{organization}/{model_id}"
+
+ @validate_hf_hub_args
+ def get_repo_discussions(
+ self,
+ repo_id: str,
+ *,
+ author: Optional[str] = None,
+ discussion_type: Optional[constants.DiscussionTypeFilter] = None,
+ discussion_status: Optional[constants.DiscussionStatusFilter] = None,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Iterator[Discussion]:
+ """
+ Fetches Discussions and Pull Requests for the given repo.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ author (`str`, *optional*):
+ Pass a value to filter by discussion author. `None` means no filter.
+ Default is `None`.
+ discussion_type (`str`, *optional*):
+ Set to `"pull_request"` to fetch only pull requests, `"discussion"`
+ to fetch only discussions. Set to `"all"` or `None` to fetch both.
+ Default is `None`.
+ discussion_status (`str`, *optional*):
+ Set to `"open"` (respectively `"closed"`) to fetch only open
+ (respectively closed) discussions. Set to `"all"` or `None`
+ to fetch both.
+ Default is `None`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if fetching from a dataset or
+ space, `None` or `"model"` if fetching from a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterator[Discussion]`: An iterator of [`Discussion`] objects.
+
+ Example:
+ Collecting all discussions of a repo in a list:
+
+ ```python
+ >>> from huggingface_hub import get_repo_discussions
+ >>> discussions_list = list(get_repo_discussions(repo_id="bert-base-uncased"))
+ ```
+
+ Iterating over discussions of a repo:
+
+ ```python
+ >>> from huggingface_hub import get_repo_discussions
+ >>> for discussion in get_repo_discussions(repo_id="bert-base-uncased"):
+ ... print(discussion.num, discussion.title)
+ ```
+ """
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ if discussion_type is not None and discussion_type not in constants.DISCUSSION_TYPES:
+ raise ValueError(f"Invalid discussion_type, must be one of {constants.DISCUSSION_TYPES}")
+ if discussion_status is not None and discussion_status not in constants.DISCUSSION_STATUS:
+ raise ValueError(f"Invalid discussion_status, must be one of {constants.DISCUSSION_STATUS}")
+
+ headers = self._build_hf_headers(token=token)
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/discussions"
+
+ params: Dict[str, Union[str, int]] = {}
+ if discussion_type is not None:
+ params["type"] = discussion_type
+ if discussion_status is not None:
+ params["status"] = discussion_status
+ if author is not None:
+ params["author"] = author
+
+ def _fetch_discussion_page(page_index: int):
+ params["p"] = page_index
+ resp = get_session().get(path, headers=headers, params=params)
+ hf_raise_for_status(resp)
+ paginated_discussions = resp.json()
+ total = paginated_discussions["count"]
+ start = paginated_discussions["start"]
+ discussions = paginated_discussions["discussions"]
+ has_next = (start + len(discussions)) < total
+ return discussions, has_next
+
+ has_next, page_index = True, 0
+
+ while has_next:
+ discussions, has_next = _fetch_discussion_page(page_index=page_index)
+ for discussion in discussions:
+ yield Discussion(
+ title=discussion["title"],
+ num=discussion["num"],
+ author=discussion.get("author", {}).get("name", "deleted"),
+ created_at=parse_datetime(discussion["createdAt"]),
+ status=discussion["status"],
+ repo_id=discussion["repo"]["name"],
+ repo_type=discussion["repo"]["type"],
+ is_pull_request=discussion["isPullRequest"],
+ endpoint=self.endpoint,
+ )
+ page_index = page_index + 1
+
+ @validate_hf_hub_args
+ def get_discussion_details(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ *,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> DiscussionWithDetails:
+ """Fetches a Discussion's / Pull Request 's details from the Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns: [`DiscussionWithDetails`]
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if not isinstance(discussion_num, int) or discussion_num <= 0:
+ raise ValueError("Invalid discussion_num, must be a positive integer")
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/discussions/{discussion_num}"
+ headers = self._build_hf_headers(token=token)
+ resp = get_session().get(path, params={"diff": "1"}, headers=headers)
+ hf_raise_for_status(resp)
+
+ discussion_details = resp.json()
+ is_pull_request = discussion_details["isPullRequest"]
+
+ target_branch = discussion_details["changes"]["base"] if is_pull_request else None
+ conflicting_files = discussion_details["filesWithConflicts"] if is_pull_request else None
+ merge_commit_oid = discussion_details["changes"].get("mergeCommitId", None) if is_pull_request else None
+
+ return DiscussionWithDetails(
+ title=discussion_details["title"],
+ num=discussion_details["num"],
+ author=discussion_details.get("author", {}).get("name", "deleted"),
+ created_at=parse_datetime(discussion_details["createdAt"]),
+ status=discussion_details["status"],
+ repo_id=discussion_details["repo"]["name"],
+ repo_type=discussion_details["repo"]["type"],
+ is_pull_request=discussion_details["isPullRequest"],
+ events=[deserialize_event(evt) for evt in discussion_details["events"]],
+ conflicting_files=conflicting_files,
+ target_branch=target_branch,
+ merge_commit_oid=merge_commit_oid,
+ diff=discussion_details.get("diff"),
+ endpoint=self.endpoint,
+ )
+
+ @validate_hf_hub_args
+ def create_discussion(
+ self,
+ repo_id: str,
+ title: str,
+ *,
+ token: Union[bool, str, None] = None,
+ description: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ pull_request: bool = False,
+ ) -> DiscussionWithDetails:
+ """Creates a Discussion or Pull Request.
+
+ Pull Requests created programmatically will be in `"draft"` status.
+
+ Creating a Pull Request with changes can also be done at once with [`HfApi.create_commit`].
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ title (`str`):
+ The title of the discussion. It can be up to 200 characters long,
+ and must be at least 3 characters long. Leading and trailing whitespaces
+ will be stripped.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ description (`str`, *optional*):
+ An optional description for the Pull Request.
+ Defaults to `"Discussion opened with the huggingface_hub Python library"`
+ pull_request (`bool`, *optional*):
+ Whether to create a Pull Request or discussion. If `True`, creates a Pull Request.
+ If `False`, creates a discussion. Defaults to `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns: [`DiscussionWithDetails`]
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+ """
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+
+ if description is not None:
+ description = description.strip()
+ description = (
+ description
+ if description
+ else (
+ f"{'Pull Request' if pull_request else 'Discussion'} opened with the"
+ " [huggingface_hub Python"
+ " library](https://huggingface.co/docs/huggingface_hub)"
+ )
+ )
+
+ headers = self._build_hf_headers(token=token)
+ resp = get_session().post(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/discussions",
+ json={
+ "title": title.strip(),
+ "description": description,
+ "pullRequest": pull_request,
+ },
+ headers=headers,
+ )
+ hf_raise_for_status(resp)
+ num = resp.json()["num"]
+ return self.get_discussion_details(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=num,
+ token=token,
+ )
+
+ @validate_hf_hub_args
+ def create_pull_request(
+ self,
+ repo_id: str,
+ title: str,
+ *,
+ token: Union[bool, str, None] = None,
+ description: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionWithDetails:
+ """Creates a Pull Request . Pull Requests created programmatically will be in `"draft"` status.
+
+ Creating a Pull Request with changes can also be done at once with [`HfApi.create_commit`];
+
+ This is a wrapper around [`HfApi.create_discussion`].
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ title (`str`):
+ The title of the discussion. It can be up to 200 characters long,
+ and must be at least 3 characters long. Leading and trailing whitespaces
+ will be stripped.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ description (`str`, *optional*):
+ An optional description for the Pull Request.
+ Defaults to `"Discussion opened with the huggingface_hub Python library"`
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns: [`DiscussionWithDetails`]
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+ """
+ return self.create_discussion(
+ repo_id=repo_id,
+ title=title,
+ token=token,
+ description=description,
+ repo_type=repo_type,
+ pull_request=True,
+ )
+
+ def _post_discussion_changes(
+ self,
+ *,
+ repo_id: str,
+ discussion_num: int,
+ resource: str,
+ body: Optional[dict] = None,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> requests.Response:
+ """Internal utility to POST changes to a Discussion or Pull Request"""
+ if not isinstance(discussion_num, int) or discussion_num <= 0:
+ raise ValueError("Invalid discussion_num, must be a positive integer")
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ repo_id = f"{repo_type}s/{repo_id}"
+
+ path = f"{self.endpoint}/api/{repo_id}/discussions/{discussion_num}/{resource}"
+
+ headers = self._build_hf_headers(token=token)
+ resp = requests.post(path, headers=headers, json=body)
+ hf_raise_for_status(resp)
+ return resp
+
+ @validate_hf_hub_args
+ def comment_discussion(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ comment: str,
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionComment:
+ """Creates a new comment on the given Discussion.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment (`str`):
+ The content of the comment to create. Comments support markdown formatting.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`DiscussionComment`]: the newly created comment
+
+
+ Examples:
+ ```python
+
+ >>> comment = \"\"\"
+ ... Hello @otheruser!
+ ...
+ ... # This is a title
+ ...
+ ... **This is bold**, *this is italic* and ~this is strikethrough~
+ ... And [this](http://url) is a link
+ ... \"\"\"
+
+ >>> HfApi().comment_discussion(
+ ... repo_id="username/repo_name",
+ ... discussion_num=34
+ ... comment=comment
+ ... )
+ # DiscussionComment(id='deadbeef0000000', type='comment', ...)
+
+ ```
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="comment",
+ body={"comment": comment},
+ )
+ return deserialize_event(resp.json()["newMessage"]) # type: ignore
+
+ @validate_hf_hub_args
+ def rename_discussion(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ new_title: str,
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionTitleChange:
+ """Renames a Discussion.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ new_title (`str`):
+ The new title for the discussion
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`DiscussionTitleChange`]: the title change event
+
+
+ Examples:
+ ```python
+ >>> new_title = "New title, fixing a typo"
+ >>> HfApi().rename_discussion(
+ ... repo_id="username/repo_name",
+ ... discussion_num=34
+ ... new_title=new_title
+ ... )
+ # DiscussionTitleChange(id='deadbeef0000000', type='title-change', ...)
+
+ ```
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="title",
+ body={"title": new_title},
+ )
+ return deserialize_event(resp.json()["newTitle"]) # type: ignore
+
+ @validate_hf_hub_args
+ def change_discussion_status(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ new_status: Literal["open", "closed"],
+ *,
+ token: Union[bool, str, None] = None,
+ comment: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionStatusChange:
+ """Closes or re-opens a Discussion or Pull Request.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ new_status (`str`):
+ The new status for the discussion, either `"open"` or `"closed"`.
+ comment (`str`, *optional*):
+ An optional comment to post with the status change.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`DiscussionStatusChange`]: the status change event
+
+
+ Examples:
+ ```python
+ >>> new_title = "New title, fixing a typo"
+ >>> HfApi().rename_discussion(
+ ... repo_id="username/repo_name",
+ ... discussion_num=34
+ ... new_title=new_title
+ ... )
+ # DiscussionStatusChange(id='deadbeef0000000', type='status-change', ...)
+
+ ```
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if new_status not in ["open", "closed"]:
+ raise ValueError("Invalid status, valid statuses are: 'open' and 'closed'")
+ body: Dict[str, str] = {"status": new_status}
+ if comment and comment.strip():
+ body["comment"] = comment.strip()
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="status",
+ body=body,
+ )
+ return deserialize_event(resp.json()["newStatus"]) # type: ignore
+
+ @validate_hf_hub_args
+ def merge_pull_request(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ *,
+ token: Union[bool, str, None] = None,
+ comment: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ):
+ """Merges a Pull Request.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment (`str`, *optional*):
+ An optional comment to post with the status change.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`DiscussionStatusChange`]: the status change event
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="merge",
+ body={"comment": comment.strip()} if comment and comment.strip() else None,
+ )
+
+ @validate_hf_hub_args
+ def edit_discussion_comment(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ comment_id: str,
+ new_content: str,
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionComment:
+ """Edits a comment on a Discussion / Pull Request.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment_id (`str`):
+ The ID of the comment to edit.
+ new_content (`str`):
+ The new content of the comment. Comments support markdown formatting.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`DiscussionComment`]: the edited comment
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource=f"comment/{comment_id.lower()}/edit",
+ body={"content": new_content},
+ )
+ return deserialize_event(resp.json()["updatedComment"]) # type: ignore
+
+ @validate_hf_hub_args
+ def hide_discussion_comment(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ comment_id: str,
+ *,
+ token: Union[bool, str, None] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionComment:
+ """Hides a comment on a Discussion / Pull Request.
+
+
+ Hidden comments' content cannot be retrieved anymore. Hiding a comment is irreversible.
+
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment_id (`str`):
+ The ID of the comment to edit.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`DiscussionComment`]: the hidden comment
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ warnings.warn(
+ "Hidden comments' content cannot be retrieved anymore. Hiding a comment is irreversible.",
+ UserWarning,
+ )
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource=f"comment/{comment_id.lower()}/hide",
+ )
+ return deserialize_event(resp.json()["updatedComment"]) # type: ignore
+
+ @validate_hf_hub_args
+ def add_space_secret(
+ self,
+ repo_id: str,
+ key: str,
+ value: str,
+ *,
+ description: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> None:
+ """Adds or updates a secret in a Space.
+
+ Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Secret key. Example: `"GITHUB_API_KEY"`
+ value (`str`):
+ Secret value. Example: `"your_github_api_key"`.
+ description (`str`, *optional*):
+ Secret description. Example: `"Github API key to access the Github API"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ payload = {"key": key, "value": value}
+ if description is not None:
+ payload["description"] = description
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+
+ @validate_hf_hub_args
+ def delete_space_secret(self, repo_id: str, key: str, *, token: Union[bool, str, None] = None) -> None:
+ """Deletes a secret from a Space.
+
+ Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Secret key. Example: `"GITHUB_API_KEY"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+ headers=self._build_hf_headers(token=token),
+ json={"key": key},
+ )
+ hf_raise_for_status(r)
+
+ @validate_hf_hub_args
+ def get_space_variables(self, repo_id: str, *, token: Union[bool, str, None] = None) -> Dict[str, SpaceVariable]:
+ """Gets all variables from a Space.
+
+ Variables allow to set environment variables to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to query. Example: `"bigcode/in-the-stack"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/spaces/{repo_id}/variables",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(r)
+ return {k: SpaceVariable(k, v) for k, v in r.json().items()}
+
+ @validate_hf_hub_args
+ def add_space_variable(
+ self,
+ repo_id: str,
+ key: str,
+ value: str,
+ *,
+ description: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Dict[str, SpaceVariable]:
+ """Adds or updates a variable in a Space.
+
+ Variables allow to set environment variables to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Variable key. Example: `"MODEL_REPO_ID"`
+ value (`str`):
+ Variable value. Example: `"the_model_repo_id"`.
+ description (`str`):
+ Description of the variable. Example: `"Model Repo ID of the implemented model"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ payload = {"key": key, "value": value}
+ if description is not None:
+ payload["description"] = description
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/variables",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+ return {k: SpaceVariable(k, v) for k, v in r.json().items()}
+
+ @validate_hf_hub_args
+ def delete_space_variable(
+ self, repo_id: str, key: str, *, token: Union[bool, str, None] = None
+ ) -> Dict[str, SpaceVariable]:
+ """Deletes a variable from a Space.
+
+ Variables allow to set environment variables to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Variable key. Example: `"MODEL_REPO_ID"`
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/spaces/{repo_id}/variables",
+ headers=self._build_hf_headers(token=token),
+ json={"key": key},
+ )
+ hf_raise_for_status(r)
+ return {k: SpaceVariable(k, v) for k, v in r.json().items()}
+
+ @validate_hf_hub_args
+ def get_space_runtime(self, repo_id: str, *, token: Union[bool, str, None] = None) -> SpaceRuntime:
+ """Gets runtime information about a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/spaces/{repo_id}/runtime", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def request_space_hardware(
+ self,
+ repo_id: str,
+ hardware: SpaceHardware,
+ *,
+ token: Union[bool, str, None] = None,
+ sleep_time: Optional[int] = None,
+ ) -> SpaceRuntime:
+ """Request new hardware for a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ hardware (`str` or [`SpaceHardware`]):
+ Hardware on which to run the Space. Example: `"t4-medium"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+
+
+
+ It is also possible to request hardware directly when creating the Space repo! See [`create_repo`] for details.
+
+
+ """
+ if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC:
+ warnings.warn(
+ "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
+ " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
+ " you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
+ UserWarning,
+ )
+ payload: Dict[str, Any] = {"flavor": hardware}
+ if sleep_time is not None:
+ payload["sleepTimeSeconds"] = sleep_time
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/hardware",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def set_space_sleep_time(
+ self, repo_id: str, sleep_time: int, *, token: Union[bool, str, None] = None
+ ) -> SpaceRuntime:
+ """Set a custom sleep time for a Space running on upgraded hardware..
+
+ Your Space will go to sleep after X seconds of inactivity. You are not billed when your Space is in "sleep"
+ mode. If a new visitor lands on your Space, it will "wake it up". Only upgraded hardware can have a
+ configurable sleep time. To know more about the sleep stage, please refer to
+ https://huggingface.co/docs/hub/spaces-gpus#sleep-time.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to pause (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+
+
+
+ It is also possible to set a custom sleep time when requesting hardware with [`request_space_hardware`].
+
+
+ """
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/sleeptime",
+ headers=self._build_hf_headers(token=token),
+ json={"seconds": sleep_time},
+ )
+ hf_raise_for_status(r)
+ runtime = SpaceRuntime(r.json())
+
+ hardware = runtime.requested_hardware or runtime.hardware
+ if hardware == SpaceHardware.CPU_BASIC:
+ warnings.warn(
+ "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
+ " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
+ " you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
+ UserWarning,
+ )
+ return runtime
+
+ @validate_hf_hub_args
+ def pause_space(self, repo_id: str, *, token: Union[bool, str, None] = None) -> SpaceRuntime:
+ """Pause your Space.
+
+ A paused Space stops executing until manually restarted by its owner. This is different from the sleeping
+ state in which free Spaces go after 48h of inactivity. Paused time is not billed to your account, no matter the
+ hardware you've selected. To restart your Space, use [`restart_space`] and go to your Space settings page.
+
+ For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause).
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to pause. Example: `"Salesforce/BLIP2"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`SpaceRuntime`]: Runtime information about your Space including `stage=PAUSED` and requested hardware.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If your Space is not found (error 404). Most probably wrong repo_id or your space is private but you
+ are not authenticated.
+ [`~utils.HfHubHTTPError`]:
+ 403 Forbidden: only the owner of a Space can pause it. If you want to manage a Space that you don't
+ own, either ask the owner by opening a Discussion or duplicate the Space.
+ [`~utils.BadRequestError`]:
+ If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide
+ a static Space, you can set it to private.
+ """
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/pause", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def restart_space(
+ self, repo_id: str, *, token: Union[bool, str, None] = None, factory_reboot: bool = False
+ ) -> SpaceRuntime:
+ """Restart your Space.
+
+ This is the only way to programmatically restart a Space if you've put it on Pause (see [`pause_space`]). You
+ must be the owner of the Space to restart it. If you are using an upgraded hardware, your account will be
+ billed as soon as the Space is restarted. You can trigger a restart no matter the current state of a Space.
+
+ For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause).
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to restart. Example: `"Salesforce/BLIP2"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ factory_reboot (`bool`, *optional*):
+ If `True`, the Space will be rebuilt from scratch without caching any requirements.
+
+ Returns:
+ [`SpaceRuntime`]: Runtime information about your Space.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If your Space is not found (error 404). Most probably wrong repo_id or your space is private but you
+ are not authenticated.
+ [`~utils.HfHubHTTPError`]:
+ 403 Forbidden: only the owner of a Space can restart it. If you want to restart a Space that you don't
+ own, either ask the owner by opening a Discussion or duplicate the Space.
+ [`~utils.BadRequestError`]:
+ If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide
+ a static Space, you can set it to private.
+ """
+ params = {}
+ if factory_reboot:
+ params["factory"] = "true"
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/restart", headers=self._build_hf_headers(token=token), params=params
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def duplicate_space(
+ self,
+ from_id: str,
+ to_id: Optional[str] = None,
+ *,
+ private: Optional[bool] = None,
+ token: Union[bool, str, None] = None,
+ exist_ok: bool = False,
+ hardware: Optional[SpaceHardware] = None,
+ storage: Optional[SpaceStorage] = None,
+ sleep_time: Optional[int] = None,
+ secrets: Optional[List[Dict[str, str]]] = None,
+ variables: Optional[List[Dict[str, str]]] = None,
+ ) -> RepoUrl:
+ """Duplicate a Space.
+
+ Programmatically duplicate a Space. The new Space will be created in your account and will be in the same state
+ as the original Space (running or paused). You can duplicate a Space no matter the current state of a Space.
+
+ Args:
+ from_id (`str`):
+ ID of the Space to duplicate. Example: `"pharma/CLIP-Interrogator"`.
+ to_id (`str`, *optional*):
+ ID of the new Space. Example: `"dog/CLIP-Interrogator"`. If not provided, the new Space will have the same
+ name as the original Space, but in your account.
+ private (`bool`, *optional*):
+ Whether the new Space should be private or not. Defaults to the same privacy as the original Space.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if repo already exists.
+ hardware (`SpaceHardware` or `str`, *optional*):
+ Choice of Hardware. Example: `"t4-medium"`. See [`SpaceHardware`] for a complete list.
+ storage (`SpaceStorage` or `str`, *optional*):
+ Choice of persistent storage tier. Example: `"small"`. See [`SpaceStorage`] for a complete list.
+ sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ secrets (`List[Dict[str, str]]`, *optional*):
+ A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+ variables (`List[Dict[str, str]]`, *optional*):
+ A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables.
+
+ Returns:
+ [`RepoUrl`]: URL to the newly created repo. Value is a subclass of `str` containing
+ attributes like `endpoint`, `repo_type` and `repo_id`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If one of `from_id` or `to_id` cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ If the HuggingFace API returned an error
+
+ Example:
+ ```python
+ >>> from huggingface_hub import duplicate_space
+
+ # Duplicate a Space to your account
+ >>> duplicate_space("multimodalart/dreambooth-training")
+ RepoUrl('https://huggingface.co/spaces/nateraw/dreambooth-training',...)
+
+ # Can set custom destination id and visibility flag.
+ >>> duplicate_space("multimodalart/dreambooth-training", to_id="my-dreambooth", private=True)
+ RepoUrl('https://huggingface.co/spaces/nateraw/my-dreambooth',...)
+ ```
+ """
+ # Parse to_id if provided
+ parsed_to_id = RepoUrl(to_id) if to_id is not None else None
+
+ # Infer target repo_id
+ to_namespace = ( # set namespace manually or default to username
+ parsed_to_id.namespace
+ if parsed_to_id is not None and parsed_to_id.namespace is not None
+ else self.whoami(token)["name"]
+ )
+ to_repo_name = parsed_to_id.repo_name if to_id is not None else RepoUrl(from_id).repo_name # type: ignore
+
+ # repository must be a valid repo_id (namespace/repo_name).
+ payload: Dict[str, Any] = {"repository": f"{to_namespace}/{to_repo_name}"}
+
+ keys = ["private", "hardware", "storageTier", "sleepTimeSeconds", "secrets", "variables"]
+ values = [private, hardware, storage, sleep_time, secrets, variables]
+ payload.update({k: v for k, v in zip(keys, values) if v is not None})
+
+ if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC:
+ warnings.warn(
+ "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
+ " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
+ " you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
+ UserWarning,
+ )
+
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{from_id}/duplicate",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exist_ok and err.response.status_code == 409:
+ # Repo already exists and `exist_ok=True`
+ pass
+ else:
+ raise
+
+ return RepoUrl(r.json()["url"], endpoint=self.endpoint)
+
+ @validate_hf_hub_args
+ def request_space_storage(
+ self,
+ repo_id: str,
+ storage: SpaceStorage,
+ *,
+ token: Union[bool, str, None] = None,
+ ) -> SpaceRuntime:
+ """Request persistent storage for a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to update. Example: `"open-llm-leaderboard/open_llm_leaderboard"`.
+ storage (`str` or [`SpaceStorage`]):
+ Storage tier. Either 'small', 'medium', or 'large'.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+
+
+
+ It is not possible to decrease persistent storage after its granted. To do so, you must delete it
+ via [`delete_space_storage`].
+
+
+ """
+ payload: Dict[str, SpaceStorage] = {"tier": storage}
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/storage",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def delete_space_storage(
+ self,
+ repo_id: str,
+ *,
+ token: Union[bool, str, None] = None,
+ ) -> SpaceRuntime:
+ """Delete persistent storage for a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to update. Example: `"open-llm-leaderboard/open_llm_leaderboard"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+ Raises:
+ [`BadRequestError`]
+ If space has no persistent storage.
+
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/spaces/{repo_id}/storage",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ #######################
+ # Inference Endpoints #
+ #######################
+
+ def list_inference_endpoints(
+ self, namespace: Optional[str] = None, *, token: Union[bool, str, None] = None
+ ) -> List[InferenceEndpoint]:
+ """Lists all inference endpoints for the given namespace.
+
+ Args:
+ namespace (`str`, *optional*):
+ The namespace to list endpoints for. Defaults to the current user. Set to `"*"` to list all endpoints
+ from all namespaces (i.e. personal namespace and all orgs the user belongs to).
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ List[`InferenceEndpoint`]: A list of all inference endpoints for the given namespace.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> api.list_inference_endpoints()
+ [InferenceEndpoint(name='my-endpoint', ...), ...]
+ ```
+ """
+ # Special case: list all endpoints for all namespaces the user has access to
+ if namespace == "*":
+ user = self.whoami(token=token)
+
+ # List personal endpoints first
+ endpoints: List[InferenceEndpoint] = list_inference_endpoints(namespace=self._get_namespace(token=token))
+
+ # Then list endpoints for all orgs the user belongs to and ignore 401 errors (no billing or no access)
+ for org in user.get("orgs", []):
+ try:
+ endpoints += list_inference_endpoints(namespace=org["name"], token=token)
+ except HfHubHTTPError as error:
+ if error.response.status_code == 401: # Either no billing or user don't have access)
+ logger.debug("Cannot list Inference Endpoints for org '%s': %s", org["name"], error)
+ pass
+
+ return endpoints
+
+ # Normal case: list endpoints for a specific namespace
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().get(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return [
+ InferenceEndpoint.from_raw(endpoint, namespace=namespace, token=token)
+ for endpoint in response.json()["items"]
+ ]
+
+ def create_inference_endpoint(
+ self,
+ name: str,
+ *,
+ repository: str,
+ framework: str,
+ accelerator: str,
+ instance_size: str,
+ instance_type: str,
+ region: str,
+ vendor: str,
+ account_id: Optional[str] = None,
+ min_replica: int = 0,
+ max_replica: int = 1,
+ scale_to_zero_timeout: int = 15,
+ revision: Optional[str] = None,
+ task: Optional[str] = None,
+ custom_image: Optional[Dict] = None,
+ secrets: Optional[Dict[str, str]] = None,
+ type: InferenceEndpointType = InferenceEndpointType.PROTECTED,
+ namespace: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> InferenceEndpoint:
+ """Create a new Inference Endpoint.
+
+ Args:
+ name (`str`):
+ The unique name for the new Inference Endpoint.
+ repository (`str`):
+ The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
+ framework (`str`):
+ The machine learning framework used for the model (e.g. `"custom"`).
+ accelerator (`str`):
+ The hardware accelerator to be used for inference (e.g. `"cpu"`).
+ instance_size (`str`):
+ The size or type of the instance to be used for hosting the model (e.g. `"x4"`).
+ instance_type (`str`):
+ The cloud instance type where the Inference Endpoint will be deployed (e.g. `"intel-icl"`).
+ region (`str`):
+ The cloud region in which the Inference Endpoint will be created (e.g. `"us-east-1"`).
+ vendor (`str`):
+ The cloud provider or vendor where the Inference Endpoint will be hosted (e.g. `"aws"`).
+ account_id (`str`, *optional*):
+ The account ID used to link a VPC to a private Inference Endpoint (if applicable).
+ min_replica (`int`, *optional*):
+ The minimum number of replicas (instances) to keep running for the Inference Endpoint. Defaults to 0.
+ max_replica (`int`, *optional*):
+ The maximum number of replicas (instances) to scale to for the Inference Endpoint. Defaults to 1.
+ scale_to_zero_timeout (`int`, *optional*):
+ The duration in minutes before an inactive endpoint is scaled to zero. Defaults to 15.
+ revision (`str`, *optional*):
+ The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
+ task (`str`, *optional*):
+ The task on which to deploy the model (e.g. `"text-classification"`).
+ custom_image (`Dict`, *optional*):
+ A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an
+ Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples).
+ secrets (`Dict[str, str]`, *optional*):
+ Secret values to inject in the container environment.
+ type ([`InferenceEndpointType]`, *optional*):
+ The type of the Inference Endpoint, which can be `"protected"` (default), `"public"` or `"private"`.
+ namespace (`str`, *optional*):
+ The namespace where the Inference Endpoint will be created. Defaults to the current user's namespace.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`InferenceEndpoint`]: information about the updated Inference Endpoint.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> endpoint = api.create_inference_endpoint(
+ ... "my-endpoint-name",
+ ... repository="gpt2",
+ ... framework="pytorch",
+ ... task="text-generation",
+ ... accelerator="cpu",
+ ... vendor="aws",
+ ... region="us-east-1",
+ ... type="protected",
+ ... instance_size="x2",
+ ... instance_type="intel-icl",
+ ... )
+ >>> endpoint
+ InferenceEndpoint(name='my-endpoint-name', status="pending",...)
+
+ # Run inference on the endpoint
+ >>> endpoint.client.text_generation(...)
+ "..."
+ ```
+
+ ```python
+ # Start an Inference Endpoint running Zephyr-7b-beta on TGI
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> endpoint = api.create_inference_endpoint(
+ ... "aws-zephyr-7b-beta-0486",
+ ... repository="HuggingFaceH4/zephyr-7b-beta",
+ ... framework="pytorch",
+ ... task="text-generation",
+ ... accelerator="gpu",
+ ... vendor="aws",
+ ... region="us-east-1",
+ ... type="protected",
+ ... instance_size="x1",
+ ... instance_type="nvidia-a10g",
+ ... custom_image={
+ ... "health_route": "/health",
+ ... "env": {
+ ... "MAX_BATCH_PREFILL_TOKENS": "2048",
+ ... "MAX_INPUT_LENGTH": "1024",
+ ... "MAX_TOTAL_TOKENS": "1512",
+ ... "MODEL_ID": "/repository"
+ ... },
+ ... "url": "ghcr.io/huggingface/text-generation-inference:1.1.0",
+ ... },
+ ... secrets={"MY_SECRET_KEY": "secret_value"},
+ ... )
+
+ ```
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ image = {"custom": custom_image} if custom_image is not None else {"huggingface": {}}
+ payload: Dict = {
+ "accountId": account_id,
+ "compute": {
+ "accelerator": accelerator,
+ "instanceSize": instance_size,
+ "instanceType": instance_type,
+ "scaling": {
+ "maxReplica": max_replica,
+ "minReplica": min_replica,
+ "scaleToZeroTimeout": scale_to_zero_timeout,
+ },
+ },
+ "model": {
+ "framework": framework,
+ "repository": repository,
+ "revision": revision,
+ "task": task,
+ "image": image,
+ },
+ "name": name,
+ "provider": {
+ "region": region,
+ "vendor": vendor,
+ },
+ "type": type,
+ }
+ if secrets:
+ payload["model"]["secrets"] = secrets
+ response = get_session().post(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def get_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> InferenceEndpoint:
+ """Get information about an Inference Endpoint.
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to retrieve information about.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`InferenceEndpoint`]: information about the requested Inference Endpoint.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> endpoint = api.get_inference_endpoint("my-text-to-image")
+ >>> endpoint
+ InferenceEndpoint(name='my-text-to-image', ...)
+
+ # Get status
+ >>> endpoint.status
+ 'running'
+ >>> endpoint.url
+ 'https://my-text-to-image.region.vendor.endpoints.huggingface.cloud'
+
+ # Run inference
+ >>> endpoint.client.text_to_image(...)
+ ```
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().get(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def update_inference_endpoint(
+ self,
+ name: str,
+ *,
+ # Compute update
+ accelerator: Optional[str] = None,
+ instance_size: Optional[str] = None,
+ instance_type: Optional[str] = None,
+ min_replica: Optional[int] = None,
+ max_replica: Optional[int] = None,
+ scale_to_zero_timeout: Optional[int] = None,
+ # Model update
+ repository: Optional[str] = None,
+ framework: Optional[str] = None,
+ revision: Optional[str] = None,
+ task: Optional[str] = None,
+ custom_image: Optional[Dict] = None,
+ secrets: Optional[Dict[str, str]] = None,
+ # Other
+ namespace: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> InferenceEndpoint:
+ """Update an Inference Endpoint.
+
+ This method allows the update of either the compute configuration, the deployed model, or both. All arguments are
+ optional but at least one must be provided.
+
+ For convenience, you can also update an Inference Endpoint using [`InferenceEndpoint.update`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to update.
+
+ accelerator (`str`, *optional*):
+ The hardware accelerator to be used for inference (e.g. `"cpu"`).
+ instance_size (`str`, *optional*):
+ The size or type of the instance to be used for hosting the model (e.g. `"x4"`).
+ instance_type (`str`, *optional*):
+ The cloud instance type where the Inference Endpoint will be deployed (e.g. `"intel-icl"`).
+ min_replica (`int`, *optional*):
+ The minimum number of replicas (instances) to keep running for the Inference Endpoint.
+ max_replica (`int`, *optional*):
+ The maximum number of replicas (instances) to scale to for the Inference Endpoint.
+ scale_to_zero_timeout (`int`, *optional*):
+ The duration in minutes before an inactive endpoint is scaled to zero.
+
+ repository (`str`, *optional*):
+ The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
+ framework (`str`, *optional*):
+ The machine learning framework used for the model (e.g. `"custom"`).
+ revision (`str`, *optional*):
+ The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
+ task (`str`, *optional*):
+ The task on which to deploy the model (e.g. `"text-classification"`).
+ custom_image (`Dict`, *optional*):
+ A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an
+ Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples).
+ secrets (`Dict[str, str]`, *optional*):
+ Secret values to inject in the container environment.
+ namespace (`str`, *optional*):
+ The namespace where the Inference Endpoint will be updated. Defaults to the current user's namespace.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`InferenceEndpoint`]: information about the updated Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ # Populate only the fields that are not None
+ payload: Dict = defaultdict(lambda: defaultdict(dict))
+ if accelerator is not None:
+ payload["compute"]["accelerator"] = accelerator
+ if instance_size is not None:
+ payload["compute"]["instanceSize"] = instance_size
+ if instance_type is not None:
+ payload["compute"]["instanceType"] = instance_type
+ if max_replica is not None:
+ payload["compute"]["scaling"]["maxReplica"] = max_replica
+ if min_replica is not None:
+ payload["compute"]["scaling"]["minReplica"] = min_replica
+ if scale_to_zero_timeout is not None:
+ payload["compute"]["scaling"]["scaleToZeroTimeout"] = scale_to_zero_timeout
+ if repository is not None:
+ payload["model"]["repository"] = repository
+ if framework is not None:
+ payload["model"]["framework"] = framework
+ if revision is not None:
+ payload["model"]["revision"] = revision
+ if task is not None:
+ payload["model"]["task"] = task
+ if custom_image is not None:
+ payload["model"]["image"] = {"custom": custom_image}
+ if secrets is not None:
+ payload["model"]["secrets"] = secrets
+
+ response = get_session().put(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def delete_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> None:
+ """Delete an Inference Endpoint.
+
+ This operation is not reversible. If you don't want to be charged for an Inference Endpoint, it is preferable
+ to pause it with [`pause_inference_endpoint`] or scale it to zero with [`scale_to_zero_inference_endpoint`].
+
+ For convenience, you can also delete an Inference Endpoint using [`InferenceEndpoint.delete`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to delete.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+ response = get_session().delete(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ def pause_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> InferenceEndpoint:
+ """Pause an Inference Endpoint.
+
+ A paused Inference Endpoint will not be charged. It can be resumed at any time using [`resume_inference_endpoint`].
+ This is different than scaling the Inference Endpoint to zero with [`scale_to_zero_inference_endpoint`], which
+ would be automatically restarted when a request is made to it.
+
+ For convenience, you can also pause an Inference Endpoint using [`pause_inference_endpoint`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to pause.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`InferenceEndpoint`]: information about the paused Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().post(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}/pause",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def resume_inference_endpoint(
+ self,
+ name: str,
+ *,
+ namespace: Optional[str] = None,
+ running_ok: bool = True,
+ token: Union[bool, str, None] = None,
+ ) -> InferenceEndpoint:
+ """Resume an Inference Endpoint.
+
+ For convenience, you can also resume an Inference Endpoint using [`InferenceEndpoint.resume`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to resume.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ running_ok (`bool`, *optional*):
+ If `True`, the method will not raise an error if the Inference Endpoint is already running. Defaults to
+ `True`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`InferenceEndpoint`]: information about the resumed Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().post(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}/resume",
+ headers=self._build_hf_headers(token=token),
+ )
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as error:
+ # If already running (and it's ok), then fetch current status and return
+ if running_ok and error.response.status_code == 400 and "already running" in error.response.text:
+ return self.get_inference_endpoint(name, namespace=namespace, token=token)
+ # Otherwise, raise the error
+ raise
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def scale_to_zero_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> InferenceEndpoint:
+ """Scale Inference Endpoint to zero.
+
+ An Inference Endpoint scaled to zero will not be charged. It will be resume on the next request to it, with a
+ cold start delay. This is different than pausing the Inference Endpoint with [`pause_inference_endpoint`], which
+ would require a manual resume with [`resume_inference_endpoint`].
+
+ For convenience, you can also scale an Inference Endpoint to zero using [`InferenceEndpoint.scale_to_zero`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to scale to zero.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`InferenceEndpoint`]: information about the scaled-to-zero Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().post(
+ f"{constants.INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}/scale-to-zero",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def _get_namespace(self, token: Union[bool, str, None] = None) -> str:
+ """Get the default namespace for the current user."""
+ me = self.whoami(token=token)
+ if me["type"] == "user":
+ return me["name"]
+ else:
+ raise ValueError(
+ "Cannot determine default namespace. You must provide a 'namespace' as input or be logged in as a"
+ " user."
+ )
+
+ ########################
+ # Collection Endpoints #
+ ########################
+ @validate_hf_hub_args
+ def list_collections(
+ self,
+ *,
+ owner: Union[List[str], str, None] = None,
+ item: Union[List[str], str, None] = None,
+ sort: Optional[Literal["lastModified", "trending", "upvotes"]] = None,
+ limit: Optional[int] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Iterable[Collection]:
+ """List collections on the Huggingface Hub, given some filters.
+
+
+
+ When listing collections, the item list per collection is truncated to 4 items maximum. To retrieve all items
+ from a collection, you must use [`get_collection`].
+
+
+
+ Args:
+ owner (`List[str]` or `str`, *optional*):
+ Filter by owner's username.
+ item (`List[str]` or `str`, *optional*):
+ Filter collections containing a particular items. Example: `"models/teknium/OpenHermes-2.5-Mistral-7B"`, `"datasets/squad"` or `"papers/2311.12983"`.
+ sort (`Literal["lastModified", "trending", "upvotes"]`, *optional*):
+ Sort collections by last modified, trending or upvotes.
+ limit (`int`, *optional*):
+ Maximum number of collections to be returned.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[Collection]`: an iterable of [`Collection`] objects.
+ """
+ # Construct the API endpoint
+ path = f"{self.endpoint}/api/collections"
+ headers = self._build_hf_headers(token=token)
+ params: Dict = {}
+ if owner is not None:
+ params.update({"owner": owner})
+ if item is not None:
+ params.update({"item": item})
+ if sort is not None:
+ params.update({"sort": sort})
+ if limit is not None:
+ params.update({"limit": limit})
+
+ # Paginate over the results until limit is reached
+ items = paginate(path, headers=headers, params=params)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+
+ # Parse as Collection and return
+ for position, collection_data in enumerate(items):
+ yield Collection(position=position, **collection_data)
+
+ def get_collection(self, collection_slug: str, *, token: Union[bool, str, None] = None) -> Collection:
+ """Gets information about a Collection on the Hub.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection of the Hub. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns: [`Collection`]
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import get_collection
+ >>> collection = get_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026")
+ >>> collection.title
+ 'Recent models'
+ >>> len(collection.items)
+ 37
+ >>> collection.items[0]
+ CollectionItem(
+ item_object_id='651446103cd773a050bf64c2',
+ item_id='TheBloke/U-Amethyst-20B-AWQ',
+ item_type='model',
+ position=88,
+ note=None
+ )
+ ```
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/collections/{collection_slug}", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return Collection(**{**r.json(), "endpoint": self.endpoint})
+
+ def create_collection(
+ self,
+ title: str,
+ *,
+ namespace: Optional[str] = None,
+ description: Optional[str] = None,
+ private: bool = False,
+ exists_ok: bool = False,
+ token: Union[bool, str, None] = None,
+ ) -> Collection:
+ """Create a new Collection on the Hub.
+
+ Args:
+ title (`str`):
+ Title of the collection to create. Example: `"Recent models"`.
+ namespace (`str`, *optional*):
+ Namespace of the collection to create (username or org). Will default to the owner name.
+ description (`str`, *optional*):
+ Description of the collection to create.
+ private (`bool`, *optional*):
+ Whether the collection should be private or not. Defaults to `False` (i.e. public collection).
+ exists_ok (`bool`, *optional*):
+ If `True`, do not raise an error if collection already exists.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns: [`Collection`]
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import create_collection
+ >>> collection = create_collection(
+ ... title="ICCV 2023",
+ ... description="Portfolio of models, papers and demos I presented at ICCV 2023",
+ ... )
+ >>> collection.slug
+ "username/iccv-2023-64f9a55bb3115b4f513ec026"
+ ```
+ """
+ if namespace is None:
+ namespace = self.whoami(token)["name"]
+
+ payload = {
+ "title": title,
+ "namespace": namespace,
+ "private": private,
+ }
+ if description is not None:
+ payload["description"] = description
+
+ r = get_session().post(
+ f"{self.endpoint}/api/collections", headers=self._build_hf_headers(token=token), json=payload
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exists_ok and err.response.status_code == 409:
+ # Collection already exists and `exists_ok=True`
+ slug = r.json()["slug"]
+ return self.get_collection(slug, token=token)
+ else:
+ raise
+ return Collection(**{**r.json(), "endpoint": self.endpoint})
+
+ def update_collection_metadata(
+ self,
+ collection_slug: str,
+ *,
+ title: Optional[str] = None,
+ description: Optional[str] = None,
+ position: Optional[int] = None,
+ private: Optional[bool] = None,
+ theme: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Collection:
+ """Update metadata of a collection on the Hub.
+
+ All arguments are optional. Only provided metadata will be updated.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ title (`str`):
+ Title of the collection to update.
+ description (`str`, *optional*):
+ Description of the collection to update.
+ position (`int`, *optional*):
+ New position of the collection in the list of collections of the user.
+ private (`bool`, *optional*):
+ Whether the collection should be private or not.
+ theme (`str`, *optional*):
+ Theme of the collection on the Hub.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns: [`Collection`]
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import update_collection_metadata
+ >>> collection = update_collection_metadata(
+ ... collection_slug="username/iccv-2023-64f9a55bb3115b4f513ec026",
+ ... title="ICCV Oct. 2023"
+ ... description="Portfolio of models, datasets, papers and demos I presented at ICCV Oct. 2023",
+ ... private=False,
+ ... theme="pink",
+ ... )
+ >>> collection.slug
+ "username/iccv-oct-2023-64f9a55bb3115b4f513ec026"
+ # ^collection slug got updated but not the trailing ID
+ ```
+ """
+ payload = {
+ "position": position,
+ "private": private,
+ "theme": theme,
+ "title": title,
+ "description": description,
+ }
+ r = get_session().patch(
+ f"{self.endpoint}/api/collections/{collection_slug}",
+ headers=self._build_hf_headers(token=token),
+ # Only send not-none values to the API
+ json={key: value for key, value in payload.items() if value is not None},
+ )
+ hf_raise_for_status(r)
+ return Collection(**{**r.json()["data"], "endpoint": self.endpoint})
+
+ def delete_collection(
+ self, collection_slug: str, *, missing_ok: bool = False, token: Union[bool, str, None] = None
+ ) -> None:
+ """Delete a collection on the Hub.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to delete. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ missing_ok (`bool`, *optional*):
+ If `True`, do not raise an error if collection doesn't exists.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import delete_collection
+ >>> collection = delete_collection("username/useless-collection-64f9a55bb3115b4f513ec026", missing_ok=True)
+ ```
+
+
+
+ This is a non-revertible action. A deleted collection cannot be restored.
+
+
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/collections/{collection_slug}", headers=self._build_hf_headers(token=token)
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if missing_ok and err.response.status_code == 404:
+ # Collection doesn't exists and `missing_ok=True`
+ return
+ else:
+ raise
+
+ def add_collection_item(
+ self,
+ collection_slug: str,
+ item_id: str,
+ item_type: CollectionItemType_T,
+ *,
+ note: Optional[str] = None,
+ exists_ok: bool = False,
+ token: Union[bool, str, None] = None,
+ ) -> Collection:
+ """Add an item to a collection on the Hub.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ item_id (`str`):
+ ID of the item to add to the collection. It can be the ID of a repo on the Hub (e.g. `"facebook/bart-large-mnli"`)
+ or a paper id (e.g. `"2307.09288"`).
+ item_type (`str`):
+ Type of the item to add. Can be one of `"model"`, `"dataset"`, `"space"` or `"paper"`.
+ note (`str`, *optional*):
+ A note to attach to the item in the collection. The maximum size for a note is 500 characters.
+ exists_ok (`bool`, *optional*):
+ If `True`, do not raise an error if item already exists.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns: [`Collection`]
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the item you try to add to the collection does not exist on the Hub.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 409 if the item you try to add to the collection is already in the collection (and exists_ok=False)
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import add_collection_item
+ >>> collection = add_collection_item(
+ ... collection_slug="davanstrien/climate-64f99dc2a5067f6b65531bab",
+ ... item_id="pierre-loic/climate-news-articles",
+ ... item_type="dataset"
+ ... )
+ >>> collection.items[-1].item_id
+ "pierre-loic/climate-news-articles"
+ # ^item got added to the collection on last position
+
+ # Add item with a note
+ >>> add_collection_item(
+ ... collection_slug="davanstrien/climate-64f99dc2a5067f6b65531bab",
+ ... item_id="datasets/climate_fever",
+ ... item_type="dataset"
+ ... note="This dataset adopts the FEVER methodology that consists of 1,535 real-world claims regarding climate-change collected on the internet."
+ ... )
+ (...)
+ ```
+ """
+ payload: Dict[str, Any] = {"item": {"id": item_id, "type": item_type}}
+ if note is not None:
+ payload["note"] = note
+ r = get_session().post(
+ f"{self.endpoint}/api/collections/{collection_slug}/items",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exists_ok and err.response.status_code == 409:
+ # Item already exists and `exists_ok=True`
+ return self.get_collection(collection_slug, token=token)
+ else:
+ raise
+ return Collection(**{**r.json(), "endpoint": self.endpoint})
+
+ def update_collection_item(
+ self,
+ collection_slug: str,
+ item_object_id: str,
+ *,
+ note: Optional[str] = None,
+ position: Optional[int] = None,
+ token: Union[bool, str, None] = None,
+ ) -> None:
+ """Update an item in a collection.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ item_object_id (`str`):
+ ID of the item in the collection. This is not the id of the item on the Hub (repo_id or paper id).
+ It must be retrieved from a [`CollectionItem`] object. Example: `collection.items[0].item_object_id`.
+ note (`str`, *optional*):
+ A note to attach to the item in the collection. The maximum size for a note is 500 characters.
+ position (`int`, *optional*):
+ New position of the item in the collection.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import get_collection, update_collection_item
+
+ # Get collection first
+ >>> collection = get_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026")
+
+ # Update item based on its ID (add note + update position)
+ >>> update_collection_item(
+ ... collection_slug="TheBloke/recent-models-64f9a55bb3115b4f513ec026",
+ ... item_object_id=collection.items[-1].item_object_id,
+ ... note="Newly updated model!"
+ ... position=0,
+ ... )
+ ```
+ """
+ payload = {"position": position, "note": note}
+ r = get_session().patch(
+ f"{self.endpoint}/api/collections/{collection_slug}/items/{item_object_id}",
+ headers=self._build_hf_headers(token=token),
+ # Only send not-none values to the API
+ json={key: value for key, value in payload.items() if value is not None},
+ )
+ hf_raise_for_status(r)
+
+ def delete_collection_item(
+ self,
+ collection_slug: str,
+ item_object_id: str,
+ *,
+ missing_ok: bool = False,
+ token: Union[bool, str, None] = None,
+ ) -> None:
+ """Delete an item from a collection.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ item_object_id (`str`):
+ ID of the item in the collection. This is not the id of the item on the Hub (repo_id or paper id).
+ It must be retrieved from a [`CollectionItem`] object. Example: `collection.items[0].item_object_id`.
+ missing_ok (`bool`, *optional*):
+ If `True`, do not raise an error if item doesn't exists.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import get_collection, delete_collection_item
+
+ # Get collection first
+ >>> collection = get_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026")
+
+ # Delete item based on its ID
+ >>> delete_collection_item(
+ ... collection_slug="TheBloke/recent-models-64f9a55bb3115b4f513ec026",
+ ... item_object_id=collection.items[-1].item_object_id,
+ ... )
+ ```
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/collections/{collection_slug}/items/{item_object_id}",
+ headers=self._build_hf_headers(token=token),
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if missing_ok and err.response.status_code == 404:
+ # Item already deleted and `missing_ok=True`
+ return
+ else:
+ raise
+
+ ##########################
+ # Manage access requests #
+ ##########################
+
+ @validate_hf_hub_args
+ def list_pending_access_requests(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> List[AccessRequest]:
+ """
+ Get pending access requests for a given gated repo.
+
+ A pending request means the user has requested access to the repo but the request has not been processed yet.
+ If the approval mode is automatic, this list should be empty. Pending requests can be accepted or rejected
+ using [`accept_access_request`] and [`reject_access_request`].
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to get access requests for.
+ repo_type (`str`, *optional*):
+ The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `List[AccessRequest]`: A list of [`AccessRequest`] objects. Each time contains a `username`, `email`,
+ `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will
+ be populated with user's answers.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import list_pending_access_requests, accept_access_request
+
+ # List pending requests
+ >>> requests = list_pending_access_requests("meta-llama/Llama-2-7b")
+ >>> len(requests)
+ 411
+ >>> requests[0]
+ [
+ AccessRequest(
+ username='clem',
+ fullname='Clem 🤗',
+ email='***',
+ timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc),
+ status='pending',
+ fields=None,
+ ),
+ ...
+ ]
+
+ # Accept Clem's request
+ >>> accept_access_request("meta-llama/Llama-2-7b", "clem")
+ ```
+ """
+ return self._list_access_requests(repo_id, "pending", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def list_accepted_access_requests(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> List[AccessRequest]:
+ """
+ Get accepted access requests for a given gated repo.
+
+ An accepted request means the user has requested access to the repo and the request has been accepted. The user
+ can download any file of the repo. If the approval mode is automatic, this list should contains by default all
+ requests. Accepted requests can be cancelled or rejected at any time using [`cancel_access_request`] and
+ [`reject_access_request`]. A cancelled request will go back to the pending list while a rejected request will
+ go to the rejected list. In both cases, the user will lose access to the repo.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to get access requests for.
+ repo_type (`str`, *optional*):
+ The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `List[AccessRequest]`: A list of [`AccessRequest`] objects. Each time contains a `username`, `email`,
+ `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will
+ be populated with user's answers.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import list_accepted_access_requests
+
+ >>> requests = list_accepted_access_requests("meta-llama/Llama-2-7b")
+ >>> len(requests)
+ 411
+ >>> requests[0]
+ [
+ AccessRequest(
+ username='clem',
+ fullname='Clem 🤗',
+ email='***',
+ timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc),
+ status='accepted',
+ fields=None,
+ ),
+ ...
+ ]
+ ```
+ """
+ return self._list_access_requests(repo_id, "accepted", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def list_rejected_access_requests(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> List[AccessRequest]:
+ """
+ Get rejected access requests for a given gated repo.
+
+ A rejected request means the user has requested access to the repo and the request has been explicitly rejected
+ by a repo owner (either you or another user from your organization). The user cannot download any file of the
+ repo. Rejected requests can be accepted or cancelled at any time using [`accept_access_request`] and
+ [`cancel_access_request`]. A cancelled request will go back to the pending list while an accepted request will
+ go to the accepted list.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to get access requests for.
+ repo_type (`str`, *optional*):
+ The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `List[AccessRequest]`: A list of [`AccessRequest`] objects. Each time contains a `username`, `email`,
+ `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will
+ be populated with user's answers.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import list_rejected_access_requests
+
+ >>> requests = list_rejected_access_requests("meta-llama/Llama-2-7b")
+ >>> len(requests)
+ 411
+ >>> requests[0]
+ [
+ AccessRequest(
+ username='clem',
+ fullname='Clem 🤗',
+ email='***',
+ timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc),
+ status='rejected',
+ fields=None,
+ ),
+ ...
+ ]
+ ```
+ """
+ return self._list_access_requests(repo_id, "rejected", repo_type=repo_type, token=token)
+
+ def _list_access_requests(
+ self,
+ repo_id: str,
+ status: Literal["accepted", "rejected", "pending"],
+ repo_type: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> List[AccessRequest]:
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+
+ response = get_session().get(
+ f"{constants.ENDPOINT}/api/{repo_type}s/{repo_id}/user-access-request/{status}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ return [
+ AccessRequest(
+ username=request["user"]["user"],
+ fullname=request["user"]["fullname"],
+ email=request["user"].get("email"),
+ status=request["status"],
+ timestamp=parse_datetime(request["timestamp"]),
+ fields=request.get("fields"), # only if custom fields in form
+ )
+ for request in response.json()
+ ]
+
+ @validate_hf_hub_args
+ def cancel_access_request(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> None:
+ """
+ Cancel an access request from a user for a given gated repo.
+
+ A cancelled request will go back to the pending list and the user will lose access to the repo.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to cancel access request for.
+ user (`str`):
+ The username of the user which access request should be cancelled.
+ repo_type (`str`, *optional*):
+ The type of the repo to cancel access request for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user does not exist on the Hub.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user access request cannot be found.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user access request is already in the pending list.
+ """
+ self._handle_access_request(repo_id, user, "pending", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def accept_access_request(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> None:
+ """
+ Accept an access request from a user for a given gated repo.
+
+ Once the request is accepted, the user will be able to download any file of the repo and access the community
+ tab. If the approval mode is automatic, you don't have to accept requests manually. An accepted request can be
+ cancelled or rejected at any time using [`cancel_access_request`] and [`reject_access_request`].
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to accept access request for.
+ user (`str`):
+ The username of the user which access request should be accepted.
+ repo_type (`str`, *optional*):
+ The type of the repo to accept access request for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user does not exist on the Hub.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user access request cannot be found.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user access request is already in the accepted list.
+ """
+ self._handle_access_request(repo_id, user, "accepted", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def reject_access_request(
+ self,
+ repo_id: str,
+ user: str,
+ *,
+ repo_type: Optional[str] = None,
+ rejection_reason: Optional[str],
+ token: Union[bool, str, None] = None,
+ ) -> None:
+ """
+ Reject an access request from a user for a given gated repo.
+
+ A rejected request will go to the rejected list. The user cannot download any file of the repo. Rejected
+ requests can be accepted or cancelled at any time using [`accept_access_request`] and [`cancel_access_request`].
+ A cancelled request will go back to the pending list while an accepted request will go to the accepted list.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to reject access request for.
+ user (`str`):
+ The username of the user which access request should be rejected.
+ repo_type (`str`, *optional*):
+ The type of the repo to reject access request for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ rejection_reason (`str`, *optional*):
+ Optional rejection reason that will be visible to the user (max 200 characters).
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user does not exist on the Hub.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user access request cannot be found.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user access request is already in the rejected list.
+ """
+ self._handle_access_request(
+ repo_id, user, "rejected", repo_type=repo_type, rejection_reason=rejection_reason, token=token
+ )
+
+ @validate_hf_hub_args
+ def _handle_access_request(
+ self,
+ repo_id: str,
+ user: str,
+ status: Literal["accepted", "rejected", "pending"],
+ repo_type: Optional[str] = None,
+ rejection_reason: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> None:
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+
+ payload = {"user": user, "status": status}
+
+ if rejection_reason is not None:
+ if status != "rejected":
+ raise ValueError("`rejection_reason` can only be passed when rejecting an access request.")
+ payload["rejectionReason"] = rejection_reason
+
+ response = get_session().post(
+ f"{constants.ENDPOINT}/api/{repo_type}s/{repo_id}/user-access-request/handle",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def grant_access(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> None:
+ """
+ Grant access to a user for a given gated repo.
+
+ Granting access don't require for the user to send an access request by themselves. The user is automatically
+ added to the accepted list meaning they can download the files You can revoke the granted access at any time
+ using [`cancel_access_request`] or [`reject_access_request`].
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to grant access to.
+ user (`str`):
+ The username of the user to grant access.
+ repo_type (`str`, *optional*):
+ The type of the repo to grant access to. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the repo is not gated.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 400 if the user already has access to the repo.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 if the user does not exist on the Hub.
+ """
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+
+ response = get_session().post(
+ f"{constants.ENDPOINT}/api/{repo_type}s/{repo_id}/user-access-request/grant",
+ headers=self._build_hf_headers(token=token),
+ json={"user": user},
+ )
+ hf_raise_for_status(response)
+ return response.json()
+
+ ###################
+ # Manage webhooks #
+ ###################
+
+ @validate_hf_hub_args
+ def get_webhook(self, webhook_id: str, *, token: Union[bool, str, None] = None) -> WebhookInfo:
+ """Get a webhook by its id.
+
+ Args:
+ webhook_id (`str`):
+ The unique identifier of the webhook to get.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`WebhookInfo`]:
+ Info about the webhook.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import get_webhook
+ >>> webhook = get_webhook("654bbbc16f2ec14d77f109cc")
+ >>> print(webhook)
+ WebhookInfo(
+ id="654bbbc16f2ec14d77f109cc",
+ watched=[WebhookWatchedItem(type="user", name="julien-c"), WebhookWatchedItem(type="org", name="HuggingFaceH4")],
+ url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ secret="my-secret",
+ domains=["repo", "discussion"],
+ disabled=False,
+ )
+ ```
+ """
+ response = get_session().get(
+ f"{constants.ENDPOINT}/api/settings/webhooks/{webhook_id}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ webhook_data = response.json()["webhook"]
+
+ watched_items = [WebhookWatchedItem(type=item["type"], name=item["name"]) for item in webhook_data["watched"]]
+
+ webhook = WebhookInfo(
+ id=webhook_data["id"],
+ url=webhook_data["url"],
+ watched=watched_items,
+ domains=webhook_data["domains"],
+ secret=webhook_data.get("secret"),
+ disabled=webhook_data["disabled"],
+ )
+
+ return webhook
+
+ @validate_hf_hub_args
+ def list_webhooks(self, *, token: Union[bool, str, None] = None) -> List[WebhookInfo]:
+ """List all configured webhooks.
+
+ Args:
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `List[WebhookInfo]`:
+ List of webhook info objects.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import list_webhooks
+ >>> webhooks = list_webhooks()
+ >>> len(webhooks)
+ 2
+ >>> webhooks[0]
+ WebhookInfo(
+ id="654bbbc16f2ec14d77f109cc",
+ watched=[WebhookWatchedItem(type="user", name="julien-c"), WebhookWatchedItem(type="org", name="HuggingFaceH4")],
+ url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ secret="my-secret",
+ domains=["repo", "discussion"],
+ disabled=False,
+ )
+ ```
+ """
+ response = get_session().get(
+ f"{constants.ENDPOINT}/api/settings/webhooks",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ webhooks_data = response.json()
+
+ return [
+ WebhookInfo(
+ id=webhook["id"],
+ url=webhook["url"],
+ watched=[WebhookWatchedItem(type=item["type"], name=item["name"]) for item in webhook["watched"]],
+ domains=webhook["domains"],
+ secret=webhook.get("secret"),
+ disabled=webhook["disabled"],
+ )
+ for webhook in webhooks_data
+ ]
+
+ @validate_hf_hub_args
+ def create_webhook(
+ self,
+ *,
+ url: str,
+ watched: List[Union[Dict, WebhookWatchedItem]],
+ domains: Optional[List[constants.WEBHOOK_DOMAIN_T]] = None,
+ secret: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> WebhookInfo:
+ """Create a new webhook.
+
+ Args:
+ url (`str`):
+ URL to send the payload to.
+ watched (`List[WebhookWatchedItem]`):
+ List of [`WebhookWatchedItem`] to be watched by the webhook. It can be users, orgs, models, datasets or spaces.
+ Watched items can also be provided as plain dictionaries.
+ domains (`List[Literal["repo", "discussion"]]`, optional):
+ List of domains to watch. It can be "repo", "discussion" or both.
+ secret (`str`, optional):
+ A secret to sign the payload with.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`WebhookInfo`]:
+ Info about the newly created webhook.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import create_webhook
+ >>> payload = create_webhook(
+ ... watched=[{"type": "user", "name": "julien-c"}, {"type": "org", "name": "HuggingFaceH4"}],
+ ... url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ ... domains=["repo", "discussion"],
+ ... secret="my-secret",
+ ... )
+ >>> print(payload)
+ WebhookInfo(
+ id="654bbbc16f2ec14d77f109cc",
+ url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ watched=[WebhookWatchedItem(type="user", name="julien-c"), WebhookWatchedItem(type="org", name="HuggingFaceH4")],
+ domains=["repo", "discussion"],
+ secret="my-secret",
+ disabled=False,
+ )
+ ```
+ """
+ watched_dicts = [asdict(item) if isinstance(item, WebhookWatchedItem) else item for item in watched]
+
+ response = get_session().post(
+ f"{constants.ENDPOINT}/api/settings/webhooks",
+ json={"watched": watched_dicts, "url": url, "domains": domains, "secret": secret},
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ webhook_data = response.json()["webhook"]
+ watched_items = [WebhookWatchedItem(type=item["type"], name=item["name"]) for item in webhook_data["watched"]]
+
+ webhook = WebhookInfo(
+ id=webhook_data["id"],
+ url=webhook_data["url"],
+ watched=watched_items,
+ domains=webhook_data["domains"],
+ secret=webhook_data.get("secret"),
+ disabled=webhook_data["disabled"],
+ )
+
+ return webhook
+
+ @validate_hf_hub_args
+ def update_webhook(
+ self,
+ webhook_id: str,
+ *,
+ url: Optional[str] = None,
+ watched: Optional[List[Union[Dict, WebhookWatchedItem]]] = None,
+ domains: Optional[List[constants.WEBHOOK_DOMAIN_T]] = None,
+ secret: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> WebhookInfo:
+ """Update an existing webhook.
+
+ Args:
+ webhook_id (`str`):
+ The unique identifier of the webhook to be updated.
+ url (`str`, optional):
+ The URL to which the payload will be sent.
+ watched (`List[WebhookWatchedItem]`, optional):
+ List of items to watch. It can be users, orgs, models, datasets, or spaces.
+ Refer to [`WebhookWatchedItem`] for more details. Watched items can also be provided as plain dictionaries.
+ domains (`List[Literal["repo", "discussion"]]`, optional):
+ The domains to watch. This can include "repo", "discussion", or both.
+ secret (`str`, optional):
+ A secret to sign the payload with, providing an additional layer of security.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`WebhookInfo`]:
+ Info about the updated webhook.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import update_webhook
+ >>> updated_payload = update_webhook(
+ ... webhook_id="654bbbc16f2ec14d77f109cc",
+ ... url="https://new.webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ ... watched=[{"type": "user", "name": "julien-c"}, {"type": "org", "name": "HuggingFaceH4"}],
+ ... domains=["repo"],
+ ... secret="my-secret",
+ ... )
+ >>> print(updated_payload)
+ WebhookInfo(
+ id="654bbbc16f2ec14d77f109cc",
+ url="https://new.webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ watched=[WebhookWatchedItem(type="user", name="julien-c"), WebhookWatchedItem(type="org", name="HuggingFaceH4")],
+ domains=["repo"],
+ secret="my-secret",
+ disabled=False,
+ ```
+ """
+ if watched is None:
+ watched = []
+ watched_dicts = [asdict(item) if isinstance(item, WebhookWatchedItem) else item for item in watched]
+
+ response = get_session().post(
+ f"{constants.ENDPOINT}/api/settings/webhooks/{webhook_id}",
+ json={"watched": watched_dicts, "url": url, "domains": domains, "secret": secret},
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ webhook_data = response.json()["webhook"]
+
+ watched_items = [WebhookWatchedItem(type=item["type"], name=item["name"]) for item in webhook_data["watched"]]
+
+ webhook = WebhookInfo(
+ id=webhook_data["id"],
+ url=webhook_data["url"],
+ watched=watched_items,
+ domains=webhook_data["domains"],
+ secret=webhook_data.get("secret"),
+ disabled=webhook_data["disabled"],
+ )
+
+ return webhook
+
+ @validate_hf_hub_args
+ def enable_webhook(self, webhook_id: str, *, token: Union[bool, str, None] = None) -> WebhookInfo:
+ """Enable a webhook (makes it "active").
+
+ Args:
+ webhook_id (`str`):
+ The unique identifier of the webhook to enable.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`WebhookInfo`]:
+ Info about the enabled webhook.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import enable_webhook
+ >>> enabled_webhook = enable_webhook("654bbbc16f2ec14d77f109cc")
+ >>> enabled_webhook
+ WebhookInfo(
+ id="654bbbc16f2ec14d77f109cc",
+ url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ watched=[WebhookWatchedItem(type="user", name="julien-c"), WebhookWatchedItem(type="org", name="HuggingFaceH4")],
+ domains=["repo", "discussion"],
+ secret="my-secret",
+ disabled=False,
+ )
+ ```
+ """
+ response = get_session().post(
+ f"{constants.ENDPOINT}/api/settings/webhooks/{webhook_id}/enable",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ webhook_data = response.json()["webhook"]
+
+ watched_items = [WebhookWatchedItem(type=item["type"], name=item["name"]) for item in webhook_data["watched"]]
+
+ webhook = WebhookInfo(
+ id=webhook_data["id"],
+ url=webhook_data["url"],
+ watched=watched_items,
+ domains=webhook_data["domains"],
+ secret=webhook_data.get("secret"),
+ disabled=webhook_data["disabled"],
+ )
+
+ return webhook
+
+ @validate_hf_hub_args
+ def disable_webhook(self, webhook_id: str, *, token: Union[bool, str, None] = None) -> WebhookInfo:
+ """Disable a webhook (makes it "disabled").
+
+ Args:
+ webhook_id (`str`):
+ The unique identifier of the webhook to disable.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ [`WebhookInfo`]:
+ Info about the disabled webhook.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import disable_webhook
+ >>> disabled_webhook = disable_webhook("654bbbc16f2ec14d77f109cc")
+ >>> disabled_webhook
+ WebhookInfo(
+ id="654bbbc16f2ec14d77f109cc",
+ url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548",
+ watched=[WebhookWatchedItem(type="user", name="julien-c"), WebhookWatchedItem(type="org", name="HuggingFaceH4")],
+ domains=["repo", "discussion"],
+ secret="my-secret",
+ disabled=True,
+ )
+ ```
+ """
+ response = get_session().post(
+ f"{constants.ENDPOINT}/api/settings/webhooks/{webhook_id}/disable",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ webhook_data = response.json()["webhook"]
+
+ watched_items = [WebhookWatchedItem(type=item["type"], name=item["name"]) for item in webhook_data["watched"]]
+
+ webhook = WebhookInfo(
+ id=webhook_data["id"],
+ url=webhook_data["url"],
+ watched=watched_items,
+ domains=webhook_data["domains"],
+ secret=webhook_data.get("secret"),
+ disabled=webhook_data["disabled"],
+ )
+
+ return webhook
+
+ @validate_hf_hub_args
+ def delete_webhook(self, webhook_id: str, *, token: Union[bool, str, None] = None) -> None:
+ """Delete a webhook.
+
+ Args:
+ webhook_id (`str`):
+ The unique identifier of the webhook to delete.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved token, which is the recommended
+ method for authentication (see https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `None`
+
+ Example:
+ ```python
+ >>> from huggingface_hub import delete_webhook
+ >>> delete_webhook("654bbbc16f2ec14d77f109cc")
+ ```
+ """
+ response = get_session().delete(
+ f"{constants.ENDPOINT}/api/settings/webhooks/{webhook_id}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ #############
+ # Internals #
+ #############
+
+ def _build_hf_headers(
+ self,
+ token: Union[bool, str, None] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ ) -> Dict[str, str]:
+ """
+ Alias for [`build_hf_headers`] that uses the token from [`HfApi`] client
+ when `token` is not provided.
+ """
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+ return build_hf_headers(
+ token=token,
+ library_name=library_name or self.library_name,
+ library_version=library_version or self.library_version,
+ user_agent=user_agent or self.user_agent,
+ headers=self.headers,
+ )
+
+ def _prepare_folder_deletions(
+ self,
+ repo_id: str,
+ repo_type: Optional[str],
+ revision: Optional[str],
+ path_in_repo: str,
+ delete_patterns: Optional[Union[List[str], str]],
+ token: Union[bool, str, None] = None,
+ ) -> List[CommitOperationDelete]:
+ """Generate the list of Delete operations for a commit to delete files from a repo.
+
+ List remote files and match them against the `delete_patterns` constraints. Returns a list of [`CommitOperationDelete`]
+ with the matching items.
+
+ Note: `.gitattributes` file is essential to make a repo work properly on the Hub. This file will always be
+ kept even if it matches the `delete_patterns` constraints.
+ """
+ if delete_patterns is None:
+ # If no delete patterns, no need to list and filter remote files
+ return []
+
+ # List remote files
+ filenames = self.list_repo_files(repo_id=repo_id, revision=revision, repo_type=repo_type, token=token)
+
+ # Compute relative path in repo
+ if path_in_repo and path_in_repo not in (".", "./"):
+ path_in_repo = path_in_repo.strip("/") + "/" # harmonize
+ relpath_to_abspath = {
+ file[len(path_in_repo) :]: file for file in filenames if file.startswith(path_in_repo)
+ }
+ else:
+ relpath_to_abspath = {file: file for file in filenames}
+
+ # Apply filter on relative paths and return
+ return [
+ CommitOperationDelete(path_in_repo=relpath_to_abspath[relpath], is_folder=False)
+ for relpath in filter_repo_objects(relpath_to_abspath.keys(), allow_patterns=delete_patterns)
+ if relpath_to_abspath[relpath] != ".gitattributes"
+ ]
+
+ def _prepare_upload_folder_additions(
+ self,
+ folder_path: Union[str, Path],
+ path_in_repo: str,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> List[CommitOperationAdd]:
+ """Generate the list of Add operations for a commit to upload a folder.
+
+ Files not matching the `allow_patterns` (allowlist) and `ignore_patterns` (denylist)
+ constraints are discarded.
+ """
+
+ folder_path = Path(folder_path).expanduser().resolve()
+ if not folder_path.is_dir():
+ raise ValueError(f"Provided path: '{folder_path}' is not a directory")
+
+ # List files from folder
+ relpath_to_abspath = {
+ path.relative_to(folder_path).as_posix(): path
+ for path in sorted(folder_path.glob("**/*")) # sorted to be deterministic
+ if path.is_file()
+ }
+
+ # Filter files
+ # Patterns are applied on the path relative to `folder_path`. `path_in_repo` is prefixed after the filtering.
+ filtered_repo_objects = list(
+ filter_repo_objects(
+ relpath_to_abspath.keys(), allow_patterns=allow_patterns, ignore_patterns=ignore_patterns
+ )
+ )
+
+ prefix = f"{path_in_repo.strip('/')}/" if path_in_repo else ""
+
+ # If updating a README.md file, make sure the metadata format is valid
+ # It's better to fail early than to fail after all the files have been hashed.
+ if "README.md" in filtered_repo_objects:
+ self._validate_yaml(
+ content=relpath_to_abspath["README.md"].read_text(encoding="utf8"),
+ repo_type=repo_type,
+ token=token,
+ )
+ if len(filtered_repo_objects) > 30:
+ log = logger.warning if len(filtered_repo_objects) > 200 else logger.info
+ log(
+ "It seems you are trying to upload a large folder at once. This might take some time and then fail if "
+ "the folder is too large. For such cases, it is recommended to upload in smaller batches or to use "
+ "`HfApi().upload_large_folder(...)`/`huggingface-cli upload-large-folder` instead. For more details, "
+ "check out https://huggingface.co/docs/huggingface_hub/main/en/guides/upload#upload-a-large-folder."
+ )
+
+ logger.info(f"Start hashing {len(filtered_repo_objects)} files.")
+ operations = [
+ CommitOperationAdd(
+ path_or_fileobj=relpath_to_abspath[relpath], # absolute path on disk
+ path_in_repo=prefix + relpath, # "absolute" path in repo
+ )
+ for relpath in filtered_repo_objects
+ ]
+ logger.info(f"Finished hashing {len(filtered_repo_objects)} files.")
+ return operations
+
+ def _validate_yaml(self, content: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None):
+ """
+ Validate YAML from `README.md`, used before file hashing and upload.
+
+ Args:
+ content (`str`):
+ Content of `README.md` to validate.
+ repo_type (`str`, *optional*):
+ The type of the repo to grant access to. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Raises:
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if YAML is invalid
+ """
+ repo_type = repo_type if repo_type is not None else constants.REPO_TYPE_MODEL
+ headers = self._build_hf_headers(token=token)
+
+ response = get_session().post(
+ f"{self.endpoint}/api/validate-yaml",
+ json={"content": content, "repoType": repo_type},
+ headers=headers,
+ )
+ # Handle warnings (example: empty metadata)
+ response_content = response.json()
+ message = "\n".join([f"- {warning.get('message')}" for warning in response_content.get("warnings", [])])
+ if message:
+ warnings.warn(f"Warnings while validating metadata in README.md:\n{message}")
+
+ # Raise on errors
+ try:
+ hf_raise_for_status(response)
+ except BadRequestError as e:
+ errors = response_content.get("errors", [])
+ message = "\n".join([f"- {error.get('message')}" for error in errors])
+ raise ValueError(f"Invalid metadata in README.md.\n{message}") from e
+
+ def get_user_overview(self, username: str, token: Union[bool, str, None] = None) -> User:
+ """
+ Get an overview of a user on the Hub.
+
+ Args:
+ username (`str`):
+ Username of the user to get an overview of.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `User`: A [`User`] object with the user's overview.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 If the user does not exist on the Hub.
+ """
+ r = get_session().get(
+ f"{constants.ENDPOINT}/api/users/{username}/overview", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return User(**r.json())
+
+ def list_organization_members(self, organization: str, token: Union[bool, str, None] = None) -> Iterable[User]:
+ """
+ List of members of an organization on the Hub.
+
+ Args:
+ organization (`str`):
+ Name of the organization to get the members of.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[User]`: A list of [`User`] objects with the members of the organization.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 If the organization does not exist on the Hub.
+
+ """
+ for member in paginate(
+ path=f"{constants.ENDPOINT}/api/organizations/{organization}/members",
+ params={},
+ headers=self._build_hf_headers(token=token),
+ ):
+ yield User(**member)
+
+ def list_user_followers(self, username: str, token: Union[bool, str, None] = None) -> Iterable[User]:
+ """
+ Get the list of followers of a user on the Hub.
+
+ Args:
+ username (`str`):
+ Username of the user to get the followers of.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[User]`: A list of [`User`] objects with the followers of the user.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 If the user does not exist on the Hub.
+
+ """
+ for follower in paginate(
+ path=f"{constants.ENDPOINT}/api/users/{username}/followers",
+ params={},
+ headers=self._build_hf_headers(token=token),
+ ):
+ yield User(**follower)
+
+ def list_user_following(self, username: str, token: Union[bool, str, None] = None) -> Iterable[User]:
+ """
+ Get the list of users followed by a user on the Hub.
+
+ Args:
+ username (`str`):
+ Username of the user to get the users followed by.
+ token (Union[bool, str, None], optional):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[User]`: A list of [`User`] objects with the users followed by the user.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 If the user does not exist on the Hub.
+
+ """
+ for followed_user in paginate(
+ path=f"{constants.ENDPOINT}/api/users/{username}/following",
+ params={},
+ headers=self._build_hf_headers(token=token),
+ ):
+ yield User(**followed_user)
+
+ def list_papers(
+ self,
+ *,
+ query: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ ) -> Iterable[PaperInfo]:
+ """
+ List daily papers on the Hugging Face Hub given a search query.
+
+ Args:
+ query (`str`, *optional*):
+ A search query string to find papers.
+ If provided, returns papers that match the query.
+ token (Union[bool, str, None], *optional*):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+
+ Returns:
+ `Iterable[PaperInfo]`: an iterable of [`huggingface_hub.hf_api.PaperInfo`] objects.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ # List all papers with "attention" in their title
+ >>> api.list_papers(query="attention")
+ ```
+ """
+ path = f"{self.endpoint}/api/papers/search"
+ params = {}
+ if query:
+ params["q"] = query
+ r = get_session().get(
+ path,
+ params=params,
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(r)
+ for paper in r.json():
+ yield PaperInfo(**paper)
+
+ def paper_info(self, id: str) -> PaperInfo:
+ """
+ Get information for a paper on the Hub.
+
+ Args:
+ id (`str`, **optional**):
+ ArXiv id of the paper.
+
+ Returns:
+ `PaperInfo`: A `PaperInfo` object.
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError):
+ HTTP 404 If the paper does not exist on the Hub.
+ """
+ path = f"{self.endpoint}/api/papers/{id}"
+ r = get_session().get(path)
+ hf_raise_for_status(r)
+ return PaperInfo(**r.json())
+
+ def auth_check(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Union[bool, str, None] = None
+ ) -> None:
+ """
+ Check if the provided user token has access to a specific repository on the Hugging Face Hub.
+
+ This method verifies whether the user, authenticated via the provided token, has access to the specified
+ repository. If the repository is not found or if the user lacks the required permissions to access it,
+ the method raises an appropriate exception.
+
+ Args:
+ repo_id (`str`):
+ The repository to check for access. Format should be `"user/repo_name"`.
+ Example: `"user/my-cool-model"`.
+
+ repo_type (`str`, *optional*):
+ The type of the repository. Should be one of `"model"`, `"dataset"`, or `"space"`.
+ If not specified, the default is `"model"`.
+
+ token `(Union[bool, str, None]`, *optional*):
+ A valid user access token. If not provided, the locally saved token will be used, which is the
+ recommended authentication method. Set to `False` to disable authentication.
+ Refer to: https://huggingface.co/docs/huggingface_hub/quick-start#authentication.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ Raised if the repository does not exist, is private, or the user does not have access. This can
+ occur if the `repo_id` or `repo_type` is incorrect or if the repository is private but the user
+ is not authenticated.
+
+ [`~utils.GatedRepoError`]:
+ Raised if the repository exists but is gated and the user is not authorized to access it.
+
+ Example:
+ Check if the user has access to a repository:
+
+ ```python
+ >>> from huggingface_hub import auth_check
+ >>> from huggingface_hub.utils import GatedRepoError, RepositoryNotFoundError
+
+ try:
+ auth_check("user/my-cool-model")
+ except GatedRepoError:
+ # Handle gated repository error
+ print("You do not have permission to access this gated repository.")
+ except RepositoryNotFoundError:
+ # Handle repository not found error
+ print("The repository was not found or you do not have access.")
+ ```
+
+ In this example:
+ - If the user has access, the method completes successfully.
+ - If the repository is gated or does not exist, appropriate exceptions are raised, allowing the user
+ to handle them accordingly.
+ """
+ headers = self._build_hf_headers(token=token)
+ if repo_type is None:
+ repo_type = constants.REPO_TYPE_MODEL
+ if repo_type not in constants.REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/auth-check"
+ r = get_session().get(path, headers=headers)
+ hf_raise_for_status(r)
+
+
+def _parse_revision_from_pr_url(pr_url: str) -> str:
+ """Safely parse revision number from a PR url.
+
+ Example:
+ ```py
+ >>> _parse_revision_from_pr_url("https://huggingface.co/bigscience/bloom/discussions/2")
+ "refs/pr/2"
+ ```
+ """
+ re_match = re.match(_REGEX_DISCUSSION_URL, pr_url)
+ if re_match is None:
+ raise RuntimeError(f"Unexpected response from the hub, expected a Pull Request URL but got: '{pr_url}'")
+ return f"refs/pr/{re_match[1]}"
+
+
+api = HfApi()
+
+whoami = api.whoami
+auth_check = api.auth_check
+get_token_permission = api.get_token_permission
+
+list_models = api.list_models
+model_info = api.model_info
+
+list_datasets = api.list_datasets
+dataset_info = api.dataset_info
+
+list_spaces = api.list_spaces
+space_info = api.space_info
+
+list_papers = api.list_papers
+paper_info = api.paper_info
+
+repo_exists = api.repo_exists
+revision_exists = api.revision_exists
+file_exists = api.file_exists
+repo_info = api.repo_info
+list_repo_files = api.list_repo_files
+list_repo_refs = api.list_repo_refs
+list_repo_commits = api.list_repo_commits
+list_repo_tree = api.list_repo_tree
+get_paths_info = api.get_paths_info
+
+get_model_tags = api.get_model_tags
+get_dataset_tags = api.get_dataset_tags
+
+create_commit = api.create_commit
+create_repo = api.create_repo
+delete_repo = api.delete_repo
+update_repo_visibility = api.update_repo_visibility
+update_repo_settings = api.update_repo_settings
+super_squash_history = api.super_squash_history
+move_repo = api.move_repo
+upload_file = api.upload_file
+upload_folder = api.upload_folder
+delete_file = api.delete_file
+delete_folder = api.delete_folder
+delete_files = api.delete_files
+upload_large_folder = api.upload_large_folder
+preupload_lfs_files = api.preupload_lfs_files
+create_branch = api.create_branch
+delete_branch = api.delete_branch
+create_tag = api.create_tag
+delete_tag = api.delete_tag
+get_full_repo_name = api.get_full_repo_name
+
+# Safetensors helpers
+get_safetensors_metadata = api.get_safetensors_metadata
+parse_safetensors_file_metadata = api.parse_safetensors_file_metadata
+
+# Background jobs
+run_as_future = api.run_as_future
+
+# Activity API
+list_liked_repos = api.list_liked_repos
+list_repo_likers = api.list_repo_likers
+unlike = api.unlike
+
+# Community API
+get_discussion_details = api.get_discussion_details
+get_repo_discussions = api.get_repo_discussions
+create_discussion = api.create_discussion
+create_pull_request = api.create_pull_request
+change_discussion_status = api.change_discussion_status
+comment_discussion = api.comment_discussion
+edit_discussion_comment = api.edit_discussion_comment
+rename_discussion = api.rename_discussion
+merge_pull_request = api.merge_pull_request
+
+# Space API
+add_space_secret = api.add_space_secret
+delete_space_secret = api.delete_space_secret
+get_space_variables = api.get_space_variables
+add_space_variable = api.add_space_variable
+delete_space_variable = api.delete_space_variable
+get_space_runtime = api.get_space_runtime
+request_space_hardware = api.request_space_hardware
+set_space_sleep_time = api.set_space_sleep_time
+pause_space = api.pause_space
+restart_space = api.restart_space
+duplicate_space = api.duplicate_space
+request_space_storage = api.request_space_storage
+delete_space_storage = api.delete_space_storage
+
+# Inference Endpoint API
+list_inference_endpoints = api.list_inference_endpoints
+create_inference_endpoint = api.create_inference_endpoint
+get_inference_endpoint = api.get_inference_endpoint
+update_inference_endpoint = api.update_inference_endpoint
+delete_inference_endpoint = api.delete_inference_endpoint
+pause_inference_endpoint = api.pause_inference_endpoint
+resume_inference_endpoint = api.resume_inference_endpoint
+scale_to_zero_inference_endpoint = api.scale_to_zero_inference_endpoint
+
+# Collections API
+get_collection = api.get_collection
+list_collections = api.list_collections
+create_collection = api.create_collection
+update_collection_metadata = api.update_collection_metadata
+delete_collection = api.delete_collection
+add_collection_item = api.add_collection_item
+update_collection_item = api.update_collection_item
+delete_collection_item = api.delete_collection_item
+delete_collection_item = api.delete_collection_item
+
+# Access requests API
+list_pending_access_requests = api.list_pending_access_requests
+list_accepted_access_requests = api.list_accepted_access_requests
+list_rejected_access_requests = api.list_rejected_access_requests
+cancel_access_request = api.cancel_access_request
+accept_access_request = api.accept_access_request
+reject_access_request = api.reject_access_request
+grant_access = api.grant_access
+
+# Webhooks API
+create_webhook = api.create_webhook
+disable_webhook = api.disable_webhook
+delete_webhook = api.delete_webhook
+enable_webhook = api.enable_webhook
+get_webhook = api.get_webhook
+list_webhooks = api.list_webhooks
+update_webhook = api.update_webhook
+
+
+# User API
+get_user_overview = api.get_user_overview
+list_organization_members = api.list_organization_members
+list_user_followers = api.list_user_followers
+list_user_following = api.list_user_following
diff --git a/env/Lib/site-packages/huggingface_hub/hf_file_system.py b/env/Lib/site-packages/huggingface_hub/hf_file_system.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e70a66a90628ccf10102920315891676733471b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/hf_file_system.py
@@ -0,0 +1,1140 @@
+import os
+import re
+import tempfile
+from collections import deque
+from dataclasses import dataclass, field
+from datetime import datetime
+from itertools import chain
+from pathlib import Path
+from typing import Any, Dict, Iterator, List, NoReturn, Optional, Tuple, Union
+from urllib.parse import quote, unquote
+
+import fsspec
+from fsspec.callbacks import _DEFAULT_CALLBACK, NoOpCallback, TqdmCallback
+from fsspec.utils import isfilelike
+from requests import Response
+
+from . import constants
+from ._commit_api import CommitOperationCopy, CommitOperationDelete
+from .errors import EntryNotFoundError, RepositoryNotFoundError, RevisionNotFoundError
+from .file_download import hf_hub_url, http_get
+from .hf_api import HfApi, LastCommitInfo, RepoFile
+from .utils import HFValidationError, hf_raise_for_status, http_backoff
+
+
+# Regex used to match special revisions with "/" in them (see #1710)
+SPECIAL_REFS_REVISION_REGEX = re.compile(
+ r"""
+ (^refs\/convert\/\w+) # `refs/convert/parquet` revisions
+ |
+ (^refs\/pr\/\d+) # PR revisions
+ """,
+ re.VERBOSE,
+)
+
+
+@dataclass
+class HfFileSystemResolvedPath:
+ """Data structure containing information about a resolved Hugging Face file system path."""
+
+ repo_type: str
+ repo_id: str
+ revision: str
+ path_in_repo: str
+ # The part placed after '@' in the initial path. It can be a quoted or unquoted refs revision.
+ # Used to reconstruct the unresolved path to return to the user.
+ _raw_revision: Optional[str] = field(default=None, repr=False)
+
+ def unresolve(self) -> str:
+ repo_path = constants.REPO_TYPES_URL_PREFIXES.get(self.repo_type, "") + self.repo_id
+ if self._raw_revision:
+ return f"{repo_path}@{self._raw_revision}/{self.path_in_repo}".rstrip("/")
+ elif self.revision != constants.DEFAULT_REVISION:
+ return f"{repo_path}@{safe_revision(self.revision)}/{self.path_in_repo}".rstrip("/")
+ else:
+ return f"{repo_path}/{self.path_in_repo}".rstrip("/")
+
+
+class HfFileSystem(fsspec.AbstractFileSystem):
+ """
+ Access a remote Hugging Face Hub repository as if were a local file system.
+
+
+
+ [`HfFileSystem`] provides fsspec compatibility, which is useful for libraries that require it (e.g., reading
+ Hugging Face datasets directly with `pandas`). However, it introduces additional overhead due to this compatibility
+ layer. For better performance and reliability, it's recommended to use `HfApi` methods when possible.
+
+
+
+ Args:
+ token (`str` or `bool`, *optional*):
+ A valid user access token (string). Defaults to the locally saved
+ token, which is the recommended method for authentication (see
+ https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
+ To disable authentication, pass `False`.
+ endpoint (`str`, *optional*):
+ Endpoint of the Hub. Defaults to .
+ Usage:
+
+ ```python
+ >>> from huggingface_hub import HfFileSystem
+
+ >>> fs = HfFileSystem()
+
+ >>> # List files
+ >>> fs.glob("my-username/my-model/*.bin")
+ ['my-username/my-model/pytorch_model.bin']
+ >>> fs.ls("datasets/my-username/my-dataset", detail=False)
+ ['datasets/my-username/my-dataset/.gitattributes', 'datasets/my-username/my-dataset/README.md', 'datasets/my-username/my-dataset/data.json']
+
+ >>> # Read/write files
+ >>> with fs.open("my-username/my-model/pytorch_model.bin") as f:
+ ... data = f.read()
+ >>> with fs.open("my-username/my-model/pytorch_model.bin", "wb") as f:
+ ... f.write(data)
+ ```
+ """
+
+ root_marker = ""
+ protocol = "hf"
+
+ def __init__(
+ self,
+ *args,
+ endpoint: Optional[str] = None,
+ token: Union[bool, str, None] = None,
+ **storage_options,
+ ):
+ super().__init__(*args, **storage_options)
+ self.endpoint = endpoint or constants.ENDPOINT
+ self.token = token
+ self._api = HfApi(endpoint=endpoint, token=token)
+ # Maps (repo_type, repo_id, revision) to a 2-tuple with:
+ # * the 1st element indicating whether the repositoy and the revision exist
+ # * the 2nd element being the exception raised if the repository or revision doesn't exist
+ self._repo_and_revision_exists_cache: Dict[
+ Tuple[str, str, Optional[str]], Tuple[bool, Optional[Exception]]
+ ] = {}
+
+ def _repo_and_revision_exist(
+ self, repo_type: str, repo_id: str, revision: Optional[str]
+ ) -> Tuple[bool, Optional[Exception]]:
+ if (repo_type, repo_id, revision) not in self._repo_and_revision_exists_cache:
+ try:
+ self._api.repo_info(
+ repo_id, revision=revision, repo_type=repo_type, timeout=constants.HF_HUB_ETAG_TIMEOUT
+ )
+ except (RepositoryNotFoundError, HFValidationError) as e:
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = False, e
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = False, e
+ except RevisionNotFoundError as e:
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = False, e
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = True, None
+ else:
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = True, None
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = True, None
+ return self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)]
+
+ def resolve_path(self, path: str, revision: Optional[str] = None) -> HfFileSystemResolvedPath:
+ """
+ Resolve a Hugging Face file system path into its components.
+
+ Args:
+ path (`str`):
+ Path to resolve.
+ revision (`str`, *optional*):
+ The revision of the repo to resolve. Defaults to the revision specified in the path.
+
+ Returns:
+ [`HfFileSystemResolvedPath`]: Resolved path information containing `repo_type`, `repo_id`, `revision` and `path_in_repo`.
+
+ Raises:
+ `ValueError`:
+ If path contains conflicting revision information.
+ `NotImplementedError`:
+ If trying to list repositories.
+ """
+
+ def _align_revision_in_path_with_revision(
+ revision_in_path: Optional[str], revision: Optional[str]
+ ) -> Optional[str]:
+ if revision is not None:
+ if revision_in_path is not None and revision_in_path != revision:
+ raise ValueError(
+ f'Revision specified in path ("{revision_in_path}") and in `revision` argument ("{revision}")'
+ " are not the same."
+ )
+ else:
+ revision = revision_in_path
+ return revision
+
+ path = self._strip_protocol(path)
+ if not path:
+ # can't list repositories at root
+ raise NotImplementedError("Access to repositories lists is not implemented.")
+ elif path.split("/")[0] + "/" in constants.REPO_TYPES_URL_PREFIXES.values():
+ if "/" not in path:
+ # can't list repositories at the repository type level
+ raise NotImplementedError("Access to repositories lists is not implemented.")
+ repo_type, path = path.split("/", 1)
+ repo_type = constants.REPO_TYPES_MAPPING[repo_type]
+ else:
+ repo_type = constants.REPO_TYPE_MODEL
+ if path.count("/") > 0:
+ if "@" in path:
+ repo_id, revision_in_path = path.split("@", 1)
+ if "/" in revision_in_path:
+ match = SPECIAL_REFS_REVISION_REGEX.search(revision_in_path)
+ if match is not None and revision in (None, match.group()):
+ # Handle `refs/convert/parquet` and PR revisions separately
+ path_in_repo = SPECIAL_REFS_REVISION_REGEX.sub("", revision_in_path).lstrip("/")
+ revision_in_path = match.group()
+ else:
+ revision_in_path, path_in_repo = revision_in_path.split("/", 1)
+ else:
+ path_in_repo = ""
+ revision = _align_revision_in_path_with_revision(unquote(revision_in_path), revision)
+ repo_and_revision_exist, err = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ _raise_file_not_found(path, err)
+ else:
+ revision_in_path = None
+ repo_id_with_namespace = "/".join(path.split("/")[:2])
+ path_in_repo_with_namespace = "/".join(path.split("/")[2:])
+ repo_id_without_namespace = path.split("/")[0]
+ path_in_repo_without_namespace = "/".join(path.split("/")[1:])
+ repo_id = repo_id_with_namespace
+ path_in_repo = path_in_repo_with_namespace
+ repo_and_revision_exist, err = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ if isinstance(err, (RepositoryNotFoundError, HFValidationError)):
+ repo_id = repo_id_without_namespace
+ path_in_repo = path_in_repo_without_namespace
+ repo_and_revision_exist, _ = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ _raise_file_not_found(path, err)
+ else:
+ _raise_file_not_found(path, err)
+ else:
+ repo_id = path
+ path_in_repo = ""
+ if "@" in path:
+ repo_id, revision_in_path = path.split("@", 1)
+ revision = _align_revision_in_path_with_revision(unquote(revision_in_path), revision)
+ else:
+ revision_in_path = None
+ repo_and_revision_exist, _ = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ raise NotImplementedError("Access to repositories lists is not implemented.")
+
+ revision = revision if revision is not None else constants.DEFAULT_REVISION
+ return HfFileSystemResolvedPath(repo_type, repo_id, revision, path_in_repo, _raw_revision=revision_in_path)
+
+ def invalidate_cache(self, path: Optional[str] = None) -> None:
+ """
+ Clear the cache for a given path.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.invalidate_cache).
+
+ Args:
+ path (`str`, *optional*):
+ Path to clear from cache. If not provided, clear the entire cache.
+
+ """
+ if not path:
+ self.dircache.clear()
+ self._repo_and_revision_exists_cache.clear()
+ else:
+ resolved_path = self.resolve_path(path)
+ path = resolved_path.unresolve()
+ while path:
+ self.dircache.pop(path, None)
+ path = self._parent(path)
+
+ # Only clear repo cache if path is to repo root
+ if not resolved_path.path_in_repo:
+ self._repo_and_revision_exists_cache.pop((resolved_path.repo_type, resolved_path.repo_id, None), None)
+ self._repo_and_revision_exists_cache.pop(
+ (resolved_path.repo_type, resolved_path.repo_id, resolved_path.revision), None
+ )
+
+ def _open(
+ self,
+ path: str,
+ mode: str = "rb",
+ revision: Optional[str] = None,
+ block_size: Optional[int] = None,
+ **kwargs,
+ ) -> "HfFileSystemFile":
+ if "a" in mode:
+ raise NotImplementedError("Appending to remote files is not yet supported.")
+ if block_size == 0:
+ return HfFileSystemStreamFile(self, path, mode=mode, revision=revision, block_size=block_size, **kwargs)
+ else:
+ return HfFileSystemFile(self, path, mode=mode, revision=revision, block_size=block_size, **kwargs)
+
+ def _rm(self, path: str, revision: Optional[str] = None, **kwargs) -> None:
+ resolved_path = self.resolve_path(path, revision=revision)
+ self._api.delete_file(
+ path_in_repo=resolved_path.path_in_repo,
+ repo_id=resolved_path.repo_id,
+ token=self.token,
+ repo_type=resolved_path.repo_type,
+ revision=resolved_path.revision,
+ commit_message=kwargs.get("commit_message"),
+ commit_description=kwargs.get("commit_description"),
+ )
+ self.invalidate_cache(path=resolved_path.unresolve())
+
+ def rm(
+ self,
+ path: str,
+ recursive: bool = False,
+ maxdepth: Optional[int] = None,
+ revision: Optional[str] = None,
+ **kwargs,
+ ) -> None:
+ """
+ Delete files from a repository.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.rm).
+
+
+
+ Note: When possible, use `HfApi.delete_file()` for better performance.
+
+
+
+ Args:
+ path (`str`):
+ Path to delete.
+ recursive (`bool`, *optional*):
+ If True, delete directory and all its contents. Defaults to False.
+ maxdepth (`int`, *optional*):
+ Maximum number of subdirectories to visit when deleting recursively.
+ revision (`str`, *optional*):
+ The git revision to delete from.
+
+ """
+ resolved_path = self.resolve_path(path, revision=revision)
+ paths = self.expand_path(path, recursive=recursive, maxdepth=maxdepth, revision=revision)
+ paths_in_repo = [self.resolve_path(path).path_in_repo for path in paths if not self.isdir(path)]
+ operations = [CommitOperationDelete(path_in_repo=path_in_repo) for path_in_repo in paths_in_repo]
+ commit_message = f"Delete {path} "
+ commit_message += "recursively " if recursive else ""
+ commit_message += f"up to depth {maxdepth} " if maxdepth is not None else ""
+ # TODO: use `commit_description` to list all the deleted paths?
+ self._api.create_commit(
+ repo_id=resolved_path.repo_id,
+ repo_type=resolved_path.repo_type,
+ token=self.token,
+ operations=operations,
+ revision=resolved_path.revision,
+ commit_message=kwargs.get("commit_message", commit_message),
+ commit_description=kwargs.get("commit_description"),
+ )
+ self.invalidate_cache(path=resolved_path.unresolve())
+
+ def ls(
+ self, path: str, detail: bool = True, refresh: bool = False, revision: Optional[str] = None, **kwargs
+ ) -> List[Union[str, Dict[str, Any]]]:
+ """
+ List the contents of a directory.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.ls).
+
+
+
+ Note: When possible, use `HfApi.list_repo_tree()` for better performance.
+
+
+
+ Args:
+ path (`str`):
+ Path to the directory.
+ detail (`bool`, *optional*):
+ If True, returns a list of dictionaries containing file information. If False,
+ returns a list of file paths. Defaults to True.
+ refresh (`bool`, *optional*):
+ If True, bypass the cache and fetch the latest data. Defaults to False.
+ revision (`str`, *optional*):
+ The git revision to list from.
+
+ Returns:
+ `List[Union[str, Dict[str, Any]]]`: List of file paths (if detail=False) or list of file information
+ dictionaries (if detail=True).
+ """
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ kwargs = {"expand_info": detail, **kwargs}
+ try:
+ out = self._ls_tree(path, refresh=refresh, revision=revision, **kwargs)
+ except EntryNotFoundError:
+ # Path could be a file
+ if not resolved_path.path_in_repo:
+ _raise_file_not_found(path, None)
+ out = self._ls_tree(self._parent(path), refresh=refresh, revision=revision, **kwargs)
+ out = [o for o in out if o["name"] == path]
+ if len(out) == 0:
+ _raise_file_not_found(path, None)
+ return out if detail else [o["name"] for o in out]
+
+ def _ls_tree(
+ self,
+ path: str,
+ recursive: bool = False,
+ refresh: bool = False,
+ revision: Optional[str] = None,
+ expand_info: bool = True,
+ ):
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ root_path = HfFileSystemResolvedPath(
+ resolved_path.repo_type,
+ resolved_path.repo_id,
+ resolved_path.revision,
+ path_in_repo="",
+ _raw_revision=resolved_path._raw_revision,
+ ).unresolve()
+
+ out = []
+ if path in self.dircache and not refresh:
+ cached_path_infos = self.dircache[path]
+ out.extend(cached_path_infos)
+ dirs_not_in_dircache = []
+ if recursive:
+ # Use BFS to traverse the cache and build the "recursive "output
+ # (The Hub uses a so-called "tree first" strategy for the tree endpoint but we sort the output to follow the spec so the result is (eventually) the same)
+ dirs_to_visit = deque(
+ [path_info for path_info in cached_path_infos if path_info["type"] == "directory"]
+ )
+ while dirs_to_visit:
+ dir_info = dirs_to_visit.popleft()
+ if dir_info["name"] not in self.dircache:
+ dirs_not_in_dircache.append(dir_info["name"])
+ else:
+ cached_path_infos = self.dircache[dir_info["name"]]
+ out.extend(cached_path_infos)
+ dirs_to_visit.extend(
+ [path_info for path_info in cached_path_infos if path_info["type"] == "directory"]
+ )
+
+ dirs_not_expanded = []
+ if expand_info:
+ # Check if there are directories with non-expanded entries
+ dirs_not_expanded = [self._parent(o["name"]) for o in out if o["last_commit"] is None]
+
+ if (recursive and dirs_not_in_dircache) or (expand_info and dirs_not_expanded):
+ # If the dircache is incomplete, find the common path of the missing and non-expanded entries
+ # and extend the output with the result of `_ls_tree(common_path, recursive=True)`
+ common_prefix = os.path.commonprefix(dirs_not_in_dircache + dirs_not_expanded)
+ # Get the parent directory if the common prefix itself is not a directory
+ common_path = (
+ common_prefix.rstrip("/")
+ if common_prefix.endswith("/")
+ or common_prefix == root_path
+ or common_prefix in chain(dirs_not_in_dircache, dirs_not_expanded)
+ else self._parent(common_prefix)
+ )
+ out = [o for o in out if not o["name"].startswith(common_path + "/")]
+ for cached_path in self.dircache:
+ if cached_path.startswith(common_path + "/"):
+ self.dircache.pop(cached_path, None)
+ self.dircache.pop(common_path, None)
+ out.extend(
+ self._ls_tree(
+ common_path,
+ recursive=recursive,
+ refresh=True,
+ revision=revision,
+ expand_info=expand_info,
+ )
+ )
+ else:
+ tree = self._api.list_repo_tree(
+ resolved_path.repo_id,
+ resolved_path.path_in_repo,
+ recursive=recursive,
+ expand=expand_info,
+ revision=resolved_path.revision,
+ repo_type=resolved_path.repo_type,
+ )
+ for path_info in tree:
+ if isinstance(path_info, RepoFile):
+ cache_path_info = {
+ "name": root_path + "/" + path_info.path,
+ "size": path_info.size,
+ "type": "file",
+ "blob_id": path_info.blob_id,
+ "lfs": path_info.lfs,
+ "last_commit": path_info.last_commit,
+ "security": path_info.security,
+ }
+ else:
+ cache_path_info = {
+ "name": root_path + "/" + path_info.path,
+ "size": 0,
+ "type": "directory",
+ "tree_id": path_info.tree_id,
+ "last_commit": path_info.last_commit,
+ }
+ parent_path = self._parent(cache_path_info["name"])
+ self.dircache.setdefault(parent_path, []).append(cache_path_info)
+ out.append(cache_path_info)
+ return out
+
+ def walk(self, path: str, *args, **kwargs) -> Iterator[Tuple[str, List[str], List[str]]]:
+ """
+ Return all files below the given path.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.walk).
+
+ Args:
+ path (`str`):
+ Root path to list files from.
+
+ Returns:
+ `Iterator[Tuple[str, List[str], List[str]]]`: An iterator of (path, list of directory names, list of file names) tuples.
+ """
+ # Set expand_info=False by default to get a x10 speed boost
+ kwargs = {"expand_info": kwargs.get("detail", False), **kwargs}
+ path = self.resolve_path(path, revision=kwargs.get("revision")).unresolve()
+ yield from super().walk(path, *args, **kwargs)
+
+ def glob(self, path: str, **kwargs) -> List[str]:
+ """
+ Find files by glob-matching.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.glob).
+
+ Args:
+ path (`str`):
+ Path pattern to match.
+
+ Returns:
+ `List[str]`: List of paths matching the pattern.
+ """
+ # Set expand_info=False by default to get a x10 speed boost
+ kwargs = {"expand_info": kwargs.get("detail", False), **kwargs}
+ path = self.resolve_path(path, revision=kwargs.get("revision")).unresolve()
+ return super().glob(path, **kwargs)
+
+ def find(
+ self,
+ path: str,
+ maxdepth: Optional[int] = None,
+ withdirs: bool = False,
+ detail: bool = False,
+ refresh: bool = False,
+ revision: Optional[str] = None,
+ **kwargs,
+ ) -> Union[List[str], Dict[str, Dict[str, Any]]]:
+ """
+ List all files below path.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.find).
+
+ Args:
+ path (`str`):
+ Root path to list files from.
+ maxdepth (`int`, *optional*):
+ Maximum depth to descend into subdirectories.
+ withdirs (`bool`, *optional*):
+ Include directory paths in the output. Defaults to False.
+ detail (`bool`, *optional*):
+ If True, returns a dict mapping paths to file information. Defaults to False.
+ refresh (`bool`, *optional*):
+ If True, bypass the cache and fetch the latest data. Defaults to False.
+ revision (`str`, *optional*):
+ The git revision to list from.
+
+ Returns:
+ `Union[List[str], Dict[str, Dict[str, Any]]]`: List of paths or dict of file information.
+ """
+ if maxdepth:
+ return super().find(
+ path, maxdepth=maxdepth, withdirs=withdirs, detail=detail, refresh=refresh, revision=revision, **kwargs
+ )
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ kwargs = {"expand_info": detail, **kwargs}
+ try:
+ out = self._ls_tree(path, recursive=True, refresh=refresh, revision=resolved_path.revision, **kwargs)
+ except EntryNotFoundError:
+ # Path could be a file
+ if self.info(path, revision=revision, **kwargs)["type"] == "file":
+ out = {path: {}}
+ else:
+ out = {}
+ else:
+ if not withdirs:
+ out = [o for o in out if o["type"] != "directory"]
+ else:
+ # If `withdirs=True`, include the directory itself to be consistent with the spec
+ path_info = self.info(path, revision=resolved_path.revision, **kwargs)
+ out = [path_info] + out if path_info["type"] == "directory" else out
+ out = {o["name"]: o for o in out}
+ names = sorted(out)
+ if not detail:
+ return names
+ else:
+ return {name: out[name] for name in names}
+
+ def cp_file(self, path1: str, path2: str, revision: Optional[str] = None, **kwargs) -> None:
+ """
+ Copy a file within or between repositories.
+
+
+
+ Note: When possible, use `HfApi.upload_file()` for better performance.
+
+
+
+ Args:
+ path1 (`str`):
+ Source path to copy from.
+ path2 (`str`):
+ Destination path to copy to.
+ revision (`str`, *optional*):
+ The git revision to copy from.
+
+ """
+ resolved_path1 = self.resolve_path(path1, revision=revision)
+ resolved_path2 = self.resolve_path(path2, revision=revision)
+
+ same_repo = (
+ resolved_path1.repo_type == resolved_path2.repo_type and resolved_path1.repo_id == resolved_path2.repo_id
+ )
+
+ if same_repo:
+ commit_message = f"Copy {path1} to {path2}"
+ self._api.create_commit(
+ repo_id=resolved_path1.repo_id,
+ repo_type=resolved_path1.repo_type,
+ revision=resolved_path2.revision,
+ commit_message=kwargs.get("commit_message", commit_message),
+ commit_description=kwargs.get("commit_description", ""),
+ operations=[
+ CommitOperationCopy(
+ src_path_in_repo=resolved_path1.path_in_repo,
+ path_in_repo=resolved_path2.path_in_repo,
+ src_revision=resolved_path1.revision,
+ )
+ ],
+ )
+ else:
+ with self.open(path1, "rb", revision=resolved_path1.revision) as f:
+ content = f.read()
+ commit_message = f"Copy {path1} to {path2}"
+ self._api.upload_file(
+ path_or_fileobj=content,
+ path_in_repo=resolved_path2.path_in_repo,
+ repo_id=resolved_path2.repo_id,
+ token=self.token,
+ repo_type=resolved_path2.repo_type,
+ revision=resolved_path2.revision,
+ commit_message=kwargs.get("commit_message", commit_message),
+ commit_description=kwargs.get("commit_description"),
+ )
+ self.invalidate_cache(path=resolved_path1.unresolve())
+ self.invalidate_cache(path=resolved_path2.unresolve())
+
+ def modified(self, path: str, **kwargs) -> datetime:
+ """
+ Get the last modified time of a file.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.modified).
+
+ Args:
+ path (`str`):
+ Path to the file.
+
+ Returns:
+ `datetime`: Last commit date of the file.
+ """
+ info = self.info(path, **kwargs)
+ return info["last_commit"]["date"]
+
+ def info(self, path: str, refresh: bool = False, revision: Optional[str] = None, **kwargs) -> Dict[str, Any]:
+ """
+ Get information about a file or directory.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.info).
+
+
+
+ Note: When possible, use `HfApi.get_paths_info()` or `HfApi.repo_info()` for better performance.
+
+
+
+ Args:
+ path (`str`):
+ Path to get info for.
+ refresh (`bool`, *optional*):
+ If True, bypass the cache and fetch the latest data. Defaults to False.
+ revision (`str`, *optional*):
+ The git revision to get info from.
+
+ Returns:
+ `Dict[str, Any]`: Dictionary containing file information (type, size, commit info, etc.).
+
+ """
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ expand_info = kwargs.get(
+ "expand_info", True
+ ) # don't expose it as a parameter in the public API to follow the spec
+ if not resolved_path.path_in_repo:
+ # Path is the root directory
+ out = {
+ "name": path,
+ "size": 0,
+ "type": "directory",
+ }
+ if expand_info:
+ last_commit = self._api.list_repo_commits(
+ resolved_path.repo_id, repo_type=resolved_path.repo_type, revision=resolved_path.revision
+ )[-1]
+ out = {
+ **out,
+ "tree_id": None, # TODO: tree_id of the root directory?
+ "last_commit": LastCommitInfo(
+ oid=last_commit.commit_id, title=last_commit.title, date=last_commit.created_at
+ ),
+ }
+ else:
+ out = None
+ parent_path = self._parent(path)
+ if not expand_info and parent_path not in self.dircache:
+ # Fill the cache with cheap call
+ self.ls(parent_path, expand_info=False)
+ if parent_path in self.dircache:
+ # Check if the path is in the cache
+ out1 = [o for o in self.dircache[parent_path] if o["name"] == path]
+ if not out1:
+ _raise_file_not_found(path, None)
+ out = out1[0]
+ if refresh or out is None or (expand_info and out and out["last_commit"] is None):
+ paths_info = self._api.get_paths_info(
+ resolved_path.repo_id,
+ resolved_path.path_in_repo,
+ expand=expand_info,
+ revision=resolved_path.revision,
+ repo_type=resolved_path.repo_type,
+ )
+ if not paths_info:
+ _raise_file_not_found(path, None)
+ path_info = paths_info[0]
+ root_path = HfFileSystemResolvedPath(
+ resolved_path.repo_type,
+ resolved_path.repo_id,
+ resolved_path.revision,
+ path_in_repo="",
+ _raw_revision=resolved_path._raw_revision,
+ ).unresolve()
+ if isinstance(path_info, RepoFile):
+ out = {
+ "name": root_path + "/" + path_info.path,
+ "size": path_info.size,
+ "type": "file",
+ "blob_id": path_info.blob_id,
+ "lfs": path_info.lfs,
+ "last_commit": path_info.last_commit,
+ "security": path_info.security,
+ }
+ else:
+ out = {
+ "name": root_path + "/" + path_info.path,
+ "size": 0,
+ "type": "directory",
+ "tree_id": path_info.tree_id,
+ "last_commit": path_info.last_commit,
+ }
+ if not expand_info:
+ out = {k: out[k] for k in ["name", "size", "type"]}
+ assert out is not None
+ return out
+
+ def exists(self, path, **kwargs):
+ """
+ Check if a file exists.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.exists).
+
+
+
+ Note: When possible, use `HfApi.file_exists()` for better performance.
+
+
+
+ Args:
+ path (`str`):
+ Path to check.
+
+ Returns:
+ `bool`: True if file exists, False otherwise.
+ """
+ try:
+ if kwargs.get("refresh", False):
+ self.invalidate_cache(path)
+
+ self.info(path, **{**kwargs, "expand_info": False})
+ return True
+ except: # noqa: E722
+ return False
+
+ def isdir(self, path):
+ """
+ Check if a path is a directory.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.isdir).
+
+ Args:
+ path (`str`):
+ Path to check.
+
+ Returns:
+ `bool`: True if path is a directory, False otherwise.
+ """
+ try:
+ return self.info(path, expand_info=False)["type"] == "directory"
+ except OSError:
+ return False
+
+ def isfile(self, path):
+ """
+ Check if a path is a file.
+
+ For more details, refer to [fsspec documentation](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.isfile).
+
+ Args:
+ path (`str`):
+ Path to check.
+
+ Returns:
+ `bool`: True if path is a file, False otherwise.
+ """
+ try:
+ return self.info(path, expand_info=False)["type"] == "file"
+ except: # noqa: E722
+ return False
+
+ def url(self, path: str) -> str:
+ """
+ Get the HTTP URL of the given path.
+
+ Args:
+ path (`str`):
+ Path to get URL for.
+
+ Returns:
+ `str`: HTTP URL to access the file or directory on the Hub.
+ """
+ resolved_path = self.resolve_path(path)
+ url = hf_hub_url(
+ resolved_path.repo_id,
+ resolved_path.path_in_repo,
+ repo_type=resolved_path.repo_type,
+ revision=resolved_path.revision,
+ endpoint=self.endpoint,
+ )
+ if self.isdir(path):
+ url = url.replace("/resolve/", "/tree/", 1)
+ return url
+
+ def get_file(self, rpath, lpath, callback=_DEFAULT_CALLBACK, outfile=None, **kwargs) -> None:
+ """
+ Copy single remote file to local.
+
+
+
+ Note: When possible, use `HfApi.hf_hub_download()` for better performance.
+
+
+
+ Args:
+ rpath (`str`):
+ Remote path to download from.
+ lpath (`str`):
+ Local path to download to.
+ callback (`Callback`, *optional*):
+ Optional callback to track download progress. Defaults to no callback.
+ outfile (`IO`, *optional*):
+ Optional file-like object to write to. If provided, `lpath` is ignored.
+
+ """
+ revision = kwargs.get("revision")
+ unhandled_kwargs = set(kwargs.keys()) - {"revision"}
+ if not isinstance(callback, (NoOpCallback, TqdmCallback)) or len(unhandled_kwargs) > 0:
+ # for now, let's not handle custom callbacks
+ # and let's not handle custom kwargs
+ return super().get_file(rpath, lpath, callback=callback, outfile=outfile, **kwargs)
+
+ # Taken from https://github.com/fsspec/filesystem_spec/blob/47b445ae4c284a82dd15e0287b1ffc410e8fc470/fsspec/spec.py#L883
+ if isfilelike(lpath):
+ outfile = lpath
+ elif self.isdir(rpath):
+ os.makedirs(lpath, exist_ok=True)
+ return None
+
+ if isinstance(lpath, (str, Path)): # otherwise, let's assume it's a file-like object
+ os.makedirs(os.path.dirname(lpath), exist_ok=True)
+
+ # Open file if not already open
+ close_file = False
+ if outfile is None:
+ outfile = open(lpath, "wb")
+ close_file = True
+ initial_pos = outfile.tell()
+
+ # Custom implementation of `get_file` to use `http_get`.
+ resolve_remote_path = self.resolve_path(rpath, revision=revision)
+ expected_size = self.info(rpath, revision=revision)["size"]
+ callback.set_size(expected_size)
+ try:
+ http_get(
+ url=hf_hub_url(
+ repo_id=resolve_remote_path.repo_id,
+ revision=resolve_remote_path.revision,
+ filename=resolve_remote_path.path_in_repo,
+ repo_type=resolve_remote_path.repo_type,
+ endpoint=self.endpoint,
+ ),
+ temp_file=outfile,
+ displayed_filename=rpath,
+ expected_size=expected_size,
+ resume_size=0,
+ headers=self._api._build_hf_headers(),
+ _tqdm_bar=callback.tqdm if isinstance(callback, TqdmCallback) else None,
+ )
+ outfile.seek(initial_pos)
+ finally:
+ # Close file only if we opened it ourselves
+ if close_file:
+ outfile.close()
+
+ @property
+ def transaction(self):
+ """A context within which files are committed together upon exit
+
+ Requires the file class to implement `.commit()` and `.discard()`
+ for the normal and exception cases.
+ """
+ # Taken from https://github.com/fsspec/filesystem_spec/blob/3fbb6fee33b46cccb015607630843dea049d3243/fsspec/spec.py#L231
+ # See https://github.com/huggingface/huggingface_hub/issues/1733
+ raise NotImplementedError("Transactional commits are not supported.")
+
+ def start_transaction(self):
+ """Begin write transaction for deferring files, non-context version"""
+ # Taken from https://github.com/fsspec/filesystem_spec/blob/3fbb6fee33b46cccb015607630843dea049d3243/fsspec/spec.py#L241
+ # See https://github.com/huggingface/huggingface_hub/issues/1733
+ raise NotImplementedError("Transactional commits are not supported.")
+
+
+class HfFileSystemFile(fsspec.spec.AbstractBufferedFile):
+ def __init__(self, fs: HfFileSystem, path: str, revision: Optional[str] = None, **kwargs):
+ try:
+ self.resolved_path = fs.resolve_path(path, revision=revision)
+ except FileNotFoundError as e:
+ if "w" in kwargs.get("mode", ""):
+ raise FileNotFoundError(
+ f"{e}.\nMake sure the repository and revision exist before writing data."
+ ) from e
+ raise
+ # avoid an unnecessary .info() call with expensive expand_info=True to instantiate .details
+ if kwargs.get("mode", "rb") == "rb":
+ self.details = fs.info(self.resolved_path.unresolve(), expand_info=False)
+ super().__init__(fs, self.resolved_path.unresolve(), **kwargs)
+ self.fs: HfFileSystem
+
+ def __del__(self):
+ if not hasattr(self, "resolved_path"):
+ # Means that the constructor failed. Nothing to do.
+ return
+ return super().__del__()
+
+ def _fetch_range(self, start: int, end: int) -> bytes:
+ headers = {
+ "range": f"bytes={start}-{end - 1}",
+ **self.fs._api._build_hf_headers(),
+ }
+ url = hf_hub_url(
+ repo_id=self.resolved_path.repo_id,
+ revision=self.resolved_path.revision,
+ filename=self.resolved_path.path_in_repo,
+ repo_type=self.resolved_path.repo_type,
+ endpoint=self.fs.endpoint,
+ )
+ r = http_backoff(
+ "GET",
+ url,
+ headers=headers,
+ retry_on_status_codes=(500, 502, 503, 504),
+ timeout=constants.HF_HUB_DOWNLOAD_TIMEOUT,
+ )
+ hf_raise_for_status(r)
+ return r.content
+
+ def _initiate_upload(self) -> None:
+ self.temp_file = tempfile.NamedTemporaryFile(prefix="hffs-", delete=False)
+
+ def _upload_chunk(self, final: bool = False) -> None:
+ self.buffer.seek(0)
+ block = self.buffer.read()
+ self.temp_file.write(block)
+ if final:
+ self.temp_file.close()
+ self.fs._api.upload_file(
+ path_or_fileobj=self.temp_file.name,
+ path_in_repo=self.resolved_path.path_in_repo,
+ repo_id=self.resolved_path.repo_id,
+ token=self.fs.token,
+ repo_type=self.resolved_path.repo_type,
+ revision=self.resolved_path.revision,
+ commit_message=self.kwargs.get("commit_message"),
+ commit_description=self.kwargs.get("commit_description"),
+ )
+ os.remove(self.temp_file.name)
+ self.fs.invalidate_cache(
+ path=self.resolved_path.unresolve(),
+ )
+
+ def read(self, length=-1):
+ """Read remote file.
+
+ If `length` is not provided or is -1, the entire file is downloaded and read. On POSIX systems and if
+ `hf_transfer` is not enabled, the file is loaded in memory directly. Otherwise, the file is downloaded to a
+ temporary file and read from there.
+ """
+ if self.mode == "rb" and (length is None or length == -1) and self.loc == 0:
+ with self.fs.open(self.path, "rb", block_size=0) as f: # block_size=0 enables fast streaming
+ return f.read()
+ return super().read(length)
+
+ def url(self) -> str:
+ return self.fs.url(self.path)
+
+
+class HfFileSystemStreamFile(fsspec.spec.AbstractBufferedFile):
+ def __init__(
+ self,
+ fs: HfFileSystem,
+ path: str,
+ mode: str = "rb",
+ revision: Optional[str] = None,
+ block_size: int = 0,
+ cache_type: str = "none",
+ **kwargs,
+ ):
+ if block_size != 0:
+ raise ValueError(f"HfFileSystemStreamFile only supports block_size=0 but got {block_size}")
+ if cache_type != "none":
+ raise ValueError(f"HfFileSystemStreamFile only supports cache_type='none' but got {cache_type}")
+ if "w" in mode:
+ raise ValueError(f"HfFileSystemStreamFile only supports reading but got mode='{mode}'")
+ try:
+ self.resolved_path = fs.resolve_path(path, revision=revision)
+ except FileNotFoundError as e:
+ if "w" in kwargs.get("mode", ""):
+ raise FileNotFoundError(
+ f"{e}.\nMake sure the repository and revision exist before writing data."
+ ) from e
+ # avoid an unnecessary .info() call to instantiate .details
+ self.details = {"name": self.resolved_path.unresolve(), "size": None}
+ super().__init__(
+ fs, self.resolved_path.unresolve(), mode=mode, block_size=block_size, cache_type=cache_type, **kwargs
+ )
+ self.response: Optional[Response] = None
+ self.fs: HfFileSystem
+
+ def seek(self, loc: int, whence: int = 0):
+ if loc == 0 and whence == 1:
+ return
+ if loc == self.loc and whence == 0:
+ return
+ raise ValueError("Cannot seek streaming HF file")
+
+ def read(self, length: int = -1):
+ read_args = (length,) if length >= 0 else ()
+ if self.response is None or self.response.raw.isclosed():
+ url = hf_hub_url(
+ repo_id=self.resolved_path.repo_id,
+ revision=self.resolved_path.revision,
+ filename=self.resolved_path.path_in_repo,
+ repo_type=self.resolved_path.repo_type,
+ endpoint=self.fs.endpoint,
+ )
+ self.response = http_backoff(
+ "GET",
+ url,
+ headers=self.fs._api._build_hf_headers(),
+ retry_on_status_codes=(500, 502, 503, 504),
+ stream=True,
+ timeout=constants.HF_HUB_DOWNLOAD_TIMEOUT,
+ )
+ hf_raise_for_status(self.response)
+ try:
+ out = self.response.raw.read(*read_args)
+ except Exception:
+ self.response.close()
+
+ # Retry by recreating the connection
+ url = hf_hub_url(
+ repo_id=self.resolved_path.repo_id,
+ revision=self.resolved_path.revision,
+ filename=self.resolved_path.path_in_repo,
+ repo_type=self.resolved_path.repo_type,
+ endpoint=self.fs.endpoint,
+ )
+ self.response = http_backoff(
+ "GET",
+ url,
+ headers={"Range": "bytes=%d-" % self.loc, **self.fs._api._build_hf_headers()},
+ retry_on_status_codes=(500, 502, 503, 504),
+ stream=True,
+ timeout=constants.HF_HUB_DOWNLOAD_TIMEOUT,
+ )
+ hf_raise_for_status(self.response)
+ try:
+ out = self.response.raw.read(*read_args)
+ except Exception:
+ self.response.close()
+ raise
+ self.loc += len(out)
+ return out
+
+ def url(self) -> str:
+ return self.fs.url(self.path)
+
+ def __del__(self):
+ if not hasattr(self, "resolved_path"):
+ # Means that the constructor failed. Nothing to do.
+ return
+ return super().__del__()
+
+ def __reduce__(self):
+ return reopen, (self.fs, self.path, self.mode, self.blocksize, self.cache.name)
+
+
+def safe_revision(revision: str) -> str:
+ return revision if SPECIAL_REFS_REVISION_REGEX.match(revision) else safe_quote(revision)
+
+
+def safe_quote(s: str) -> str:
+ return quote(s, safe="")
+
+
+def _raise_file_not_found(path: str, err: Optional[Exception]) -> NoReturn:
+ msg = path
+ if isinstance(err, RepositoryNotFoundError):
+ msg = f"{path} (repository not found)"
+ elif isinstance(err, RevisionNotFoundError):
+ msg = f"{path} (revision not found)"
+ elif isinstance(err, HFValidationError):
+ msg = f"{path} (invalid repository id)"
+ raise FileNotFoundError(msg) from err
+
+
+def reopen(fs: HfFileSystem, path: str, mode: str, block_size: int, cache_type: str):
+ return fs.open(path, mode=mode, block_size=block_size, cache_type=cache_type)
diff --git a/env/Lib/site-packages/huggingface_hub/hub_mixin.py b/env/Lib/site-packages/huggingface_hub/hub_mixin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8bf32f1266526b031174ad33b2299b1379b0c4da
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/hub_mixin.py
@@ -0,0 +1,836 @@
+import inspect
+import json
+import os
+from dataclasses import Field, asdict, dataclass, is_dataclass
+from pathlib import Path
+from typing import Any, Callable, ClassVar, Dict, List, Optional, Protocol, Tuple, Type, TypeVar, Union
+
+import packaging.version
+
+from . import constants
+from .errors import EntryNotFoundError, HfHubHTTPError
+from .file_download import hf_hub_download
+from .hf_api import HfApi
+from .repocard import ModelCard, ModelCardData
+from .utils import (
+ SoftTemporaryDirectory,
+ is_jsonable,
+ is_safetensors_available,
+ is_simple_optional_type,
+ is_torch_available,
+ logging,
+ unwrap_simple_optional_type,
+ validate_hf_hub_args,
+)
+
+
+if is_torch_available():
+ import torch # type: ignore
+
+if is_safetensors_available():
+ import safetensors
+ from safetensors.torch import load_model as load_model_as_safetensor
+ from safetensors.torch import save_model as save_model_as_safetensor
+
+
+logger = logging.get_logger(__name__)
+
+
+# Type alias for dataclass instances, copied from https://github.com/python/typeshed/blob/9f28171658b9ca6c32a7cb93fbb99fc92b17858b/stdlib/_typeshed/__init__.pyi#L349
+class DataclassInstance(Protocol):
+ __dataclass_fields__: ClassVar[Dict[str, Field]]
+
+
+# Generic variable that is either ModelHubMixin or a subclass thereof
+T = TypeVar("T", bound="ModelHubMixin")
+# Generic variable to represent an args type
+ARGS_T = TypeVar("ARGS_T")
+ENCODER_T = Callable[[ARGS_T], Any]
+DECODER_T = Callable[[Any], ARGS_T]
+CODER_T = Tuple[ENCODER_T, DECODER_T]
+
+
+DEFAULT_MODEL_CARD = """
+---
+# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
+# Doc / guide: https://huggingface.co/docs/hub/model-cards
+{{ card_data }}
+---
+
+This model has been pushed to the Hub using the [PytorchModelHubMixin](https://huggingface.co/docs/huggingface_hub/package_reference/mixins#huggingface_hub.PyTorchModelHubMixin) integration:
+- Library: {{ repo_url | default("[More Information Needed]", true) }}
+- Docs: {{ docs_url | default("[More Information Needed]", true) }}
+"""
+
+
+@dataclass
+class MixinInfo:
+ model_card_template: str
+ model_card_data: ModelCardData
+ repo_url: Optional[str] = None
+ docs_url: Optional[str] = None
+
+
+class ModelHubMixin:
+ """
+ A generic mixin to integrate ANY machine learning framework with the Hub.
+
+ To integrate your framework, your model class must inherit from this class. Custom logic for saving/loading models
+ have to be overwritten in [`_from_pretrained`] and [`_save_pretrained`]. [`PyTorchModelHubMixin`] is a good example
+ of mixin integration with the Hub. Check out our [integration guide](../guides/integrations) for more instructions.
+
+ When inheriting from [`ModelHubMixin`], you can define class-level attributes. These attributes are not passed to
+ `__init__` but to the class definition itself. This is useful to define metadata about the library integrating
+ [`ModelHubMixin`].
+
+ For more details on how to integrate the mixin with your library, checkout the [integration guide](../guides/integrations).
+
+ Args:
+ repo_url (`str`, *optional*):
+ URL of the library repository. Used to generate model card.
+ docs_url (`str`, *optional*):
+ URL of the library documentation. Used to generate model card.
+ model_card_template (`str`, *optional*):
+ Template of the model card. Used to generate model card. Defaults to a generic template.
+ language (`str` or `List[str]`, *optional*):
+ Language supported by the library. Used to generate model card.
+ library_name (`str`, *optional*):
+ Name of the library integrating ModelHubMixin. Used to generate model card.
+ license (`str`, *optional*):
+ License of the library integrating ModelHubMixin. Used to generate model card.
+ E.g: "apache-2.0"
+ license_name (`str`, *optional*):
+ Name of the library integrating ModelHubMixin. Used to generate model card.
+ Only used if `license` is set to `other`.
+ E.g: "coqui-public-model-license".
+ license_link (`str`, *optional*):
+ URL to the license of the library integrating ModelHubMixin. Used to generate model card.
+ Only used if `license` is set to `other` and `license_name` is set.
+ E.g: "https://coqui.ai/cpml".
+ pipeline_tag (`str`, *optional*):
+ Tag of the pipeline. Used to generate model card. E.g. "text-classification".
+ tags (`List[str]`, *optional*):
+ Tags to be added to the model card. Used to generate model card. E.g. ["x-custom-tag", "arxiv:2304.12244"]
+ coders (`Dict[Type, Tuple[Callable, Callable]]`, *optional*):
+ Dictionary of custom types and their encoders/decoders. Used to encode/decode arguments that are not
+ jsonable by default. E.g dataclasses, argparse.Namespace, OmegaConf, etc.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import ModelHubMixin
+
+ # Inherit from ModelHubMixin
+ >>> class MyCustomModel(
+ ... ModelHubMixin,
+ ... library_name="my-library",
+ ... tags=["x-custom-tag", "arxiv:2304.12244"],
+ ... repo_url="https://github.com/huggingface/my-cool-library",
+ ... docs_url="https://huggingface.co/docs/my-cool-library",
+ ... # ^ optional metadata to generate model card
+ ... ):
+ ... def __init__(self, size: int = 512, device: str = "cpu"):
+ ... # define how to initialize your model
+ ... super().__init__()
+ ... ...
+ ...
+ ... def _save_pretrained(self, save_directory: Path) -> None:
+ ... # define how to serialize your model
+ ... ...
+ ...
+ ... @classmethod
+ ... def from_pretrained(
+ ... cls: Type[T],
+ ... pretrained_model_name_or_path: Union[str, Path],
+ ... *,
+ ... force_download: bool = False,
+ ... resume_download: Optional[bool] = None,
+ ... proxies: Optional[Dict] = None,
+ ... token: Optional[Union[str, bool]] = None,
+ ... cache_dir: Optional[Union[str, Path]] = None,
+ ... local_files_only: bool = False,
+ ... revision: Optional[str] = None,
+ ... **model_kwargs,
+ ... ) -> T:
+ ... # define how to deserialize your model
+ ... ...
+
+ >>> model = MyCustomModel(size=256, device="gpu")
+
+ # Save model weights to local directory
+ >>> model.save_pretrained("my-awesome-model")
+
+ # Push model weights to the Hub
+ >>> model.push_to_hub("my-awesome-model")
+
+ # Download and initialize weights from the Hub
+ >>> reloaded_model = MyCustomModel.from_pretrained("username/my-awesome-model")
+ >>> reloaded_model.size
+ 256
+
+ # Model card has been correctly populated
+ >>> from huggingface_hub import ModelCard
+ >>> card = ModelCard.load("username/my-awesome-model")
+ >>> card.data.tags
+ ["x-custom-tag", "pytorch_model_hub_mixin", "model_hub_mixin"]
+ >>> card.data.library_name
+ "my-library"
+ ```
+ """
+
+ _hub_mixin_config: Optional[Union[dict, DataclassInstance]] = None
+ # ^ optional config attribute automatically set in `from_pretrained`
+ _hub_mixin_info: MixinInfo
+ # ^ information about the library integrating ModelHubMixin (used to generate model card)
+ _hub_mixin_inject_config: bool # whether `_from_pretrained` expects `config` or not
+ _hub_mixin_init_parameters: Dict[str, inspect.Parameter] # __init__ parameters
+ _hub_mixin_jsonable_default_values: Dict[str, Any] # default values for __init__ parameters
+ _hub_mixin_jsonable_custom_types: Tuple[Type, ...] # custom types that can be encoded/decoded
+ _hub_mixin_coders: Dict[Type, CODER_T] # encoders/decoders for custom types
+ # ^ internal values to handle config
+
+ def __init_subclass__(
+ cls,
+ *,
+ # Generic info for model card
+ repo_url: Optional[str] = None,
+ docs_url: Optional[str] = None,
+ # Model card template
+ model_card_template: str = DEFAULT_MODEL_CARD,
+ # Model card metadata
+ language: Optional[List[str]] = None,
+ library_name: Optional[str] = None,
+ license: Optional[str] = None,
+ license_name: Optional[str] = None,
+ license_link: Optional[str] = None,
+ pipeline_tag: Optional[str] = None,
+ tags: Optional[List[str]] = None,
+ # How to encode/decode arguments with custom type into a JSON config?
+ coders: Optional[
+ Dict[Type, CODER_T]
+ # Key is a type.
+ # Value is a tuple (encoder, decoder).
+ # Example: {MyCustomType: (lambda x: x.value, lambda data: MyCustomType(data))}
+ ] = None,
+ ) -> None:
+ """Inspect __init__ signature only once when subclassing + handle modelcard."""
+ super().__init_subclass__()
+
+ # Will be reused when creating modelcard
+ tags = tags or []
+ tags.append("model_hub_mixin")
+
+ # Initialize MixinInfo if not existent
+ info = MixinInfo(model_card_template=model_card_template, model_card_data=ModelCardData())
+
+ # If parent class has a MixinInfo, inherit from it as a copy
+ if hasattr(cls, "_hub_mixin_info"):
+ # Inherit model card template from parent class if not explicitly set
+ if model_card_template == DEFAULT_MODEL_CARD:
+ info.model_card_template = cls._hub_mixin_info.model_card_template
+
+ # Inherit from parent model card data
+ info.model_card_data = ModelCardData(**cls._hub_mixin_info.model_card_data.to_dict())
+
+ # Inherit other info
+ info.docs_url = cls._hub_mixin_info.docs_url
+ info.repo_url = cls._hub_mixin_info.repo_url
+ cls._hub_mixin_info = info
+
+ # Update MixinInfo with metadata
+ if model_card_template is not None and model_card_template != DEFAULT_MODEL_CARD:
+ info.model_card_template = model_card_template
+ if repo_url is not None:
+ info.repo_url = repo_url
+ if docs_url is not None:
+ info.docs_url = docs_url
+ if language is not None:
+ info.model_card_data.language = language
+ if library_name is not None:
+ info.model_card_data.library_name = library_name
+ if license is not None:
+ info.model_card_data.license = license
+ if license_name is not None:
+ info.model_card_data.license_name = license_name
+ if license_link is not None:
+ info.model_card_data.license_link = license_link
+ if pipeline_tag is not None:
+ info.model_card_data.pipeline_tag = pipeline_tag
+ if tags is not None:
+ if info.model_card_data.tags is not None:
+ info.model_card_data.tags.extend(tags)
+ else:
+ info.model_card_data.tags = tags
+
+ info.model_card_data.tags = sorted(set(info.model_card_data.tags))
+
+ # Handle encoders/decoders for args
+ cls._hub_mixin_coders = coders or {}
+ cls._hub_mixin_jsonable_custom_types = tuple(cls._hub_mixin_coders.keys())
+
+ # Inspect __init__ signature to handle config
+ cls._hub_mixin_init_parameters = dict(inspect.signature(cls.__init__).parameters)
+ cls._hub_mixin_jsonable_default_values = {
+ param.name: cls._encode_arg(param.default)
+ for param in cls._hub_mixin_init_parameters.values()
+ if param.default is not inspect.Parameter.empty and cls._is_jsonable(param.default)
+ }
+ cls._hub_mixin_inject_config = "config" in inspect.signature(cls._from_pretrained).parameters
+
+ def __new__(cls: Type[T], *args, **kwargs) -> T:
+ """Create a new instance of the class and handle config.
+
+ 3 cases:
+ - If `self._hub_mixin_config` is already set, do nothing.
+ - If `config` is passed as a dataclass, set it as `self._hub_mixin_config`.
+ - Otherwise, build `self._hub_mixin_config` from default values and passed values.
+ """
+ instance = super().__new__(cls)
+
+ # If `config` is already set, return early
+ if instance._hub_mixin_config is not None:
+ return instance
+
+ # Infer passed values
+ passed_values = {
+ **{
+ key: value
+ for key, value in zip(
+ # [1:] to skip `self` parameter
+ list(cls._hub_mixin_init_parameters)[1:],
+ args,
+ )
+ },
+ **kwargs,
+ }
+
+ # If config passed as dataclass => set it and return early
+ if is_dataclass(passed_values.get("config")):
+ instance._hub_mixin_config = passed_values["config"]
+ return instance
+
+ # Otherwise, build config from default + passed values
+ init_config = {
+ # default values
+ **cls._hub_mixin_jsonable_default_values,
+ # passed values
+ **{
+ key: cls._encode_arg(value) # Encode custom types as jsonable value
+ for key, value in passed_values.items()
+ if instance._is_jsonable(value) # Only if jsonable or we have a custom encoder
+ },
+ }
+ passed_config = init_config.pop("config", {})
+
+ # Populate `init_config` with provided config
+ if isinstance(passed_config, dict):
+ init_config.update(passed_config)
+
+ # Set `config` attribute and return
+ if init_config != {}:
+ instance._hub_mixin_config = init_config
+ return instance
+
+ @classmethod
+ def _is_jsonable(cls, value: Any) -> bool:
+ """Check if a value is JSON serializable."""
+ if isinstance(value, cls._hub_mixin_jsonable_custom_types):
+ return True
+ return is_jsonable(value)
+
+ @classmethod
+ def _encode_arg(cls, arg: Any) -> Any:
+ """Encode an argument into a JSON serializable format."""
+ for type_, (encoder, _) in cls._hub_mixin_coders.items():
+ if isinstance(arg, type_):
+ if arg is None:
+ return None
+ return encoder(arg)
+ return arg
+
+ @classmethod
+ def _decode_arg(cls, expected_type: Type[ARGS_T], value: Any) -> Optional[ARGS_T]:
+ """Decode a JSON serializable value into an argument."""
+ if is_simple_optional_type(expected_type):
+ if value is None:
+ return None
+ expected_type = unwrap_simple_optional_type(expected_type)
+ # Dataclass => handle it
+ if is_dataclass(expected_type):
+ return _load_dataclass(expected_type, value) # type: ignore[return-value]
+ # Otherwise => check custom decoders
+ for type_, (_, decoder) in cls._hub_mixin_coders.items():
+ if inspect.isclass(expected_type) and issubclass(expected_type, type_):
+ return decoder(value)
+ # Otherwise => don't decode
+ return value
+
+ def save_pretrained(
+ self,
+ save_directory: Union[str, Path],
+ *,
+ config: Optional[Union[dict, DataclassInstance]] = None,
+ repo_id: Optional[str] = None,
+ push_to_hub: bool = False,
+ model_card_kwargs: Optional[Dict[str, Any]] = None,
+ **push_to_hub_kwargs,
+ ) -> Optional[str]:
+ """
+ Save weights in local directory.
+
+ Args:
+ save_directory (`str` or `Path`):
+ Path to directory in which the model weights and configuration will be saved.
+ config (`dict` or `DataclassInstance`, *optional*):
+ Model configuration specified as a key/value dictionary or a dataclass instance.
+ push_to_hub (`bool`, *optional*, defaults to `False`):
+ Whether or not to push your model to the Huggingface Hub after saving it.
+ repo_id (`str`, *optional*):
+ ID of your repository on the Hub. Used only if `push_to_hub=True`. Will default to the folder name if
+ not provided.
+ model_card_kwargs (`Dict[str, Any]`, *optional*):
+ Additional arguments passed to the model card template to customize the model card.
+ push_to_hub_kwargs:
+ Additional key word arguments passed along to the [`~ModelHubMixin.push_to_hub`] method.
+ Returns:
+ `str` or `None`: url of the commit on the Hub if `push_to_hub=True`, `None` otherwise.
+ """
+ save_directory = Path(save_directory)
+ save_directory.mkdir(parents=True, exist_ok=True)
+
+ # Remove config.json if already exists. After `_save_pretrained` we don't want to overwrite config.json
+ # as it might have been saved by the custom `_save_pretrained` already. However we do want to overwrite
+ # an existing config.json if it was not saved by `_save_pretrained`.
+ config_path = save_directory / constants.CONFIG_NAME
+ config_path.unlink(missing_ok=True)
+
+ # save model weights/files (framework-specific)
+ self._save_pretrained(save_directory)
+
+ # save config (if provided and if not serialized yet in `_save_pretrained`)
+ if config is None:
+ config = self._hub_mixin_config
+ if config is not None:
+ if is_dataclass(config):
+ config = asdict(config) # type: ignore[arg-type]
+ if not config_path.exists():
+ config_str = json.dumps(config, sort_keys=True, indent=2)
+ config_path.write_text(config_str)
+
+ # save model card
+ model_card_path = save_directory / "README.md"
+ model_card_kwargs = model_card_kwargs if model_card_kwargs is not None else {}
+ if not model_card_path.exists(): # do not overwrite if already exists
+ self.generate_model_card(**model_card_kwargs).save(save_directory / "README.md")
+
+ # push to the Hub if required
+ if push_to_hub:
+ kwargs = push_to_hub_kwargs.copy() # soft-copy to avoid mutating input
+ if config is not None: # kwarg for `push_to_hub`
+ kwargs["config"] = config
+ if repo_id is None:
+ repo_id = save_directory.name # Defaults to `save_directory` name
+ return self.push_to_hub(repo_id=repo_id, model_card_kwargs=model_card_kwargs, **kwargs)
+ return None
+
+ def _save_pretrained(self, save_directory: Path) -> None:
+ """
+ Overwrite this method in subclass to define how to save your model.
+ Check out our [integration guide](../guides/integrations) for instructions.
+
+ Args:
+ save_directory (`str` or `Path`):
+ Path to directory in which the model weights and configuration will be saved.
+ """
+ raise NotImplementedError
+
+ @classmethod
+ @validate_hf_hub_args
+ def from_pretrained(
+ cls: Type[T],
+ pretrained_model_name_or_path: Union[str, Path],
+ *,
+ force_download: bool = False,
+ resume_download: Optional[bool] = None,
+ proxies: Optional[Dict] = None,
+ token: Optional[Union[str, bool]] = None,
+ cache_dir: Optional[Union[str, Path]] = None,
+ local_files_only: bool = False,
+ revision: Optional[str] = None,
+ **model_kwargs,
+ ) -> T:
+ """
+ Download a model from the Huggingface Hub and instantiate it.
+
+ Args:
+ pretrained_model_name_or_path (`str`, `Path`):
+ - Either the `model_id` (string) of a model hosted on the Hub, e.g. `bigscience/bloom`.
+ - Or a path to a `directory` containing model weights saved using
+ [`~transformers.PreTrainedModel.save_pretrained`], e.g., `../path/to/my_model_directory/`.
+ revision (`str`, *optional*):
+ Revision of the model on the Hub. Can be a branch name, a git tag or any commit id.
+ Defaults to the latest commit on `main` branch.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether to force (re-)downloading the model weights and configuration files from the Hub, overriding
+ the existing cache.
+ proxies (`Dict[str, str]`, *optional*):
+ A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',
+ 'http://hostname': 'foo.bar:4012'}`. The proxies are used on every request.
+ token (`str` or `bool`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. By default, it will use the token
+ cached when running `huggingface-cli login`.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the local cached file if it exists.
+ model_kwargs (`Dict`, *optional*):
+ Additional kwargs to pass to the model during initialization.
+ """
+ model_id = str(pretrained_model_name_or_path)
+ config_file: Optional[str] = None
+ if os.path.isdir(model_id):
+ if constants.CONFIG_NAME in os.listdir(model_id):
+ config_file = os.path.join(model_id, constants.CONFIG_NAME)
+ else:
+ logger.warning(f"{constants.CONFIG_NAME} not found in {Path(model_id).resolve()}")
+ else:
+ try:
+ config_file = hf_hub_download(
+ repo_id=model_id,
+ filename=constants.CONFIG_NAME,
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ )
+ except HfHubHTTPError as e:
+ logger.info(f"{constants.CONFIG_NAME} not found on the HuggingFace Hub: {str(e)}")
+
+ # Read config
+ config = None
+ if config_file is not None:
+ with open(config_file, "r", encoding="utf-8") as f:
+ config = json.load(f)
+
+ # Decode custom types in config
+ for key, value in config.items():
+ if key in cls._hub_mixin_init_parameters:
+ expected_type = cls._hub_mixin_init_parameters[key].annotation
+ if expected_type is not inspect.Parameter.empty:
+ config[key] = cls._decode_arg(expected_type, value)
+
+ # Populate model_kwargs from config
+ for param in cls._hub_mixin_init_parameters.values():
+ if param.name not in model_kwargs and param.name in config:
+ model_kwargs[param.name] = config[param.name]
+
+ # Check if `config` argument was passed at init
+ if "config" in cls._hub_mixin_init_parameters and "config" not in model_kwargs:
+ # Decode `config` argument if it was passed
+ config_annotation = cls._hub_mixin_init_parameters["config"].annotation
+ config = cls._decode_arg(config_annotation, config)
+
+ # Forward config to model initialization
+ model_kwargs["config"] = config
+
+ # Inject config if `**kwargs` are expected
+ if is_dataclass(cls):
+ for key in cls.__dataclass_fields__:
+ if key not in model_kwargs and key in config:
+ model_kwargs[key] = config[key]
+ elif any(param.kind == inspect.Parameter.VAR_KEYWORD for param in cls._hub_mixin_init_parameters.values()):
+ for key, value in config.items():
+ if key not in model_kwargs:
+ model_kwargs[key] = value
+
+ # Finally, also inject if `_from_pretrained` expects it
+ if cls._hub_mixin_inject_config and "config" not in model_kwargs:
+ model_kwargs["config"] = config
+
+ instance = cls._from_pretrained(
+ model_id=str(model_id),
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ local_files_only=local_files_only,
+ token=token,
+ **model_kwargs,
+ )
+
+ # Implicitly set the config as instance attribute if not already set by the class
+ # This way `config` will be available when calling `save_pretrained` or `push_to_hub`.
+ if config is not None and (getattr(instance, "_hub_mixin_config", None) in (None, {})):
+ instance._hub_mixin_config = config
+
+ return instance
+
+ @classmethod
+ def _from_pretrained(
+ cls: Type[T],
+ *,
+ model_id: str,
+ revision: Optional[str],
+ cache_dir: Optional[Union[str, Path]],
+ force_download: bool,
+ proxies: Optional[Dict],
+ resume_download: Optional[bool],
+ local_files_only: bool,
+ token: Optional[Union[str, bool]],
+ **model_kwargs,
+ ) -> T:
+ """Overwrite this method in subclass to define how to load your model from pretrained.
+
+ Use [`hf_hub_download`] or [`snapshot_download`] to download files from the Hub before loading them. Most
+ args taken as input can be directly passed to those 2 methods. If needed, you can add more arguments to this
+ method using "model_kwargs". For example [`PyTorchModelHubMixin._from_pretrained`] takes as input a `map_location`
+ parameter to set on which device the model should be loaded.
+
+ Check out our [integration guide](../guides/integrations) for more instructions.
+
+ Args:
+ model_id (`str`):
+ ID of the model to load from the Huggingface Hub (e.g. `bigscience/bloom`).
+ revision (`str`, *optional*):
+ Revision of the model on the Hub. Can be a branch name, a git tag or any commit id. Defaults to the
+ latest commit on `main` branch.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether to force (re-)downloading the model weights and configuration files from the Hub, overriding
+ the existing cache.
+ proxies (`Dict[str, str]`, *optional*):
+ A dictionary of proxy servers to use by protocol or endpoint (e.g., `{'http': 'foo.bar:3128',
+ 'http://hostname': 'foo.bar:4012'}`).
+ token (`str` or `bool`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. By default, it will use the token
+ cached when running `huggingface-cli login`.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the local cached file if it exists.
+ model_kwargs:
+ Additional keyword arguments passed along to the [`~ModelHubMixin._from_pretrained`] method.
+ """
+ raise NotImplementedError
+
+ @validate_hf_hub_args
+ def push_to_hub(
+ self,
+ repo_id: str,
+ *,
+ config: Optional[Union[dict, DataclassInstance]] = None,
+ commit_message: str = "Push model using huggingface_hub.",
+ private: Optional[bool] = None,
+ token: Optional[str] = None,
+ branch: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ model_card_kwargs: Optional[Dict[str, Any]] = None,
+ ) -> str:
+ """
+ Upload model checkpoint to the Hub.
+
+ Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
+ `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
+ details.
+
+ Args:
+ repo_id (`str`):
+ ID of the repository to push to (example: `"username/my-model"`).
+ config (`dict` or `DataclassInstance`, *optional*):
+ Model configuration specified as a key/value dictionary or a dataclass instance.
+ commit_message (`str`, *optional*):
+ Message to commit while pushing.
+ private (`bool`, *optional*):
+ Whether the repository created should be private.
+ If `None` (default), the repo will be public unless the organization's default is private.
+ token (`str`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. By default, it will use the token
+ cached when running `huggingface-cli login`.
+ branch (`str`, *optional*):
+ The git branch on which to push the model. This defaults to `"main"`.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `branch` with that commit. Defaults to `False`.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are pushed.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not pushed.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo.
+ model_card_kwargs (`Dict[str, Any]`, *optional*):
+ Additional arguments passed to the model card template to customize the model card.
+
+ Returns:
+ The url of the commit of your model in the given repository.
+ """
+ api = HfApi(token=token)
+ repo_id = api.create_repo(repo_id=repo_id, private=private, exist_ok=True).repo_id
+
+ # Push the files to the repo in a single commit
+ with SoftTemporaryDirectory() as tmp:
+ saved_path = Path(tmp) / repo_id
+ self.save_pretrained(saved_path, config=config, model_card_kwargs=model_card_kwargs)
+ return api.upload_folder(
+ repo_id=repo_id,
+ repo_type="model",
+ folder_path=saved_path,
+ commit_message=commit_message,
+ revision=branch,
+ create_pr=create_pr,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ delete_patterns=delete_patterns,
+ )
+
+ def generate_model_card(self, *args, **kwargs) -> ModelCard:
+ card = ModelCard.from_template(
+ card_data=self._hub_mixin_info.model_card_data,
+ template_str=self._hub_mixin_info.model_card_template,
+ repo_url=self._hub_mixin_info.repo_url,
+ docs_url=self._hub_mixin_info.docs_url,
+ **kwargs,
+ )
+ return card
+
+
+class PyTorchModelHubMixin(ModelHubMixin):
+ """
+ Implementation of [`ModelHubMixin`] to provide model Hub upload/download capabilities to PyTorch models. The model
+ is set in evaluation mode by default using `model.eval()` (dropout modules are deactivated). To train the model,
+ you should first set it back in training mode with `model.train()`.
+
+ See [`ModelHubMixin`] for more details on how to use the mixin.
+
+ Example:
+
+ ```python
+ >>> import torch
+ >>> import torch.nn as nn
+ >>> from huggingface_hub import PyTorchModelHubMixin
+
+ >>> class MyModel(
+ ... nn.Module,
+ ... PyTorchModelHubMixin,
+ ... library_name="keras-nlp",
+ ... repo_url="https://github.com/keras-team/keras-nlp",
+ ... docs_url="https://keras.io/keras_nlp/",
+ ... # ^ optional metadata to generate model card
+ ... ):
+ ... def __init__(self, hidden_size: int = 512, vocab_size: int = 30000, output_size: int = 4):
+ ... super().__init__()
+ ... self.param = nn.Parameter(torch.rand(hidden_size, vocab_size))
+ ... self.linear = nn.Linear(output_size, vocab_size)
+
+ ... def forward(self, x):
+ ... return self.linear(x + self.param)
+ >>> model = MyModel(hidden_size=256)
+
+ # Save model weights to local directory
+ >>> model.save_pretrained("my-awesome-model")
+
+ # Push model weights to the Hub
+ >>> model.push_to_hub("my-awesome-model")
+
+ # Download and initialize weights from the Hub
+ >>> model = MyModel.from_pretrained("username/my-awesome-model")
+ >>> model.hidden_size
+ 256
+ ```
+ """
+
+ def __init_subclass__(cls, *args, tags: Optional[List[str]] = None, **kwargs) -> None:
+ tags = tags or []
+ tags.append("pytorch_model_hub_mixin")
+ kwargs["tags"] = tags
+ return super().__init_subclass__(*args, **kwargs)
+
+ def _save_pretrained(self, save_directory: Path) -> None:
+ """Save weights from a Pytorch model to a local directory."""
+ model_to_save = self.module if hasattr(self, "module") else self # type: ignore
+ save_model_as_safetensor(model_to_save, str(save_directory / constants.SAFETENSORS_SINGLE_FILE))
+
+ @classmethod
+ def _from_pretrained(
+ cls,
+ *,
+ model_id: str,
+ revision: Optional[str],
+ cache_dir: Optional[Union[str, Path]],
+ force_download: bool,
+ proxies: Optional[Dict],
+ resume_download: Optional[bool],
+ local_files_only: bool,
+ token: Union[str, bool, None],
+ map_location: str = "cpu",
+ strict: bool = False,
+ **model_kwargs,
+ ):
+ """Load Pytorch pretrained weights and return the loaded model."""
+ model = cls(**model_kwargs)
+ if os.path.isdir(model_id):
+ print("Loading weights from local directory")
+ model_file = os.path.join(model_id, constants.SAFETENSORS_SINGLE_FILE)
+ return cls._load_as_safetensor(model, model_file, map_location, strict)
+ else:
+ try:
+ model_file = hf_hub_download(
+ repo_id=model_id,
+ filename=constants.SAFETENSORS_SINGLE_FILE,
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ )
+ return cls._load_as_safetensor(model, model_file, map_location, strict)
+ except EntryNotFoundError:
+ model_file = hf_hub_download(
+ repo_id=model_id,
+ filename=constants.PYTORCH_WEIGHTS_NAME,
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ )
+ return cls._load_as_pickle(model, model_file, map_location, strict)
+
+ @classmethod
+ def _load_as_pickle(cls, model: T, model_file: str, map_location: str, strict: bool) -> T:
+ state_dict = torch.load(model_file, map_location=torch.device(map_location), weights_only=True)
+ model.load_state_dict(state_dict, strict=strict) # type: ignore
+ model.eval() # type: ignore
+ return model
+
+ @classmethod
+ def _load_as_safetensor(cls, model: T, model_file: str, map_location: str, strict: bool) -> T:
+ if packaging.version.parse(safetensors.__version__) < packaging.version.parse("0.4.3"): # type: ignore [attr-defined]
+ load_model_as_safetensor(model, model_file, strict=strict) # type: ignore [arg-type]
+ if map_location != "cpu":
+ logger.warning(
+ "Loading model weights on other devices than 'cpu' is not supported natively in your version of safetensors."
+ " This means that the model is loaded on 'cpu' first and then copied to the device."
+ " This leads to a slower loading time."
+ " Please update safetensors to version 0.4.3 or above for improved performance."
+ )
+ model.to(map_location) # type: ignore [attr-defined]
+ else:
+ safetensors.torch.load_model(model, model_file, strict=strict, device=map_location) # type: ignore [arg-type]
+ return model
+
+
+def _load_dataclass(datacls: Type[DataclassInstance], data: dict) -> DataclassInstance:
+ """Load a dataclass instance from a dictionary.
+
+ Fields not expected by the dataclass are ignored.
+ """
+ return datacls(**{k: v for k, v in data.items() if k in datacls.__dataclass_fields__})
diff --git a/env/Lib/site-packages/huggingface_hub/inference/__init__.py b/env/Lib/site-packages/huggingface_hub/inference/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_client.py b/env/Lib/site-packages/huggingface_hub/inference/_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..c59ebb5169c0e9243c4a5f465cc2df10d469df74
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_client.py
@@ -0,0 +1,3516 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Related resources:
+# https://huggingface.co/tasks
+# https://huggingface.co/docs/huggingface.js/inference/README
+# https://github.com/huggingface/huggingface.js/tree/main/packages/inference/src
+# https://github.com/huggingface/text-generation-inference/tree/main/clients/python
+# https://github.com/huggingface/text-generation-inference/blob/main/clients/python/text_generation/client.py
+# https://huggingface.slack.com/archives/C03E4DQ9LAJ/p1680169099087869
+# https://github.com/huggingface/unity-api#tasks
+#
+# Some TODO:
+# - add all tasks
+#
+# NOTE: the philosophy of this client is "let's make it as easy as possible to use it, even if less optimized". Some
+# examples of how it translates:
+# - Timeout / Server unavailable is handled by the client in a single "timeout" parameter.
+# - Files can be provided as bytes, file paths, or URLs and the client will try to "guess" the type.
+# - Images are parsed as PIL.Image for easier manipulation.
+# - Provides a "recommended model" for each task => suboptimal but user-wise quicker to get a first script running.
+# - Only the main parameters are publicly exposed. Power users can always read the docs for more options.
+import base64
+import logging
+import re
+import warnings
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Literal, Optional, Union, overload
+
+from requests import HTTPError
+
+from huggingface_hub import constants
+from huggingface_hub.errors import BadRequestError, InferenceTimeoutError
+from huggingface_hub.inference._common import (
+ TASKS_EXPECTING_IMAGES,
+ ContentT,
+ ModelStatus,
+ RequestParameters,
+ _b64_encode,
+ _b64_to_image,
+ _bytes_to_dict,
+ _bytes_to_image,
+ _bytes_to_list,
+ _get_unsupported_text_generation_kwargs,
+ _import_numpy,
+ _open_as_binary,
+ _set_unsupported_text_generation_kwargs,
+ _stream_chat_completion_response,
+ _stream_text_generation_response,
+ raise_text_generation_error,
+)
+from huggingface_hub.inference._generated.types import (
+ AudioClassificationOutputElement,
+ AudioClassificationOutputTransform,
+ AudioToAudioOutputElement,
+ AutomaticSpeechRecognitionOutput,
+ ChatCompletionInputGrammarType,
+ ChatCompletionInputStreamOptions,
+ ChatCompletionInputTool,
+ ChatCompletionInputToolChoiceClass,
+ ChatCompletionInputToolChoiceEnum,
+ ChatCompletionOutput,
+ ChatCompletionStreamOutput,
+ DocumentQuestionAnsweringOutputElement,
+ FillMaskOutputElement,
+ ImageClassificationOutputElement,
+ ImageClassificationOutputTransform,
+ ImageSegmentationOutputElement,
+ ImageSegmentationSubtask,
+ ImageToImageTargetSize,
+ ImageToTextOutput,
+ ObjectDetectionOutputElement,
+ Padding,
+ QuestionAnsweringOutputElement,
+ SummarizationOutput,
+ SummarizationTruncationStrategy,
+ TableQuestionAnsweringOutputElement,
+ TextClassificationOutputElement,
+ TextClassificationOutputTransform,
+ TextGenerationInputGrammarType,
+ TextGenerationOutput,
+ TextGenerationStreamOutput,
+ TextToSpeechEarlyStoppingEnum,
+ TokenClassificationAggregationStrategy,
+ TokenClassificationOutputElement,
+ TranslationOutput,
+ TranslationTruncationStrategy,
+ VisualQuestionAnsweringOutputElement,
+ ZeroShotClassificationOutputElement,
+ ZeroShotImageClassificationOutputElement,
+)
+from huggingface_hub.inference._providers import PROVIDER_T, HFInferenceTask, get_provider_helper
+from huggingface_hub.utils import build_hf_headers, get_session, hf_raise_for_status
+from huggingface_hub.utils._deprecation import _deprecate_arguments, _deprecate_method
+
+
+if TYPE_CHECKING:
+ import numpy as np
+ from PIL.Image import Image
+
+logger = logging.getLogger(__name__)
+
+
+MODEL_KWARGS_NOT_USED_REGEX = re.compile(r"The following `model_kwargs` are not used by the model: \[(.*?)\]")
+
+
+class InferenceClient:
+ """
+ Initialize a new Inference Client.
+
+ [`InferenceClient`] aims to provide a unified experience to perform inference. The client can be used
+ seamlessly with either the (free) Inference API, self-hosted Inference Endpoints, or third-party Inference Providers.
+
+ Args:
+ model (`str`, `optional`):
+ The model to run inference with. Can be a model id hosted on the Hugging Face Hub, e.g. `meta-llama/Meta-Llama-3-8B-Instruct`
+ or a URL to a deployed Inference Endpoint. Defaults to None, in which case a recommended model is
+ automatically selected for the task.
+ Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2
+ arguments are mutually exclusive. If using `base_url` for chat completion, the `/chat/completions` suffix
+ path will be appended to the base URL (see the [TGI Messages API](https://huggingface.co/docs/text-generation-inference/en/messages_api)
+ documentation for details). When passing a URL as `model`, the client will not append any suffix path to it.
+ provider (`str`, *optional*):
+ Name of the provider to use for inference. Can be `"black-forest-labs"`, `"fal-ai"`, `"fireworks-ai"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"replicate"`, "sambanova"` or `"together"`.
+ defaults to hf-inference (Hugging Face Serverless Inference API).
+ If model is a URL or `base_url` is passed, then `provider` is not used.
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Pass `token=False` if you don't want to send your token to the server.
+ Note: for better compatibility with OpenAI's client, `token` has been aliased as `api_key`. Those 2
+ arguments are mutually exclusive and have the exact same behavior.
+ timeout (`float`, `optional`):
+ The maximum number of seconds to wait for a response from the server. Loading a new model in Inference
+ API can take up to several minutes. Defaults to None, meaning it will loop until the server is available.
+ headers (`Dict[str, str]`, `optional`):
+ Additional headers to send to the server. By default only the authorization and user-agent headers are sent.
+ Values in this dictionary will override the default values.
+ cookies (`Dict[str, str]`, `optional`):
+ Additional cookies to send to the server.
+ proxies (`Any`, `optional`):
+ Proxies to use for the request.
+ base_url (`str`, `optional`):
+ Base URL to run inference. This is a duplicated argument from `model` to make [`InferenceClient`]
+ follow the same pattern as `openai.OpenAI` client. Cannot be used if `model` is set. Defaults to None.
+ api_key (`str`, `optional`):
+ Token to use for authentication. This is a duplicated argument from `token` to make [`InferenceClient`]
+ follow the same pattern as `openai.OpenAI` client. Cannot be used if `token` is set. Defaults to None.
+ """
+
+ def __init__(
+ self,
+ model: Optional[str] = None,
+ *,
+ provider: Optional[PROVIDER_T] = None,
+ token: Optional[str] = None,
+ timeout: Optional[float] = None,
+ headers: Optional[Dict[str, str]] = None,
+ cookies: Optional[Dict[str, str]] = None,
+ proxies: Optional[Any] = None,
+ # OpenAI compatibility
+ base_url: Optional[str] = None,
+ api_key: Optional[str] = None,
+ ) -> None:
+ if model is not None and base_url is not None:
+ raise ValueError(
+ "Received both `model` and `base_url` arguments. Please provide only one of them."
+ " `base_url` is an alias for `model` to make the API compatible with OpenAI's client."
+ " If using `base_url` for chat completion, the `/chat/completions` suffix path will be appended to the base url."
+ " When passing a URL as `model`, the client will not append any suffix path to it."
+ )
+ if token is not None and api_key is not None:
+ raise ValueError(
+ "Received both `token` and `api_key` arguments. Please provide only one of them."
+ " `api_key` is an alias for `token` to make the API compatible with OpenAI's client."
+ " It has the exact same behavior as `token`."
+ )
+
+ self.model: Optional[str] = base_url or model
+ self.token: Optional[str] = token if token is not None else api_key
+ self.headers = headers if headers is not None else {}
+
+ # Configure provider
+ self.provider = provider if provider is not None else "hf-inference"
+
+ self.cookies = cookies
+ self.timeout = timeout
+ self.proxies = proxies
+
+ def __repr__(self):
+ return f""
+
+ @overload
+ def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[False] = ...,
+ ) -> bytes: ...
+
+ @overload
+ def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[True] = ...,
+ ) -> Iterable[bytes]: ...
+
+ @overload
+ def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, Iterable[bytes]]: ...
+
+ @_deprecate_method(
+ version="0.31.0",
+ message=(
+ "Making direct POST requests to the inference server is not supported anymore. "
+ "Please use task methods instead (e.g. `InferenceClient.chat_completion`). "
+ "If your use case is not supported, please open an issue in https://github.com/huggingface/huggingface_hub."
+ ),
+ )
+ def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, Iterable[bytes]]:
+ """
+ Make a POST request to the inference server.
+
+ This method is deprecated and will be removed in the future.
+ Please use task methods instead (e.g. `InferenceClient.chat_completion`).
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(
+ "Cannot use `post` with another provider than `hf-inference`. "
+ "`InferenceClient.post` is deprecated and should not be used directly anymore."
+ )
+ provider_helper = HFInferenceTask(task or "unknown")
+ mapped_model = provider_helper._prepare_mapped_model(model or self.model)
+ url = provider_helper._prepare_url(self.token, mapped_model) # type: ignore[arg-type]
+ headers = provider_helper._prepare_headers(self.headers, self.token) # type: ignore[arg-type]
+ return self._inner_post(
+ request_parameters=RequestParameters(
+ url=url,
+ task=task or "unknown",
+ model=model or "unknown",
+ json=json,
+ data=data,
+ headers=headers,
+ ),
+ stream=stream,
+ )
+
+ @overload
+ def _inner_post( # type: ignore[misc]
+ self, request_parameters: RequestParameters, *, stream: Literal[False] = ...
+ ) -> bytes: ...
+
+ @overload
+ def _inner_post( # type: ignore[misc]
+ self, request_parameters: RequestParameters, *, stream: Literal[True] = ...
+ ) -> Iterable[bytes]: ...
+
+ @overload
+ def _inner_post(
+ self, request_parameters: RequestParameters, *, stream: bool = False
+ ) -> Union[bytes, Iterable[bytes]]: ...
+
+ def _inner_post(
+ self, request_parameters: RequestParameters, *, stream: bool = False
+ ) -> Union[bytes, Iterable[bytes]]:
+ """Make a request to the inference server."""
+ # TODO: this should be handled in provider helpers directly
+ if request_parameters.task in TASKS_EXPECTING_IMAGES and "Accept" not in request_parameters.headers:
+ request_parameters.headers["Accept"] = "image/png"
+
+ while True:
+ with _open_as_binary(request_parameters.data) as data_as_binary:
+ try:
+ response = get_session().post(
+ request_parameters.url,
+ json=request_parameters.json,
+ data=data_as_binary,
+ headers=request_parameters.headers,
+ cookies=self.cookies,
+ timeout=self.timeout,
+ stream=stream,
+ proxies=self.proxies,
+ )
+ except TimeoutError as error:
+ # Convert any `TimeoutError` to a `InferenceTimeoutError`
+ raise InferenceTimeoutError(f"Inference call timed out: {request_parameters.url}") from error # type: ignore
+
+ try:
+ hf_raise_for_status(response)
+ return response.iter_lines() if stream else response.content
+ except HTTPError as error:
+ if error.response.status_code == 422 and request_parameters.task != "unknown":
+ msg = str(error.args[0])
+ if len(error.response.text) > 0:
+ msg += f"\n{error.response.text}\n"
+ error.args = (msg,) + error.args[1:]
+ raise
+
+ def audio_classification(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ top_k: Optional[int] = None,
+ function_to_apply: Optional["AudioClassificationOutputTransform"] = None,
+ ) -> List[AudioClassificationOutputElement]:
+ """
+ Perform audio classification on the provided audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content to classify. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model to use for audio classification. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio classification will be used.
+ top_k (`int`, *optional*):
+ When specified, limits the output to the top K most probable classes.
+ function_to_apply (`"AudioClassificationOutputTransform"`, *optional*):
+ The function to apply to the model outputs in order to retrieve the scores.
+
+ Returns:
+ `List[AudioClassificationOutputElement]`: List of [`AudioClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.audio_classification("audio.flac")
+ [
+ AudioClassificationOutputElement(score=0.4976358711719513, label='hap'),
+ AudioClassificationOutputElement(score=0.3677836060523987, label='neu'),
+ ...
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="audio-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=audio,
+ parameters={"function_to_apply": function_to_apply, "top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return AudioClassificationOutputElement.parse_obj_as_list(response)
+
+ def audio_to_audio(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[AudioToAudioOutputElement]:
+ """
+ Performs multiple tasks related to audio-to-audio depending on the model (eg: speech enhancement, source separation).
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content for the model. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model can be any model which takes an audio file and returns another audio file. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio_to_audio will be used.
+
+ Returns:
+ `List[AudioToAudioOutputElement]`: A list of [`AudioToAudioOutputElement`] items containing audios label, content-type, and audio content in blob.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> audio_output = client.audio_to_audio("audio.flac")
+ >>> for i, item in enumerate(audio_output):
+ >>> with open(f"output_{i}.flac", "wb") as f:
+ f.write(item.blob)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="audio-to-audio")
+ request_parameters = provider_helper.prepare_request(
+ inputs=audio,
+ parameters={},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ audio_output = AudioToAudioOutputElement.parse_obj_as_list(response)
+ for item in audio_output:
+ item.blob = base64.b64decode(item.blob)
+ return audio_output
+
+ def automatic_speech_recognition(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> AutomaticSpeechRecognitionOutput:
+ """
+ Perform automatic speech recognition (ASR or audio-to-text) on the given audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The content to transcribe. It can be raw audio bytes, local audio file, or a URL to an audio file.
+ model (`str`, *optional*):
+ The model to use for ASR. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for ASR will be used.
+ extra_body (`Dict`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+ Returns:
+ [`AutomaticSpeechRecognitionOutput`]: An item containing the transcribed text and optionally the timestamp chunks.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.automatic_speech_recognition("hello_world.flac").text
+ "hello world"
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="automatic-speech-recognition")
+ request_parameters = provider_helper.prepare_request(
+ inputs=audio,
+ parameters={**(extra_body or {})},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return AutomaticSpeechRecognitionOutput.parse_obj_as_instance(response)
+
+ @overload
+ def chat_completion( # type: ignore
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[False] = False,
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> ChatCompletionOutput: ...
+
+ @overload
+ def chat_completion( # type: ignore
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[True] = True,
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> Iterable[ChatCompletionStreamOutput]: ...
+
+ @overload
+ def chat_completion(
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> Union[ChatCompletionOutput, Iterable[ChatCompletionStreamOutput]]: ...
+
+ def chat_completion(
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ # Parameters from ChatCompletionInput (handled manually)
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> Union[ChatCompletionOutput, Iterable[ChatCompletionStreamOutput]]:
+ """
+ A method for completing conversations using a specified language model.
+
+
+
+ The `client.chat_completion` method is aliased as `client.chat.completions.create` for compatibility with OpenAI's client.
+ Inputs and outputs are strictly the same and using either syntax will yield the same results.
+ Check out the [Inference guide](https://huggingface.co/docs/huggingface_hub/guides/inference#openai-compatibility)
+ for more details about OpenAI's compatibility.
+
+
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ messages (List of [`ChatCompletionInputMessage`]):
+ Conversation history consisting of roles and content pairs.
+ model (`str`, *optional*):
+ The model to use for chat-completion. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for chat-based text-generation will be used.
+ See https://huggingface.co/tasks/text-generation for more details.
+ If `model` is a model ID, it is passed to the server as the `model` parameter. If you want to define a
+ custom URL while setting `model` in the request payload, you must set `base_url` when initializing [`InferenceClient`].
+ frequency_penalty (`float`, *optional*):
+ Penalizes new tokens based on their existing frequency
+ in the text so far. Range: [-2.0, 2.0]. Defaults to 0.0.
+ logit_bias (`List[float]`, *optional*):
+ Adjusts the likelihood of specific tokens appearing in the generated output.
+ logprobs (`bool`, *optional*):
+ Whether to return log probabilities of the output tokens or not. If true, returns the log
+ probabilities of each output token returned in the content of message.
+ max_tokens (`int`, *optional*):
+ Maximum number of tokens allowed in the response. Defaults to 100.
+ n (`int`, *optional*):
+ The number of completions to generate for each prompt.
+ presence_penalty (`float`, *optional*):
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the
+ text so far, increasing the model's likelihood to talk about new topics.
+ response_format ([`ChatCompletionInputGrammarType`], *optional*):
+ Grammar constraints. Can be either a JSONSchema or a regex.
+ seed (Optional[`int`], *optional*):
+ Seed for reproducible control flow. Defaults to None.
+ stop (`List[str]`, *optional*):
+ Up to four strings which trigger the end of the response.
+ Defaults to None.
+ stream (`bool`, *optional*):
+ Enable realtime streaming of responses. Defaults to False.
+ stream_options ([`ChatCompletionInputStreamOptions`], *optional*):
+ Options for streaming completions.
+ temperature (`float`, *optional*):
+ Controls randomness of the generations. Lower values ensure
+ less random completions. Range: [0, 2]. Defaults to 1.0.
+ top_logprobs (`int`, *optional*):
+ An integer between 0 and 5 specifying the number of most likely tokens to return at each token
+ position, each with an associated log probability. logprobs must be set to true if this parameter is
+ used.
+ top_p (`float`, *optional*):
+ Fraction of the most likely next words to sample from.
+ Must be between 0 and 1. Defaults to 1.0.
+ tool_choice ([`ChatCompletionInputToolChoiceClass`] or [`ChatCompletionInputToolChoiceEnum`], *optional*):
+ The tool to use for the completion. Defaults to "auto".
+ tool_prompt (`str`, *optional*):
+ A prompt to be appended before the tools.
+ tools (List of [`ChatCompletionInputTool`], *optional*):
+ A list of tools the model may call. Currently, only functions are supported as a tool. Use this to
+ provide a list of functions the model may generate JSON inputs for.
+ extra_body (`Dict`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+ Returns:
+ [`ChatCompletionOutput`] or Iterable of [`ChatCompletionStreamOutput`]:
+ Generated text returned from the server:
+ - if `stream=False`, the generated text is returned as a [`ChatCompletionOutput`] (default).
+ - if `stream=True`, the generated text is returned token by token as a sequence of [`ChatCompletionStreamOutput`].
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> messages = [{"role": "user", "content": "What is the capital of France?"}]
+ >>> client = InferenceClient("meta-llama/Meta-Llama-3-8B-Instruct")
+ >>> client.chat_completion(messages, max_tokens=100)
+ ChatCompletionOutput(
+ choices=[
+ ChatCompletionOutputComplete(
+ finish_reason='eos_token',
+ index=0,
+ message=ChatCompletionOutputMessage(
+ role='assistant',
+ content='The capital of France is Paris.',
+ name=None,
+ tool_calls=None
+ ),
+ logprobs=None
+ )
+ ],
+ created=1719907176,
+ id='',
+ model='meta-llama/Meta-Llama-3-8B-Instruct',
+ object='text_completion',
+ system_fingerprint='2.0.4-sha-f426a33',
+ usage=ChatCompletionOutputUsage(
+ completion_tokens=8,
+ prompt_tokens=17,
+ total_tokens=25
+ )
+ )
+ ```
+
+ Example using streaming:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> messages = [{"role": "user", "content": "What is the capital of France?"}]
+ >>> client = InferenceClient("meta-llama/Meta-Llama-3-8B-Instruct")
+ >>> for token in client.chat_completion(messages, max_tokens=10, stream=True):
+ ... print(token)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content='The', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' capital', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ (...)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' may', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ```
+
+ Example using OpenAI's syntax:
+ ```py
+ # instead of `from openai import OpenAI`
+ from huggingface_hub import InferenceClient
+
+ # instead of `client = OpenAI(...)`
+ client = InferenceClient(
+ base_url=...,
+ api_key=...,
+ )
+
+ output = client.chat.completions.create(
+ model="meta-llama/Meta-Llama-3-8B-Instruct",
+ messages=[
+ {"role": "system", "content": "You are a helpful assistant."},
+ {"role": "user", "content": "Count to 10"},
+ ],
+ stream=True,
+ max_tokens=1024,
+ )
+
+ for chunk in output:
+ print(chunk.choices[0].delta.content)
+ ```
+
+ Example using a third-party provider directly with extra (provider-specific) parameters. Usage will be billed on your Together AI account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="together", # Use Together AI provider
+ ... api_key="", # Pass your Together API key directly
+ ... )
+ >>> client.chat_completion(
+ ... model="meta-llama/Meta-Llama-3-8B-Instruct",
+ ... messages=[{"role": "user", "content": "What is the capital of France?"}],
+ ... extra_body={"safety_model": "Meta-Llama/Llama-Guard-7b"},
+ ... )
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="sambanova", # Use Sambanova provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> client.chat_completion(
+ ... model="meta-llama/Meta-Llama-3-8B-Instruct",
+ ... messages=[{"role": "user", "content": "What is the capital of France?"}],
+ ... )
+ ```
+
+ Example using Image + Text as input:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+
+ # provide a remote URL
+ >>> image_url ="https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg"
+ # or a base64-encoded image
+ >>> image_path = "/path/to/image.jpeg"
+ >>> with open(image_path, "rb") as f:
+ ... base64_image = base64.b64encode(f.read()).decode("utf-8")
+ >>> image_url = f"data:image/jpeg;base64,{base64_image}"
+
+ >>> client = InferenceClient("meta-llama/Llama-3.2-11B-Vision-Instruct")
+ >>> output = client.chat.completions.create(
+ ... messages=[
+ ... {
+ ... "role": "user",
+ ... "content": [
+ ... {
+ ... "type": "image_url",
+ ... "image_url": {"url": image_url},
+ ... },
+ ... {
+ ... "type": "text",
+ ... "text": "Describe this image in one sentence.",
+ ... },
+ ... ],
+ ... },
+ ... ],
+ ... )
+ >>> output
+ The image depicts the iconic Statue of Liberty situated in New York Harbor, New York, on a clear day.
+ ```
+
+ Example using tools:
+ ```py
+ >>> client = InferenceClient("meta-llama/Meta-Llama-3-70B-Instruct")
+ >>> messages = [
+ ... {
+ ... "role": "system",
+ ... "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.",
+ ... },
+ ... {
+ ... "role": "user",
+ ... "content": "What's the weather like the next 3 days in San Francisco, CA?",
+ ... },
+ ... ]
+ >>> tools = [
+ ... {
+ ... "type": "function",
+ ... "function": {
+ ... "name": "get_current_weather",
+ ... "description": "Get the current weather",
+ ... "parameters": {
+ ... "type": "object",
+ ... "properties": {
+ ... "location": {
+ ... "type": "string",
+ ... "description": "The city and state, e.g. San Francisco, CA",
+ ... },
+ ... "format": {
+ ... "type": "string",
+ ... "enum": ["celsius", "fahrenheit"],
+ ... "description": "The temperature unit to use. Infer this from the users location.",
+ ... },
+ ... },
+ ... "required": ["location", "format"],
+ ... },
+ ... },
+ ... },
+ ... {
+ ... "type": "function",
+ ... "function": {
+ ... "name": "get_n_day_weather_forecast",
+ ... "description": "Get an N-day weather forecast",
+ ... "parameters": {
+ ... "type": "object",
+ ... "properties": {
+ ... "location": {
+ ... "type": "string",
+ ... "description": "The city and state, e.g. San Francisco, CA",
+ ... },
+ ... "format": {
+ ... "type": "string",
+ ... "enum": ["celsius", "fahrenheit"],
+ ... "description": "The temperature unit to use. Infer this from the users location.",
+ ... },
+ ... "num_days": {
+ ... "type": "integer",
+ ... "description": "The number of days to forecast",
+ ... },
+ ... },
+ ... "required": ["location", "format", "num_days"],
+ ... },
+ ... },
+ ... },
+ ... ]
+
+ >>> response = client.chat_completion(
+ ... model="meta-llama/Meta-Llama-3-70B-Instruct",
+ ... messages=messages,
+ ... tools=tools,
+ ... tool_choice="auto",
+ ... max_tokens=500,
+ ... )
+ >>> response.choices[0].message.tool_calls[0].function
+ ChatCompletionOutputFunctionDefinition(
+ arguments={
+ 'location': 'San Francisco, CA',
+ 'format': 'fahrenheit',
+ 'num_days': 3
+ },
+ name='get_n_day_weather_forecast',
+ description=None
+ )
+ ```
+
+ Example using response_format:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient("meta-llama/Meta-Llama-3-70B-Instruct")
+ >>> messages = [
+ ... {
+ ... "role": "user",
+ ... "content": "I saw a puppy a cat and a raccoon during my bike ride in the park. What did I saw and when?",
+ ... },
+ ... ]
+ >>> response_format = {
+ ... "type": "json",
+ ... "value": {
+ ... "properties": {
+ ... "location": {"type": "string"},
+ ... "activity": {"type": "string"},
+ ... "animals_seen": {"type": "integer", "minimum": 1, "maximum": 5},
+ ... "animals": {"type": "array", "items": {"type": "string"}},
+ ... },
+ ... "required": ["location", "activity", "animals_seen", "animals"],
+ ... },
+ ... }
+ >>> response = client.chat_completion(
+ ... messages=messages,
+ ... response_format=response_format,
+ ... max_tokens=500,
+ )
+ >>> response.choices[0].message.content
+ '{\n\n"activity": "bike ride",\n"animals": ["puppy", "cat", "raccoon"],\n"animals_seen": 3,\n"location": "park"}'
+ ```
+ """
+ # Get the provider helper
+ provider_helper = get_provider_helper(self.provider, task="conversational")
+
+ # Since `chat_completion(..., model=xxx)` is also a payload parameter for the server, we need to handle 'model' differently.
+ # `self.model` takes precedence over 'model' argument for building URL.
+ # `model` takes precedence for payload value.
+ model_id_or_url = self.model or model
+ payload_model = model or self.model
+
+ # Prepare the payload
+ parameters = {
+ "model": payload_model,
+ "frequency_penalty": frequency_penalty,
+ "logit_bias": logit_bias,
+ "logprobs": logprobs,
+ "max_tokens": max_tokens,
+ "n": n,
+ "presence_penalty": presence_penalty,
+ "response_format": response_format,
+ "seed": seed,
+ "stop": stop,
+ "temperature": temperature,
+ "tool_choice": tool_choice,
+ "tool_prompt": tool_prompt,
+ "tools": tools,
+ "top_logprobs": top_logprobs,
+ "top_p": top_p,
+ "stream": stream,
+ "stream_options": stream_options,
+ **(extra_body or {}),
+ }
+ request_parameters = provider_helper.prepare_request(
+ inputs=messages,
+ parameters=parameters,
+ headers=self.headers,
+ model=model_id_or_url,
+ api_key=self.token,
+ )
+ data = self._inner_post(request_parameters, stream=stream)
+
+ if stream:
+ return _stream_chat_completion_response(data) # type: ignore[arg-type]
+
+ return ChatCompletionOutput.parse_obj_as_instance(data) # type: ignore[arg-type]
+
+ def document_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ doc_stride: Optional[int] = None,
+ handle_impossible_answer: Optional[bool] = None,
+ lang: Optional[str] = None,
+ max_answer_len: Optional[int] = None,
+ max_question_len: Optional[int] = None,
+ max_seq_len: Optional[int] = None,
+ top_k: Optional[int] = None,
+ word_boxes: Optional[List[Union[List[float], str]]] = None,
+ ) -> List[DocumentQuestionAnsweringOutputElement]:
+ """
+ Answer questions on document images.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the document question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended document question answering model will be used.
+ Defaults to None.
+ doc_stride (`int`, *optional*):
+ If the words in the document are too long to fit with the question for the model, it will be split in
+ several chunks with some overlap. This argument controls the size of that overlap.
+ handle_impossible_answer (`bool`, *optional*):
+ Whether to accept impossible as an answer
+ lang (`str`, *optional*):
+ Language to use while running OCR. Defaults to english.
+ max_answer_len (`int`, *optional*):
+ The maximum length of predicted answers (e.g., only answers with a shorter length are considered).
+ max_question_len (`int`, *optional*):
+ The maximum length of the question after tokenization. It will be truncated if needed.
+ max_seq_len (`int`, *optional*):
+ The maximum length of the total sentence (context + question) in tokens of each chunk passed to the
+ model. The context will be split in several chunks (using doc_stride as overlap) if needed.
+ top_k (`int`, *optional*):
+ The number of answers to return (will be chosen by order of likelihood). Can return less than top_k
+ answers if there are not enough options available within the context.
+ word_boxes (`List[Union[List[float], str`, *optional*):
+ A list of words and bounding boxes (normalized 0->1000). If provided, the inference will skip the OCR
+ step and use the provided bounding boxes instead.
+ Returns:
+ `List[DocumentQuestionAnsweringOutputElement]`: a list of [`DocumentQuestionAnsweringOutputElement`] items containing the predicted label, associated probability, word ids, and page number.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.document_question_answering(image="https://huggingface.co/spaces/impira/docquery/resolve/2359223c1837a7587402bda0f2643382a6eefeab/invoice.png", question="What is the invoice number?")
+ [DocumentQuestionAnsweringOutputElement(answer='us-001', end=16, score=0.9999666213989258, start=16)]
+ ```
+ """
+ inputs: Dict[str, Any] = {"question": question, "image": _b64_encode(image)}
+ provider_helper = get_provider_helper(self.provider, task="document-question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=inputs,
+ parameters={
+ "doc_stride": doc_stride,
+ "handle_impossible_answer": handle_impossible_answer,
+ "lang": lang,
+ "max_answer_len": max_answer_len,
+ "max_question_len": max_question_len,
+ "max_seq_len": max_seq_len,
+ "top_k": top_k,
+ "word_boxes": word_boxes,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return DocumentQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ def feature_extraction(
+ self,
+ text: str,
+ *,
+ normalize: Optional[bool] = None,
+ prompt_name: Optional[str] = None,
+ truncate: Optional[bool] = None,
+ truncation_direction: Optional[Literal["Left", "Right"]] = None,
+ model: Optional[str] = None,
+ ) -> "np.ndarray":
+ """
+ Generate embeddings for a given text.
+
+ Args:
+ text (`str`):
+ The text to embed.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+ normalize (`bool`, *optional*):
+ Whether to normalize the embeddings or not.
+ Only available on server powered by Text-Embedding-Inference.
+ prompt_name (`str`, *optional*):
+ The name of the prompt that should be used by for encoding. If not set, no prompt will be applied.
+ Must be a key in the `Sentence Transformers` configuration `prompts` dictionary.
+ For example if ``prompt_name`` is "query" and the ``prompts`` is {"query": "query: ",...},
+ then the sentence "What is the capital of France?" will be encoded as "query: What is the capital of France?"
+ because the prompt text will be prepended before any text to encode.
+ truncate (`bool`, *optional*):
+ Whether to truncate the embeddings or not.
+ Only available on server powered by Text-Embedding-Inference.
+ truncation_direction (`Literal["Left", "Right"]`, *optional*):
+ Which side of the input should be truncated when `truncate=True` is passed.
+
+ Returns:
+ `np.ndarray`: The embedding representing the input text as a float32 numpy array.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.feature_extraction("Hi, who are you?")
+ array([[ 2.424802 , 2.93384 , 1.1750331 , ..., 1.240499, -0.13776633, -0.7889173 ],
+ [-0.42943227, -0.6364878 , -1.693462 , ..., 0.41978157, -2.4336355 , 0.6162071 ],
+ ...,
+ [ 0.28552425, -0.928395 , -1.2077185 , ..., 0.76810825, -2.1069427 , 0.6236161 ]], dtype=float32)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="feature-extraction")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "normalize": normalize,
+ "prompt_name": prompt_name,
+ "truncate": truncate,
+ "truncation_direction": truncation_direction,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ np = _import_numpy()
+ return np.array(_bytes_to_dict(response), dtype="float32")
+
+ def fill_mask(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ targets: Optional[List[str]] = None,
+ top_k: Optional[int] = None,
+ ) -> List[FillMaskOutputElement]:
+ """
+ Fill in a hole with a missing word (token to be precise).
+
+ Args:
+ text (`str`):
+ a string to be filled from, must contain the [MASK] token (check model card for exact name of the mask).
+ model (`str`, *optional*):
+ The model to use for the fill mask task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended fill mask model will be used.
+ targets (`List[str`, *optional*):
+ When passed, the model will limit the scores to the passed targets instead of looking up in the whole
+ vocabulary. If the provided targets are not in the model vocab, they will be tokenized and the first
+ resulting token will be used (with a warning, and that might be slower).
+ top_k (`int`, *optional*):
+ When passed, overrides the number of predictions to return.
+ Returns:
+ `List[FillMaskOutputElement]`: a list of [`FillMaskOutputElement`] items containing the predicted label, associated
+ probability, token reference, and completed text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.fill_mask("The goal of life is .")
+ [
+ FillMaskOutputElement(score=0.06897063553333282, token=11098, token_str=' happiness', sequence='The goal of life is happiness.'),
+ FillMaskOutputElement(score=0.06554922461509705, token=45075, token_str=' immortality', sequence='The goal of life is immortality.')
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="fill-mask")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={"targets": targets, "top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return FillMaskOutputElement.parse_obj_as_list(response)
+
+ def image_classification(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ function_to_apply: Optional["ImageClassificationOutputTransform"] = None,
+ top_k: Optional[int] = None,
+ ) -> List[ImageClassificationOutputElement]:
+ """
+ Perform image classification on the given image using the specified model.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to classify. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image classification. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image classification will be used.
+ function_to_apply (`"ImageClassificationOutputTransform"`, *optional*):
+ The function to apply to the model outputs in order to retrieve the scores.
+ top_k (`int`, *optional*):
+ When specified, limits the output to the top K most probable classes.
+ Returns:
+ `List[ImageClassificationOutputElement]`: a list of [`ImageClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.image_classification("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ [ImageClassificationOutputElement(label='Blenheim spaniel', score=0.9779096841812134), ...]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="image-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={"function_to_apply": function_to_apply, "top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return ImageClassificationOutputElement.parse_obj_as_list(response)
+
+ def image_segmentation(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ mask_threshold: Optional[float] = None,
+ overlap_mask_area_threshold: Optional[float] = None,
+ subtask: Optional["ImageSegmentationSubtask"] = None,
+ threshold: Optional[float] = None,
+ ) -> List[ImageSegmentationOutputElement]:
+ """
+ Perform image segmentation on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to segment. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image segmentation. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image segmentation will be used.
+ mask_threshold (`float`, *optional*):
+ Threshold to use when turning the predicted masks into binary values.
+ overlap_mask_area_threshold (`float`, *optional*):
+ Mask overlap threshold to eliminate small, disconnected segments.
+ subtask (`"ImageSegmentationSubtask"`, *optional*):
+ Segmentation task to be performed, depending on model capabilities.
+ threshold (`float`, *optional*):
+ Probability threshold to filter out predicted masks.
+ Returns:
+ `List[ImageSegmentationOutputElement]`: A list of [`ImageSegmentationOutputElement`] items containing the segmented masks and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.image_segmentation("cat.jpg")
+ [ImageSegmentationOutputElement(score=0.989008, label='LABEL_184', mask=), ...]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="audio-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={
+ "mask_threshold": mask_threshold,
+ "overlap_mask_area_threshold": overlap_mask_area_threshold,
+ "subtask": subtask,
+ "threshold": threshold,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ output = ImageSegmentationOutputElement.parse_obj_as_list(response)
+ for item in output:
+ item.mask = _b64_to_image(item.mask) # type: ignore [assignment]
+ return output
+
+ def image_to_image(
+ self,
+ image: ContentT,
+ prompt: Optional[str] = None,
+ *,
+ negative_prompt: Optional[str] = None,
+ num_inference_steps: Optional[int] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ target_size: Optional[ImageToImageTargetSize] = None,
+ **kwargs,
+ ) -> "Image":
+ """
+ Perform image-to-image translation using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for translation. It can be raw bytes, an image file, or a URL to an online image.
+ prompt (`str`, *optional*):
+ The text prompt to guide the image generation.
+ negative_prompt (`str`, *optional*):
+ One prompt to guide what NOT to include in image generation.
+ num_inference_steps (`int`, *optional*):
+ For diffusion models. The number of denoising steps. More denoising steps usually lead to a higher
+ quality image at the expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ For diffusion models. A higher guidance scale value encourages the model to generate images closely
+ linked to the text prompt at the expense of lower image quality.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+ target_size (`ImageToImageTargetSize`, *optional*):
+ The size in pixel of the output image.
+
+ Returns:
+ `Image`: The translated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> image = client.image_to_image("cat.jpg", prompt="turn the cat into a tiger")
+ >>> image.save("tiger.jpg")
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="image-to-image")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={
+ "prompt": prompt,
+ "negative_prompt": negative_prompt,
+ "target_size": target_size,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ **kwargs,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return _bytes_to_image(response)
+
+ def image_to_text(self, image: ContentT, *, model: Optional[str] = None) -> ImageToTextOutput:
+ """
+ Takes an input image and return text.
+
+ Models can have very different outputs depending on your use case (image captioning, optical character recognition
+ (OCR), Pix2Struct, etc). Please have a look to the model card to learn more about a model's specificities.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image..
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ [`ImageToTextOutput`]: The generated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.image_to_text("cat.jpg")
+ 'a cat standing in a grassy field '
+ >>> client.image_to_text("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ 'a dog laying on the grass next to a flower pot '
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="image-to-text")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ output = ImageToTextOutput.parse_obj(response)
+ return output[0] if isinstance(output, list) else output
+
+ def object_detection(
+ self, image: ContentT, *, model: Optional[str] = None, threshold: Optional[float] = None
+ ) -> List[ObjectDetectionOutputElement]:
+ """
+ Perform object detection on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to detect objects on. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for object detection. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for object detection (DETR) will be used.
+ threshold (`float`, *optional*):
+ The probability necessary to make a prediction.
+ Returns:
+ `List[ObjectDetectionOutputElement]`: A list of [`ObjectDetectionOutputElement`] items containing the bounding boxes and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If the request output is not a List.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.object_detection("people.jpg")
+ [ObjectDetectionOutputElement(score=0.9486683011054993, label='person', box=ObjectDetectionBoundingBox(xmin=59, ymin=39, xmax=420, ymax=510)), ...]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="object-detection")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={"threshold": threshold},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return ObjectDetectionOutputElement.parse_obj_as_list(response)
+
+ def question_answering(
+ self,
+ question: str,
+ context: str,
+ *,
+ model: Optional[str] = None,
+ align_to_words: Optional[bool] = None,
+ doc_stride: Optional[int] = None,
+ handle_impossible_answer: Optional[bool] = None,
+ max_answer_len: Optional[int] = None,
+ max_question_len: Optional[int] = None,
+ max_seq_len: Optional[int] = None,
+ top_k: Optional[int] = None,
+ ) -> Union[QuestionAnsweringOutputElement, List[QuestionAnsweringOutputElement]]:
+ """
+ Retrieve the answer to a question from a given text.
+
+ Args:
+ question (`str`):
+ Question to be answered.
+ context (`str`):
+ The context of the question.
+ model (`str`):
+ The model to use for the question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint.
+ align_to_words (`bool`, *optional*):
+ Attempts to align the answer to real words. Improves quality on space separated languages. Might hurt
+ on non-space-separated languages (like Japanese or Chinese)
+ doc_stride (`int`, *optional*):
+ If the context is too long to fit with the question for the model, it will be split in several chunks
+ with some overlap. This argument controls the size of that overlap.
+ handle_impossible_answer (`bool`, *optional*):
+ Whether to accept impossible as an answer.
+ max_answer_len (`int`, *optional*):
+ The maximum length of predicted answers (e.g., only answers with a shorter length are considered).
+ max_question_len (`int`, *optional*):
+ The maximum length of the question after tokenization. It will be truncated if needed.
+ max_seq_len (`int`, *optional*):
+ The maximum length of the total sentence (context + question) in tokens of each chunk passed to the
+ model. The context will be split in several chunks (using docStride as overlap) if needed.
+ top_k (`int`, *optional*):
+ The number of answers to return (will be chosen by order of likelihood). Note that we return less than
+ topk answers if there are not enough options available within the context.
+
+ Returns:
+ Union[`QuestionAnsweringOutputElement`, List[`QuestionAnsweringOutputElement`]]:
+ When top_k is 1 or not provided, it returns a single `QuestionAnsweringOutputElement`.
+ When top_k is greater than 1, it returns a list of `QuestionAnsweringOutputElement`.
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.question_answering(question="What's my name?", context="My name is Clara and I live in Berkeley.")
+ QuestionAnsweringOutputElement(answer='Clara', end=16, score=0.9326565265655518, start=11)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={
+ "align_to_words": align_to_words,
+ "doc_stride": doc_stride,
+ "handle_impossible_answer": handle_impossible_answer,
+ "max_answer_len": max_answer_len,
+ "max_question_len": max_question_len,
+ "max_seq_len": max_seq_len,
+ "top_k": top_k,
+ },
+ extra_payload={"question": question, "context": context},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ # Parse the response as a single `QuestionAnsweringOutputElement` when top_k is 1 or not provided, or a list of `QuestionAnsweringOutputElement` to ensure backward compatibility.
+ output = QuestionAnsweringOutputElement.parse_obj(response)
+ return output
+
+ def sentence_similarity(
+ self, sentence: str, other_sentences: List[str], *, model: Optional[str] = None
+ ) -> List[float]:
+ """
+ Compute the semantic similarity between a sentence and a list of other sentences by comparing their embeddings.
+
+ Args:
+ sentence (`str`):
+ The main sentence to compare to others.
+ other_sentences (`List[str]`):
+ The list of sentences to compare to.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[float]`: The embedding representing the input text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.sentence_similarity(
+ ... "Machine learning is so easy.",
+ ... other_sentences=[
+ ... "Deep learning is so straightforward.",
+ ... "This is so difficult, like rocket science.",
+ ... "I can't believe how much I struggled with this.",
+ ... ],
+ ... )
+ [0.7785726189613342, 0.45876261591911316, 0.2906220555305481]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="sentence-similarity")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={},
+ extra_payload={"source_sentence": sentence, "sentences": other_sentences},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return _bytes_to_list(response)
+
+ def summarization(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ clean_up_tokenization_spaces: Optional[bool] = None,
+ generate_parameters: Optional[Dict[str, Any]] = None,
+ truncation: Optional["SummarizationTruncationStrategy"] = None,
+ ) -> SummarizationOutput:
+ """
+ Generate a summary of a given text using a specified model.
+
+ Args:
+ text (`str`):
+ The input text to summarize.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for summarization will be used.
+ clean_up_tokenization_spaces (`bool`, *optional*):
+ Whether to clean up the potential extra spaces in the text output.
+ generate_parameters (`Dict[str, Any]`, *optional*):
+ Additional parametrization of the text generation algorithm.
+ truncation (`"SummarizationTruncationStrategy"`, *optional*):
+ The truncation strategy to use.
+ Returns:
+ [`SummarizationOutput`]: The generated summary text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.summarization("The Eiffel tower...")
+ SummarizationOutput(generated_text="The Eiffel tower is one of the most famous landmarks in the world....")
+ ```
+ """
+ parameters = {
+ "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
+ "generate_parameters": generate_parameters,
+ "truncation": truncation,
+ }
+ provider_helper = get_provider_helper(self.provider, task="summarization")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters=parameters,
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return SummarizationOutput.parse_obj_as_list(response)[0]
+
+ def table_question_answering(
+ self,
+ table: Dict[str, Any],
+ query: str,
+ *,
+ model: Optional[str] = None,
+ padding: Optional["Padding"] = None,
+ sequential: Optional[bool] = None,
+ truncation: Optional[bool] = None,
+ ) -> TableQuestionAnsweringOutputElement:
+ """
+ Retrieve the answer to a question from information given in a table.
+
+ Args:
+ table (`str`):
+ A table of data represented as a dict of lists where entries are headers and the lists are all the
+ values, all lists must have the same size.
+ query (`str`):
+ The query in plain text that you want to ask the table.
+ model (`str`):
+ The model to use for the table-question-answering task. Can be a model ID hosted on the Hugging Face
+ Hub or a URL to a deployed Inference Endpoint.
+ padding (`"Padding"`, *optional*):
+ Activates and controls padding.
+ sequential (`bool`, *optional*):
+ Whether to do inference sequentially or as a batch. Batching is faster, but models like SQA require the
+ inference to be done sequentially to extract relations within sequences, given their conversational
+ nature.
+ truncation (`bool`, *optional*):
+ Activates and controls truncation.
+
+ Returns:
+ [`TableQuestionAnsweringOutputElement`]: a table question answering output containing the answer, coordinates, cells and the aggregator used.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> query = "How many stars does the transformers repository have?"
+ >>> table = {"Repository": ["Transformers", "Datasets", "Tokenizers"], "Stars": ["36542", "4512", "3934"]}
+ >>> client.table_question_answering(table, query, model="google/tapas-base-finetuned-wtq")
+ TableQuestionAnsweringOutputElement(answer='36542', coordinates=[[0, 1]], cells=['36542'], aggregator='AVERAGE')
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="table-question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={"model": model, "padding": padding, "sequential": sequential, "truncation": truncation},
+ extra_payload={"query": query, "table": table},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return TableQuestionAnsweringOutputElement.parse_obj_as_instance(response)
+
+ def tabular_classification(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[str]:
+ """
+ Classifying a target category (a group) based on a set of attributes.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes to classify.
+ model (`str`, *optional*):
+ The model to use for the tabular classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of labels, one per row in the initial table.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> table = {
+ ... "fixed_acidity": ["7.4", "7.8", "10.3"],
+ ... "volatile_acidity": ["0.7", "0.88", "0.32"],
+ ... "citric_acid": ["0", "0", "0.45"],
+ ... "residual_sugar": ["1.9", "2.6", "6.4"],
+ ... "chlorides": ["0.076", "0.098", "0.073"],
+ ... "free_sulfur_dioxide": ["11", "25", "5"],
+ ... "total_sulfur_dioxide": ["34", "67", "13"],
+ ... "density": ["0.9978", "0.9968", "0.9976"],
+ ... "pH": ["3.51", "3.2", "3.23"],
+ ... "sulphates": ["0.56", "0.68", "0.82"],
+ ... "alcohol": ["9.4", "9.8", "12.6"],
+ ... }
+ >>> client.tabular_classification(table=table, model="julien-c/wine-quality")
+ ["5", "5", "5"]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="tabular-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ extra_payload={"table": table},
+ parameters={},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return _bytes_to_list(response)
+
+ def tabular_regression(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[float]:
+ """
+ Predicting a numerical target value given a set of attributes/features in a table.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes stored in a table. The attributes used to predict the target can be both numerical and categorical.
+ model (`str`, *optional*):
+ The model to use for the tabular regression task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular regression model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of predicted numerical target values.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> table = {
+ ... "Height": ["11.52", "12.48", "12.3778"],
+ ... "Length1": ["23.2", "24", "23.9"],
+ ... "Length2": ["25.4", "26.3", "26.5"],
+ ... "Length3": ["30", "31.2", "31.1"],
+ ... "Species": ["Bream", "Bream", "Bream"],
+ ... "Width": ["4.02", "4.3056", "4.6961"],
+ ... }
+ >>> client.tabular_regression(table, model="scikit-learn/Fish-Weight")
+ [110, 120, 130]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="tabular-regression")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={},
+ extra_payload={"table": table},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return _bytes_to_list(response)
+
+ def text_classification(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ top_k: Optional[int] = None,
+ function_to_apply: Optional["TextClassificationOutputTransform"] = None,
+ ) -> List[TextClassificationOutputElement]:
+ """
+ Perform text classification (e.g. sentiment-analysis) on the given text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the text classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended text classification model will be used.
+ Defaults to None.
+ top_k (`int`, *optional*):
+ When specified, limits the output to the top K most probable classes.
+ function_to_apply (`"TextClassificationOutputTransform"`, *optional*):
+ The function to apply to the model outputs in order to retrieve the scores.
+
+ Returns:
+ `List[TextClassificationOutputElement]`: a list of [`TextClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.text_classification("I like you")
+ [
+ TextClassificationOutputElement(label='POSITIVE', score=0.9998695850372314),
+ TextClassificationOutputElement(label='NEGATIVE', score=0.0001304351753788069),
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "function_to_apply": function_to_apply,
+ "top_k": top_k,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return TextClassificationOutputElement.parse_obj_as_list(response)[0] # type: ignore [return-value]
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> str: ...
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> TextGenerationOutput: ...
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> Iterable[str]: ...
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> Iterable[TextGenerationStreamOutput]: ...
+
+ @overload
+ def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: bool = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> Union[TextGenerationOutput, Iterable[TextGenerationStreamOutput]]: ...
+
+ def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: bool = False,
+ stream: bool = False,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> Union[str, TextGenerationOutput, Iterable[str], Iterable[TextGenerationStreamOutput]]:
+ """
+ Given a prompt, generate the following text.
+
+
+
+ If you want to generate a response from chat messages, you should use the [`InferenceClient.chat_completion`] method.
+ It accepts a list of messages instead of a single text prompt and handles the chat templating for you.
+
+
+
+ Args:
+ prompt (`str`):
+ Input text.
+ details (`bool`, *optional*):
+ By default, text_generation returns a string. Pass `details=True` if you want a detailed output (tokens,
+ probabilities, seed, finish reason, etc.). Only available for models running on with the
+ `text-generation-inference` backend.
+ stream (`bool`, *optional*):
+ By default, text_generation returns the full generated text. Pass `stream=True` if you want a stream of
+ tokens to be returned. Only available for models running on with the `text-generation-inference`
+ backend.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+ adapter_id (`str`, *optional*):
+ Lora adapter id.
+ best_of (`int`, *optional*):
+ Generate best_of sequences and return the one if the highest token logprobs.
+ decoder_input_details (`bool`, *optional*):
+ Return the decoder input token logprobs and ids. You must set `details=True` as well for it to be taken
+ into account. Defaults to `False`.
+ do_sample (`bool`, *optional*):
+ Activate logits sampling
+ frequency_penalty (`float`, *optional*):
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in
+ the text so far, decreasing the model's likelihood to repeat the same line verbatim.
+ grammar ([`TextGenerationInputGrammarType`], *optional*):
+ Grammar constraints. Can be either a JSONSchema or a regex.
+ max_new_tokens (`int`, *optional*):
+ Maximum number of generated tokens. Defaults to 100.
+ repetition_penalty (`float`, *optional*):
+ The parameter for repetition penalty. 1.0 means no penalty. See [this
+ paper](https://arxiv.org/pdf/1909.05858.pdf) for more details.
+ return_full_text (`bool`, *optional*):
+ Whether to prepend the prompt to the generated text
+ seed (`int`, *optional*):
+ Random sampling seed
+ stop (`List[str]`, *optional*):
+ Stop generating tokens if a member of `stop` is generated.
+ stop_sequences (`List[str]`, *optional*):
+ Deprecated argument. Use `stop` instead.
+ temperature (`float`, *optional*):
+ The value used to module the logits distribution.
+ top_n_tokens (`int`, *optional*):
+ Return information about the `top_n_tokens` most likely tokens at each generation step, instead of
+ just the sampled token.
+ top_k (`int`, *optional`):
+ The number of highest probability vocabulary tokens to keep for top-k-filtering.
+ top_p (`float`, *optional`):
+ If set to < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or
+ higher are kept for generation.
+ truncate (`int`, *optional`):
+ Truncate inputs tokens to the given size.
+ typical_p (`float`, *optional`):
+ Typical Decoding mass
+ See [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666) for more information
+ watermark (`bool`, *optional`):
+ Watermarking with [A Watermark for Large Language Models](https://arxiv.org/abs/2301.10226)
+
+ Returns:
+ `Union[str, TextGenerationOutput, Iterable[str], Iterable[TextGenerationStreamOutput]]`:
+ Generated text returned from the server:
+ - if `stream=False` and `details=False`, the generated text is returned as a `str` (default)
+ - if `stream=True` and `details=False`, the generated text is returned token by token as a `Iterable[str]`
+ - if `stream=False` and `details=True`, the generated text is returned with more details as a [`~huggingface_hub.TextGenerationOutput`]
+ - if `details=True` and `stream=True`, the generated text is returned token by token as a iterable of [`~huggingface_hub.TextGenerationStreamOutput`]
+
+ Raises:
+ `ValidationError`:
+ If input values are not valid. No HTTP call is made to the server.
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ # Case 1: generate text
+ >>> client.text_generation("The huggingface_hub library is ", max_new_tokens=12)
+ '100% open source and built to be easy to use.'
+
+ # Case 2: iterate over the generated tokens. Useful for large generation.
+ >>> for token in client.text_generation("The huggingface_hub library is ", max_new_tokens=12, stream=True):
+ ... print(token)
+ 100
+ %
+ open
+ source
+ and
+ built
+ to
+ be
+ easy
+ to
+ use
+ .
+
+ # Case 3: get more details about the generation process.
+ >>> client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True)
+ TextGenerationOutput(
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationDetails(
+ finish_reason='length',
+ generated_tokens=12,
+ seed=None,
+ prefill=[
+ TextGenerationPrefillOutputToken(id=487, text='The', logprob=None),
+ TextGenerationPrefillOutputToken(id=53789, text=' hugging', logprob=-13.171875),
+ (...)
+ TextGenerationPrefillOutputToken(id=204, text=' ', logprob=-7.0390625)
+ ],
+ tokens=[
+ TokenElement(id=1425, text='100', logprob=-1.0175781, special=False),
+ TokenElement(id=16, text='%', logprob=-0.0463562, special=False),
+ (...)
+ TokenElement(id=25, text='.', logprob=-0.5703125, special=False)
+ ],
+ best_of_sequences=None
+ )
+ )
+
+ # Case 4: iterate over the generated tokens with more details.
+ # Last object is more complete, containing the full generated text and the finish reason.
+ >>> for details in client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True, stream=True):
+ ... print(details)
+ ...
+ TextGenerationStreamOutput(token=TokenElement(id=1425, text='100', logprob=-1.0175781, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=16, text='%', logprob=-0.0463562, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1314, text=' open', logprob=-1.3359375, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3178, text=' source', logprob=-0.28100586, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=273, text=' and', logprob=-0.5961914, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3426, text=' built', logprob=-1.9423828, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-1.4121094, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=314, text=' be', logprob=-1.5224609, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1833, text=' easy', logprob=-2.1132812, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-0.08520508, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=745, text=' use', logprob=-0.39453125, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(
+ id=25,
+ text='.',
+ logprob=-0.5703125,
+ special=False),
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationStreamOutputStreamDetails(finish_reason='length', generated_tokens=12, seed=None)
+ )
+
+ # Case 5: generate constrained output using grammar
+ >>> response = client.text_generation(
+ ... prompt="I saw a puppy a cat and a raccoon during my bike ride in the park",
+ ... model="HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1",
+ ... max_new_tokens=100,
+ ... repetition_penalty=1.3,
+ ... grammar={
+ ... "type": "json",
+ ... "value": {
+ ... "properties": {
+ ... "location": {"type": "string"},
+ ... "activity": {"type": "string"},
+ ... "animals_seen": {"type": "integer", "minimum": 1, "maximum": 5},
+ ... "animals": {"type": "array", "items": {"type": "string"}},
+ ... },
+ ... "required": ["location", "activity", "animals_seen", "animals"],
+ ... },
+ ... },
+ ... )
+ >>> json.loads(response)
+ {
+ "activity": "bike riding",
+ "animals": ["puppy", "cat", "raccoon"],
+ "animals_seen": 3,
+ "location": "park"
+ }
+ ```
+ """
+ if decoder_input_details and not details:
+ warnings.warn(
+ "`decoder_input_details=True` has been passed to the server but `details=False` is set meaning that"
+ " the output from the server will be truncated."
+ )
+ decoder_input_details = False
+
+ if stop_sequences is not None:
+ warnings.warn(
+ "`stop_sequences` is a deprecated argument for `text_generation` task"
+ " and will be removed in version '0.28.0'. Use `stop` instead.",
+ FutureWarning,
+ )
+ if stop is None:
+ stop = stop_sequences # use deprecated arg if provided
+
+ # Build payload
+ parameters = {
+ "adapter_id": adapter_id,
+ "best_of": best_of,
+ "decoder_input_details": decoder_input_details,
+ "details": details,
+ "do_sample": do_sample,
+ "frequency_penalty": frequency_penalty,
+ "grammar": grammar,
+ "max_new_tokens": max_new_tokens,
+ "repetition_penalty": repetition_penalty,
+ "return_full_text": return_full_text,
+ "seed": seed,
+ "stop": stop if stop is not None else [],
+ "temperature": temperature,
+ "top_k": top_k,
+ "top_n_tokens": top_n_tokens,
+ "top_p": top_p,
+ "truncate": truncate,
+ "typical_p": typical_p,
+ "watermark": watermark,
+ }
+
+ # Remove some parameters if not a TGI server
+ unsupported_kwargs = _get_unsupported_text_generation_kwargs(model)
+ if len(unsupported_kwargs) > 0:
+ # The server does not support some parameters
+ # => means it is not a TGI server
+ # => remove unsupported parameters and warn the user
+
+ ignored_parameters = []
+ for key in unsupported_kwargs:
+ if parameters.get(key):
+ ignored_parameters.append(key)
+ parameters.pop(key, None)
+ if len(ignored_parameters) > 0:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Ignoring following parameters:"
+ f" {', '.join(ignored_parameters)}.",
+ UserWarning,
+ )
+ if details:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Parameter `details=True` will"
+ " be ignored meaning only the generated text will be returned.",
+ UserWarning,
+ )
+ details = False
+ if stream:
+ raise ValueError(
+ "API endpoint/model for text-generation is not served via TGI. Cannot return output as a stream."
+ " Please pass `stream=False` as input."
+ )
+
+ provider_helper = get_provider_helper(self.provider, task="text-generation")
+ request_parameters = provider_helper.prepare_request(
+ inputs=prompt,
+ parameters=parameters,
+ extra_payload={"stream": stream},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+
+ # Handle errors separately for more precise error messages
+ try:
+ bytes_output = self._inner_post(request_parameters, stream=stream)
+ except HTTPError as e:
+ match = MODEL_KWARGS_NOT_USED_REGEX.search(str(e))
+ if isinstance(e, BadRequestError) and match:
+ unused_params = [kwarg.strip("' ") for kwarg in match.group(1).split(",")]
+ _set_unsupported_text_generation_kwargs(model, unused_params)
+ return self.text_generation( # type: ignore
+ prompt=prompt,
+ details=details,
+ stream=stream,
+ model=model or self.model,
+ adapter_id=adapter_id,
+ best_of=best_of,
+ decoder_input_details=decoder_input_details,
+ do_sample=do_sample,
+ frequency_penalty=frequency_penalty,
+ grammar=grammar,
+ max_new_tokens=max_new_tokens,
+ repetition_penalty=repetition_penalty,
+ return_full_text=return_full_text,
+ seed=seed,
+ stop=stop,
+ temperature=temperature,
+ top_k=top_k,
+ top_n_tokens=top_n_tokens,
+ top_p=top_p,
+ truncate=truncate,
+ typical_p=typical_p,
+ watermark=watermark,
+ )
+ raise_text_generation_error(e)
+
+ # Parse output
+ if stream:
+ return _stream_text_generation_response(bytes_output, details) # type: ignore
+
+ data = _bytes_to_dict(bytes_output) # type: ignore[arg-type]
+
+ # Data can be a single element (dict) or an iterable of dicts where we select the first element of.
+ if isinstance(data, list):
+ data = data[0]
+
+ return TextGenerationOutput.parse_obj_as_instance(data) if details else data["generated_text"]
+
+ def text_to_image(
+ self,
+ prompt: str,
+ *,
+ negative_prompt: Optional[str] = None,
+ height: Optional[int] = None,
+ width: Optional[int] = None,
+ num_inference_steps: Optional[int] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ scheduler: Optional[str] = None,
+ seed: Optional[int] = None,
+ extra_body: Optional[Dict[str, Any]] = None,
+ ) -> "Image":
+ """
+ Generate an image based on a given text using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ prompt (`str`):
+ The prompt to generate an image from.
+ negative_prompt (`str`, *optional*):
+ One prompt to guide what NOT to include in image generation.
+ height (`int`, *optional*):
+ The height in pixels of the output image
+ width (`int`, *optional*):
+ The width in pixels of the output image
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
+ expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ A higher guidance scale value encourages the model to generate images closely linked to the text
+ prompt, but values too high may cause saturation and other artifacts.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended text-to-image model will be used.
+ Defaults to None.
+ scheduler (`str`, *optional*):
+ Override the scheduler with a compatible one.
+ seed (`int`, *optional*):
+ Seed for the random number generator.
+ extra_body (`Dict[str, Any]`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+
+ Returns:
+ `Image`: The generated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ >>> image = client.text_to_image("An astronaut riding a horse on the moon.")
+ >>> image.save("astronaut.png")
+
+ >>> image = client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... negative_prompt="low resolution, blurry",
+ ... model="stabilityai/stable-diffusion-2-1",
+ ... )
+ >>> image.save("better_astronaut.png")
+ ```
+ Example using a third-party provider directly. Usage will be billed on your fal.ai account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="fal-ai", # Use fal.ai provider
+ ... api_key="fal-ai-api-key", # Pass your fal.ai API key
+ ... )
+ >>> image = client.text_to_image(
+ ... "A majestic lion in a fantasy forest",
+ ... model="black-forest-labs/FLUX.1-schnell",
+ ... )
+ >>> image.save("lion.png")
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Use replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> image = client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... model="black-forest-labs/FLUX.1-dev",
+ ... )
+ >>> image.save("astronaut.png")
+ ```
+
+ Example using Replicate provider with extra parameters
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Use replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> image = client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... model="black-forest-labs/FLUX.1-schnell",
+ ... extra_body={"output_quality": 100},
+ ... )
+ >>> image.save("astronaut.png")
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-to-image")
+ request_parameters = provider_helper.prepare_request(
+ inputs=prompt,
+ parameters={
+ "negative_prompt": negative_prompt,
+ "height": height,
+ "width": width,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ "scheduler": scheduler,
+ "seed": seed,
+ **(extra_body or {}),
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ response = provider_helper.get_response(response)
+ return _bytes_to_image(response)
+
+ def text_to_video(
+ self,
+ prompt: str,
+ *,
+ model: Optional[str] = None,
+ guidance_scale: Optional[float] = None,
+ negative_prompt: Optional[List[str]] = None,
+ num_frames: Optional[float] = None,
+ num_inference_steps: Optional[int] = None,
+ seed: Optional[int] = None,
+ extra_body: Optional[Dict[str, Any]] = None,
+ ) -> bytes:
+ """
+ Generate a video based on a given text.
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ prompt (`str`):
+ The prompt to generate a video from.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended text-to-video model will be used.
+ Defaults to None.
+ guidance_scale (`float`, *optional*):
+ A higher guidance scale value encourages the model to generate videos closely linked to the text
+ prompt, but values too high may cause saturation and other artifacts.
+ negative_prompt (`List[str]`, *optional*):
+ One or several prompt to guide what NOT to include in video generation.
+ num_frames (`float`, *optional*):
+ The num_frames parameter determines how many video frames are generated.
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality video at the
+ expense of slower inference.
+ seed (`int`, *optional*):
+ Seed for the random number generator.
+ extra_body (`Dict[str, Any]`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+
+ Returns:
+ `bytes`: The generated video.
+
+ Example:
+
+ Example using a third-party provider directly. Usage will be billed on your fal.ai account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="fal-ai", # Using fal.ai provider
+ ... api_key="fal-ai-api-key", # Pass your fal.ai API key
+ ... )
+ >>> video = client.text_to_video(
+ ... "A majestic lion running in a fantasy forest",
+ ... model="tencent/HunyuanVideo",
+ ... )
+ >>> with open("lion.mp4", "wb") as file:
+ ... file.write(video)
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Using replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> video = client.text_to_video(
+ ... "A cat running in a park",
+ ... model="genmo/mochi-1-preview",
+ ... )
+ >>> with open("cat.mp4", "wb") as file:
+ ... file.write(video)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-to-video")
+ request_parameters = provider_helper.prepare_request(
+ inputs=prompt,
+ parameters={
+ "guidance_scale": guidance_scale,
+ "negative_prompt": negative_prompt,
+ "num_frames": num_frames,
+ "num_inference_steps": num_inference_steps,
+ "seed": seed,
+ **(extra_body or {}),
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ response = provider_helper.get_response(response)
+ return response
+
+ def text_to_speech(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ do_sample: Optional[bool] = None,
+ early_stopping: Optional[Union[bool, "TextToSpeechEarlyStoppingEnum"]] = None,
+ epsilon_cutoff: Optional[float] = None,
+ eta_cutoff: Optional[float] = None,
+ max_length: Optional[int] = None,
+ max_new_tokens: Optional[int] = None,
+ min_length: Optional[int] = None,
+ min_new_tokens: Optional[int] = None,
+ num_beam_groups: Optional[int] = None,
+ num_beams: Optional[int] = None,
+ penalty_alpha: Optional[float] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ typical_p: Optional[float] = None,
+ use_cache: Optional[bool] = None,
+ extra_body: Optional[Dict[str, Any]] = None,
+ ) -> bytes:
+ """
+ Synthesize an audio of a voice pronouncing a given text.
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ text (`str`):
+ The text to synthesize.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended text-to-speech model will be used.
+ Defaults to None.
+ do_sample (`bool`, *optional*):
+ Whether to use sampling instead of greedy decoding when generating new tokens.
+ early_stopping (`Union[bool, "TextToSpeechEarlyStoppingEnum"]`, *optional*):
+ Controls the stopping condition for beam-based methods.
+ epsilon_cutoff (`float`, *optional*):
+ If set to float strictly between 0 and 1, only tokens with a conditional probability greater than
+ epsilon_cutoff will be sampled. In the paper, suggested values range from 3e-4 to 9e-4, depending on
+ the size of the model. See [Truncation Sampling as Language Model
+ Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ eta_cutoff (`float`, *optional*):
+ Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to float strictly
+ between 0 and 1, a token is only considered if it is greater than either eta_cutoff or sqrt(eta_cutoff)
+ * exp(-entropy(softmax(next_token_logits))). The latter term is intuitively the expected next token
+ probability, scaled by sqrt(eta_cutoff). In the paper, suggested values range from 3e-4 to 2e-3,
+ depending on the size of the model. See [Truncation Sampling as Language Model
+ Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ max_length (`int`, *optional*):
+ The maximum length (in tokens) of the generated text, including the input.
+ max_new_tokens (`int`, *optional*):
+ The maximum number of tokens to generate. Takes precedence over max_length.
+ min_length (`int`, *optional*):
+ The minimum length (in tokens) of the generated text, including the input.
+ min_new_tokens (`int`, *optional*):
+ The minimum number of tokens to generate. Takes precedence over min_length.
+ num_beam_groups (`int`, *optional*):
+ Number of groups to divide num_beams into in order to ensure diversity among different groups of beams.
+ See [this paper](https://hf.co/papers/1610.02424) for more details.
+ num_beams (`int`, *optional*):
+ Number of beams to use for beam search.
+ penalty_alpha (`float`, *optional*):
+ The value balances the model confidence and the degeneration penalty in contrastive search decoding.
+ temperature (`float`, *optional*):
+ The value used to modulate the next token probabilities.
+ top_k (`int`, *optional*):
+ The number of highest probability vocabulary tokens to keep for top-k-filtering.
+ top_p (`float`, *optional*):
+ If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to
+ top_p or higher are kept for generation.
+ typical_p (`float`, *optional*):
+ Local typicality measures how similar the conditional probability of predicting a target token next is
+ to the expected conditional probability of predicting a random token next, given the partial text
+ already generated. If set to float < 1, the smallest set of the most locally typical tokens with
+ probabilities that add up to typical_p or higher are kept for generation. See [this
+ paper](https://hf.co/papers/2202.00666) for more details.
+ use_cache (`bool`, *optional*):
+ Whether the model should use the past last key/values attentions to speed up decoding
+ extra_body (`Dict[str, Any]`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+ Returns:
+ `bytes`: The generated audio.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from pathlib import Path
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ >>> audio = client.text_to_speech("Hello world")
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+
+ Example using a third-party provider directly. Usage will be billed on your Replicate account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate",
+ ... api_key="your-replicate-api-key", # Pass your Replicate API key directly
+ ... )
+ >>> audio = client.text_to_speech(
+ ... text="Hello world",
+ ... model="OuteAI/OuteTTS-0.3-500M",
+ ... )
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate",
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> audio =client.text_to_speech(
+ ... text="Hello world",
+ ... model="OuteAI/OuteTTS-0.3-500M",
+ ... )
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+ Example using Replicate provider with extra parameters
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Use replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> audio = client.text_to_speech(
+ ... "Hello, my name is Kororo, an awesome text-to-speech model.",
+ ... model="hexgrad/Kokoro-82M",
+ ... extra_body={"voice": "af_nicole"},
+ ... )
+ >>> Path("hello.flac").write_bytes(audio)
+ ```
+
+ Example music-gen using "YuE-s1-7B-anneal-en-cot" on fal.ai
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> lyrics = '''
+ ... [verse]
+ ... In the town where I was born
+ ... Lived a man who sailed to sea
+ ... And he told us of his life
+ ... In the land of submarines
+ ... So we sailed on to the sun
+ ... 'Til we found a sea of green
+ ... And we lived beneath the waves
+ ... In our yellow submarine
+
+ ... [chorus]
+ ... We all live in a yellow submarine
+ ... Yellow submarine, yellow submarine
+ ... We all live in a yellow submarine
+ ... Yellow submarine, yellow submarine
+ ... '''
+ >>> genres = "pavarotti-style tenor voice"
+ >>> client = InferenceClient(
+ ... provider="fal-ai",
+ ... model="m-a-p/YuE-s1-7B-anneal-en-cot",
+ ... api_key=...,
+ ... )
+ >>> audio = client.text_to_speech(lyrics, extra_body={"genres": genres})
+ >>> with open("output.mp3", "wb") as f:
+ ... f.write(audio)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-to-speech")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "do_sample": do_sample,
+ "early_stopping": early_stopping,
+ "epsilon_cutoff": epsilon_cutoff,
+ "eta_cutoff": eta_cutoff,
+ "max_length": max_length,
+ "max_new_tokens": max_new_tokens,
+ "min_length": min_length,
+ "min_new_tokens": min_new_tokens,
+ "num_beam_groups": num_beam_groups,
+ "num_beams": num_beams,
+ "penalty_alpha": penalty_alpha,
+ "temperature": temperature,
+ "top_k": top_k,
+ "top_p": top_p,
+ "typical_p": typical_p,
+ "use_cache": use_cache,
+ **(extra_body or {}),
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ response = provider_helper.get_response(response)
+ return response
+
+ def token_classification(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ aggregation_strategy: Optional["TokenClassificationAggregationStrategy"] = None,
+ ignore_labels: Optional[List[str]] = None,
+ stride: Optional[int] = None,
+ ) -> List[TokenClassificationOutputElement]:
+ """
+ Perform token classification on the given text.
+ Usually used for sentence parsing, either grammatical, or Named Entity Recognition (NER) to understand keywords contained within text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the token classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended token classification model will be used.
+ Defaults to None.
+ aggregation_strategy (`"TokenClassificationAggregationStrategy"`, *optional*):
+ The strategy used to fuse tokens based on model predictions
+ ignore_labels (`List[str`, *optional*):
+ A list of labels to ignore
+ stride (`int`, *optional*):
+ The number of overlapping tokens between chunks when splitting the input text.
+
+ Returns:
+ `List[TokenClassificationOutputElement]`: List of [`TokenClassificationOutputElement`] items containing the entity group, confidence score, word, start and end index.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.token_classification("My name is Sarah Jessica Parker but you can call me Jessica")
+ [
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9971321225166321,
+ word='Sarah Jessica Parker',
+ start=11,
+ end=31,
+ ),
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9773476123809814,
+ word='Jessica',
+ start=52,
+ end=59,
+ )
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="token-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "aggregation_strategy": aggregation_strategy,
+ "ignore_labels": ignore_labels,
+ "stride": stride,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return TokenClassificationOutputElement.parse_obj_as_list(response)
+
+ def translation(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ src_lang: Optional[str] = None,
+ tgt_lang: Optional[str] = None,
+ clean_up_tokenization_spaces: Optional[bool] = None,
+ truncation: Optional["TranslationTruncationStrategy"] = None,
+ generate_parameters: Optional[Dict[str, Any]] = None,
+ ) -> TranslationOutput:
+ """
+ Convert text from one language to another.
+
+ Check out https://huggingface.co/tasks/translation for more information on how to choose the best model for
+ your specific use case. Source and target languages usually depend on the model.
+ However, it is possible to specify source and target languages for certain models. If you are working with one of these models,
+ you can use `src_lang` and `tgt_lang` arguments to pass the relevant information.
+
+ Args:
+ text (`str`):
+ A string to be translated.
+ model (`str`, *optional*):
+ The model to use for the translation task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended translation model will be used.
+ Defaults to None.
+ src_lang (`str`, *optional*):
+ The source language of the text. Required for models that can translate from multiple languages.
+ tgt_lang (`str`, *optional*):
+ Target language to translate to. Required for models that can translate to multiple languages.
+ clean_up_tokenization_spaces (`bool`, *optional*):
+ Whether to clean up the potential extra spaces in the text output.
+ truncation (`"TranslationTruncationStrategy"`, *optional*):
+ The truncation strategy to use.
+ generate_parameters (`Dict[str, Any]`, *optional*):
+ Additional parametrization of the text generation algorithm.
+
+ Returns:
+ [`TranslationOutput`]: The generated translated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If only one of the `src_lang` and `tgt_lang` arguments are provided.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.translation("My name is Wolfgang and I live in Berlin")
+ 'Mein Name ist Wolfgang und ich lebe in Berlin.'
+ >>> client.translation("My name is Wolfgang and I live in Berlin", model="Helsinki-NLP/opus-mt-en-fr")
+ TranslationOutput(translation_text='Je m'appelle Wolfgang et je vis à Berlin.')
+ ```
+
+ Specifying languages:
+ ```py
+ >>> client.translation("My name is Sarah Jessica Parker but you can call me Jessica", model="facebook/mbart-large-50-many-to-many-mmt", src_lang="en_XX", tgt_lang="fr_XX")
+ "Mon nom est Sarah Jessica Parker mais vous pouvez m'appeler Jessica"
+ ```
+ """
+ # Throw error if only one of `src_lang` and `tgt_lang` was given
+ if src_lang is not None and tgt_lang is None:
+ raise ValueError("You cannot specify `src_lang` without specifying `tgt_lang`.")
+
+ if src_lang is None and tgt_lang is not None:
+ raise ValueError("You cannot specify `tgt_lang` without specifying `src_lang`.")
+
+ provider_helper = get_provider_helper(self.provider, task="translation")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "src_lang": src_lang,
+ "tgt_lang": tgt_lang,
+ "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
+ "truncation": truncation,
+ "generate_parameters": generate_parameters,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return TranslationOutput.parse_obj_as_list(response)[0]
+
+ def visual_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ top_k: Optional[int] = None,
+ ) -> List[VisualQuestionAnsweringOutputElement]:
+ """
+ Answering open-ended questions based on an image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the visual question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended visual question answering model will be used.
+ Defaults to None.
+ top_k (`int`, *optional*):
+ The number of answers to return (will be chosen by order of likelihood). Note that we return less than
+ topk answers if there are not enough options available within the context.
+ Returns:
+ `List[VisualQuestionAnsweringOutputElement]`: a list of [`VisualQuestionAnsweringOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.visual_question_answering(
+ ... image="https://huggingface.co/datasets/mishig/sample_images/resolve/main/tiger.jpg",
+ ... question="What is the animal doing?"
+ ... )
+ [
+ VisualQuestionAnsweringOutputElement(score=0.778609573841095, answer='laying down'),
+ VisualQuestionAnsweringOutputElement(score=0.6957435607910156, answer='sitting'),
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="visual-question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={"top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ extra_payload={"question": question, "image": _b64_encode(image)},
+ )
+ response = self._inner_post(request_parameters)
+ return VisualQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ @_deprecate_arguments(
+ version="0.30.0",
+ deprecated_args=["labels"],
+ custom_message="`labels`has been renamed to `candidate_labels` and will be removed in huggingface_hub>=0.30.0.",
+ )
+ def zero_shot_classification(
+ self,
+ text: str,
+ # temporarily keeping it optional for backward compatibility.
+ candidate_labels: List[str] = None, # type: ignore
+ *,
+ multi_label: Optional[bool] = False,
+ hypothesis_template: Optional[str] = None,
+ model: Optional[str] = None,
+ # deprecated argument
+ labels: List[str] = None, # type: ignore
+ ) -> List[ZeroShotClassificationOutputElement]:
+ """
+ Provide as input a text and a set of candidate labels to classify the input text.
+
+ Args:
+ text (`str`):
+ The input text to classify.
+ candidate_labels (`List[str]`):
+ The set of possible class labels to classify the text into.
+ labels (`List[str]`, *optional*):
+ (deprecated) List of strings. Each string is the verbalization of a possible label for the input text.
+ multi_label (`bool`, *optional*):
+ Whether multiple candidate labels can be true. If false, the scores are normalized such that the sum of
+ the label likelihoods for each sequence is 1. If true, the labels are considered independent and
+ probabilities are normalized for each candidate.
+ hypothesis_template (`str`, *optional*):
+ The sentence used in conjunction with `candidate_labels` to attempt the text classification by
+ replacing the placeholder with the candidate labels.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. If not provided, the default recommended zero-shot classification model will be used.
+
+
+ Returns:
+ `List[ZeroShotClassificationOutputElement]`: List of [`ZeroShotClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example with `multi_label=False`:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> text = (
+ ... "A new model offers an explanation for how the Galilean satellites formed around the solar system's"
+ ... "largest world. Konstantin Batygin did not set out to solve one of the solar system's most puzzling"
+ ... " mysteries when he went for a run up a hill in Nice, France."
+ ... )
+ >>> labels = ["space & cosmos", "scientific discovery", "microbiology", "robots", "archeology"]
+ >>> client.zero_shot_classification(text, labels)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.7961668968200684),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.18570658564567566),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.00730885099619627),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.006258360575884581),
+ ZeroShotClassificationOutputElement(label='robots', score=0.004559356719255447),
+ ]
+ >>> client.zero_shot_classification(text, labels, multi_label=True)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.9829297661781311),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.755190908908844),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.0005462635890580714),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.00047131875180639327),
+ ZeroShotClassificationOutputElement(label='robots', score=0.00030448526376858354),
+ ]
+ ```
+
+ Example with `multi_label=True` and a custom `hypothesis_template`:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.zero_shot_classification(
+ ... text="I really like our dinner and I'm very happy. I don't like the weather though.",
+ ... labels=["positive", "negative", "pessimistic", "optimistic"],
+ ... multi_label=True,
+ ... hypothesis_template="This text is {} towards the weather"
+ ... )
+ [
+ ZeroShotClassificationOutputElement(label='negative', score=0.9231801629066467),
+ ZeroShotClassificationOutputElement(label='pessimistic', score=0.8760990500450134),
+ ZeroShotClassificationOutputElement(label='optimistic', score=0.0008674879791215062),
+ ZeroShotClassificationOutputElement(label='positive', score=0.0005250611575320363)
+ ]
+ ```
+ """
+ # handle deprecation
+ if labels is not None:
+ if candidate_labels is not None:
+ raise ValueError(
+ "Cannot specify both `labels` and `candidate_labels`. Use `candidate_labels` instead."
+ )
+ candidate_labels = labels
+ elif candidate_labels is None:
+ raise ValueError("Must specify `candidate_labels`")
+
+ provider_helper = get_provider_helper(self.provider, task="zero-shot-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "candidate_labels": candidate_labels,
+ "multi_label": multi_label,
+ "hypothesis_template": hypothesis_template,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ output = _bytes_to_dict(response)
+ return [
+ ZeroShotClassificationOutputElement.parse_obj_as_instance({"label": label, "score": score})
+ for label, score in zip(output["labels"], output["scores"])
+ ]
+
+ @_deprecate_arguments(
+ version="0.30.0",
+ deprecated_args=["labels"],
+ custom_message="`labels`has been renamed to `candidate_labels` and will be removed in huggingface_hub>=0.30.0.",
+ )
+ def zero_shot_image_classification(
+ self,
+ image: ContentT,
+ # temporarily keeping it optional for backward compatibility.
+ candidate_labels: List[str] = None, # type: ignore
+ *,
+ model: Optional[str] = None,
+ hypothesis_template: Optional[str] = None,
+ # deprecated argument
+ labels: List[str] = None, # type: ignore
+ ) -> List[ZeroShotImageClassificationOutputElement]:
+ """
+ Provide input image and text labels to predict text labels for the image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image.
+ candidate_labels (`List[str]`):
+ The candidate labels for this image
+ labels (`List[str]`, *optional*):
+ (deprecated) List of string possible labels. There must be at least 2 labels.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. If not provided, the default recommended zero-shot image classification model will be used.
+ hypothesis_template (`str`, *optional*):
+ The sentence used in conjunction with `candidate_labels` to attempt the image classification by
+ replacing the placeholder with the candidate labels.
+
+ Returns:
+ `List[ZeroShotImageClassificationOutputElement]`: List of [`ZeroShotImageClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ >>> client.zero_shot_image_classification(
+ ... "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg",
+ ... labels=["dog", "cat", "horse"],
+ ... )
+ [ZeroShotImageClassificationOutputElement(label='dog', score=0.956),...]
+ ```
+ """
+ # handle deprecation
+ if labels is not None:
+ if candidate_labels is not None:
+ raise ValueError(
+ "Cannot specify both `labels` and `candidate_labels`. Use `candidate_labels` instead."
+ )
+ candidate_labels = labels
+ elif candidate_labels is None:
+ raise ValueError("Must specify `candidate_labels`")
+ # Raise ValueError if input is less than 2 labels
+ if len(candidate_labels) < 2:
+ raise ValueError("You must specify at least 2 classes to compare.")
+
+ provider_helper = get_provider_helper(self.provider, task="zero-shot-image-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={
+ "candidate_labels": candidate_labels,
+ "hypothesis_template": hypothesis_template,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = self._inner_post(request_parameters)
+ return ZeroShotImageClassificationOutputElement.parse_obj_as_list(response)
+
+ @_deprecate_method(
+ version="0.33.0",
+ message=(
+ "HF Inference API is getting revamped and will only support warm models in the future (no cold start allowed)."
+ " Use `HfApi.list_models(..., inference_provider='...')` to list warm models per provider."
+ ),
+ )
+ def list_deployed_models(
+ self, frameworks: Union[None, str, Literal["all"], List[str]] = None
+ ) -> Dict[str, List[str]]:
+ """
+ List models deployed on the HF Serverless Inference API service.
+
+ This helper checks deployed models framework by framework. By default, it will check the 4 main frameworks that
+ are supported and account for 95% of the hosted models. However, if you want a complete list of models you can
+ specify `frameworks="all"` as input. Alternatively, if you know before-hand which framework you are interested
+ in, you can also restrict to search to this one (e.g. `frameworks="text-generation-inference"`). The more
+ frameworks are checked, the more time it will take.
+
+
+
+ This endpoint method does not return a live list of all models available for the HF Inference API service.
+ It searches over a cached list of models that were recently available and the list may not be up to date.
+ If you want to know the live status of a specific model, use [`~InferenceClient.get_model_status`].
+
+
+
+
+
+ This endpoint method is mostly useful for discoverability. If you already know which model you want to use and want to
+ check its availability, you can directly use [`~InferenceClient.get_model_status`].
+
+
+
+ Args:
+ frameworks (`Literal["all"]` or `List[str]` or `str`, *optional*):
+ The frameworks to filter on. By default only a subset of the available frameworks are tested. If set to
+ "all", all available frameworks will be tested. It is also possible to provide a single framework or a
+ custom set of frameworks to check.
+
+ Returns:
+ `Dict[str, List[str]]`: A dictionary mapping task names to a sorted list of model IDs.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ # Discover zero-shot-classification models currently deployed
+ >>> models = client.list_deployed_models()
+ >>> models["zero-shot-classification"]
+ ['Narsil/deberta-large-mnli-zero-cls', 'facebook/bart-large-mnli', ...]
+
+ # List from only 1 framework
+ >>> client.list_deployed_models("text-generation-inference")
+ {'text-generation': ['bigcode/starcoder', 'meta-llama/Llama-2-70b-chat-hf', ...], ...}
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Listing deployed models is not supported on '{self.provider}'.")
+
+ # Resolve which frameworks to check
+ if frameworks is None:
+ frameworks = constants.MAIN_INFERENCE_API_FRAMEWORKS
+ elif frameworks == "all":
+ frameworks = constants.ALL_INFERENCE_API_FRAMEWORKS
+ elif isinstance(frameworks, str):
+ frameworks = [frameworks]
+ frameworks = list(set(frameworks))
+
+ # Fetch them iteratively
+ models_by_task: Dict[str, List[str]] = {}
+
+ def _unpack_response(framework: str, items: List[Dict]) -> None:
+ for model in items:
+ if framework == "sentence-transformers":
+ # Model running with the `sentence-transformers` framework can work with both tasks even if not
+ # branded as such in the API response
+ models_by_task.setdefault("feature-extraction", []).append(model["model_id"])
+ models_by_task.setdefault("sentence-similarity", []).append(model["model_id"])
+ else:
+ models_by_task.setdefault(model["task"], []).append(model["model_id"])
+
+ for framework in frameworks:
+ response = get_session().get(
+ f"{constants.INFERENCE_ENDPOINT}/framework/{framework}", headers=build_hf_headers(token=self.token)
+ )
+ hf_raise_for_status(response)
+ _unpack_response(framework, response.json())
+
+ # Sort alphabetically for discoverability and return
+ for task, models in models_by_task.items():
+ models_by_task[task] = sorted(set(models), key=lambda x: x.lower())
+ return models_by_task
+
+ def get_endpoint_info(self, *, model: Optional[str] = None) -> Dict[str, Any]:
+ """
+ Get information about the deployed endpoint.
+
+ This endpoint is only available on endpoints powered by Text-Generation-Inference (TGI) or Text-Embedding-Inference (TEI).
+ Endpoints powered by `transformers` return an empty payload.
+
+ Args:
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `Dict[str, Any]`: Information about the endpoint.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient("meta-llama/Meta-Llama-3-70B-Instruct")
+ >>> client.get_endpoint_info()
+ {
+ 'model_id': 'meta-llama/Meta-Llama-3-70B-Instruct',
+ 'model_sha': None,
+ 'model_dtype': 'torch.float16',
+ 'model_device_type': 'cuda',
+ 'model_pipeline_tag': None,
+ 'max_concurrent_requests': 128,
+ 'max_best_of': 2,
+ 'max_stop_sequences': 4,
+ 'max_input_length': 8191,
+ 'max_total_tokens': 8192,
+ 'waiting_served_ratio': 0.3,
+ 'max_batch_total_tokens': 1259392,
+ 'max_waiting_tokens': 20,
+ 'max_batch_size': None,
+ 'validation_workers': 32,
+ 'max_client_batch_size': 4,
+ 'version': '2.0.2',
+ 'sha': 'dccab72549635c7eb5ddb17f43f0b7cdff07c214',
+ 'docker_label': 'sha-dccab72'
+ }
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Getting endpoint info is not supported on '{self.provider}'.")
+
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if model.startswith(("http://", "https://")):
+ url = model.rstrip("/") + "/info"
+ else:
+ url = f"{constants.INFERENCE_ENDPOINT}/models/{model}/info"
+
+ response = get_session().get(url, headers=build_hf_headers(token=self.token))
+ hf_raise_for_status(response)
+ return response.json()
+
+ def health_check(self, model: Optional[str] = None) -> bool:
+ """
+ Check the health of the deployed endpoint.
+
+ Health check is only available with Inference Endpoints powered by Text-Generation-Inference (TGI) or Text-Embedding-Inference (TEI).
+ For Inference API, please use [`InferenceClient.get_model_status`] instead.
+
+ Args:
+ model (`str`, *optional*):
+ URL of the Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `bool`: True if everything is working fine.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient("https://jzgu0buei5.us-east-1.aws.endpoints.huggingface.cloud")
+ >>> client.health_check()
+ True
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Health check is not supported on '{self.provider}'.")
+
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if not model.startswith(("http://", "https://")):
+ raise ValueError(
+ "Model must be an Inference Endpoint URL. For serverless Inference API, please use `InferenceClient.get_model_status`."
+ )
+ url = model.rstrip("/") + "/health"
+
+ response = get_session().get(url, headers=build_hf_headers(token=self.token))
+ return response.status_code == 200
+
+ @_deprecate_method(
+ version="0.33.0",
+ message=(
+ "HF Inference API is getting revamped and will only support warm models in the future (no cold start allowed)."
+ " Use `HfApi.model_info` to get the model status both with HF Inference API and external providers."
+ ),
+ )
+ def get_model_status(self, model: Optional[str] = None) -> ModelStatus:
+ """
+ Get the status of a model hosted on the HF Inference API.
+
+
+
+ This endpoint is mostly useful when you already know which model you want to use and want to check its
+ availability. If you want to discover already deployed models, you should rather use [`~InferenceClient.list_deployed_models`].
+
+
+
+ Args:
+ model (`str`, *optional*):
+ Identifier of the model for witch the status gonna be checked. If model is not provided,
+ the model associated with this instance of [`InferenceClient`] will be used. Only HF Inference API service can be checked so the
+ identifier cannot be a URL.
+
+
+ Returns:
+ [`ModelStatus`]: An instance of ModelStatus dataclass, containing information,
+ about the state of the model: load, state, compute type and framework.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.get_model_status("meta-llama/Meta-Llama-3-8B-Instruct")
+ ModelStatus(loaded=True, state='Loaded', compute_type='gpu', framework='text-generation-inference')
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Getting model status is not supported on '{self.provider}'.")
+
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if model.startswith("https://"):
+ raise NotImplementedError("Model status is only available for Inference API endpoints.")
+ url = f"{constants.INFERENCE_ENDPOINT}/status/{model}"
+
+ response = get_session().get(url, headers=build_hf_headers(token=self.token))
+ hf_raise_for_status(response)
+ response_data = response.json()
+
+ if "error" in response_data:
+ raise ValueError(response_data["error"])
+
+ return ModelStatus(
+ loaded=response_data["loaded"],
+ state=response_data["state"],
+ compute_type=response_data["compute_type"],
+ framework=response_data["framework"],
+ )
+
+ @property
+ def chat(self) -> "ProxyClientChat":
+ return ProxyClientChat(self)
+
+
+class _ProxyClient:
+ """Proxy class to be able to call `client.chat.completion.create(...)` as OpenAI client."""
+
+ def __init__(self, client: InferenceClient):
+ self._client = client
+
+
+class ProxyClientChat(_ProxyClient):
+ """Proxy class to be able to call `client.chat.completion.create(...)` as OpenAI client."""
+
+ @property
+ def completions(self) -> "ProxyClientChatCompletions":
+ return ProxyClientChatCompletions(self._client)
+
+
+class ProxyClientChatCompletions(_ProxyClient):
+ """Proxy class to be able to call `client.chat.completion.create(...)` as OpenAI client."""
+
+ @property
+ def create(self):
+ return self._client.chat_completion
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_common.py b/env/Lib/site-packages/huggingface_hub/inference/_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..574f726b67dfd11baa8db8914f37969306f2a925
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_common.py
@@ -0,0 +1,422 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities used by both the sync and async inference clients."""
+
+import base64
+import io
+import json
+import logging
+from contextlib import contextmanager
+from dataclasses import dataclass
+from pathlib import Path
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncIterable,
+ BinaryIO,
+ ContextManager,
+ Dict,
+ Generator,
+ Iterable,
+ List,
+ Literal,
+ NoReturn,
+ Optional,
+ Union,
+ overload,
+)
+
+from requests import HTTPError
+
+from huggingface_hub.errors import (
+ GenerationError,
+ IncompleteGenerationError,
+ OverloadedError,
+ TextGenerationError,
+ UnknownError,
+ ValidationError,
+)
+
+from ..utils import get_session, is_aiohttp_available, is_numpy_available, is_pillow_available
+from ._generated.types import ChatCompletionStreamOutput, TextGenerationStreamOutput
+
+
+if TYPE_CHECKING:
+ from aiohttp import ClientResponse, ClientSession
+ from PIL.Image import Image
+
+# TYPES
+UrlT = str
+PathT = Union[str, Path]
+BinaryT = Union[bytes, BinaryIO]
+ContentT = Union[BinaryT, PathT, UrlT]
+
+# Use to set a Accept: image/png header
+TASKS_EXPECTING_IMAGES = {"text-to-image", "image-to-image"}
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class RequestParameters:
+ url: str
+ task: str
+ model: Optional[str]
+ json: Optional[Union[str, Dict, List]]
+ data: Optional[ContentT]
+ headers: Dict[str, Any]
+
+
+# Add dataclass for ModelStatus. We use this dataclass in get_model_status function.
+@dataclass
+class ModelStatus:
+ """
+ This Dataclass represents the model status in the HF Inference API.
+
+ Args:
+ loaded (`bool`):
+ If the model is currently loaded into HF's Inference API. Models
+ are loaded on-demand, leading to the user's first request taking longer.
+ If a model is loaded, you can be assured that it is in a healthy state.
+ state (`str`):
+ The current state of the model. This can be 'Loaded', 'Loadable', 'TooBig'.
+ If a model's state is 'Loadable', it's not too big and has a supported
+ backend. Loadable models are automatically loaded when the user first
+ requests inference on the endpoint. This means it is transparent for the
+ user to load a model, except that the first call takes longer to complete.
+ compute_type (`Dict`):
+ Information about the compute resource the model is using or will use, such as 'gpu' type and number of
+ replicas.
+ framework (`str`):
+ The name of the framework that the model was built with, such as 'transformers'
+ or 'text-generation-inference'.
+ """
+
+ loaded: bool
+ state: str
+ compute_type: Dict
+ framework: str
+
+
+## IMPORT UTILS
+
+
+def _import_aiohttp():
+ # Make sure `aiohttp` is installed on the machine.
+ if not is_aiohttp_available():
+ raise ImportError("Please install aiohttp to use `AsyncInferenceClient` (`pip install aiohttp`).")
+ import aiohttp
+
+ return aiohttp
+
+
+def _import_numpy():
+ """Make sure `numpy` is installed on the machine."""
+ if not is_numpy_available():
+ raise ImportError("Please install numpy to use deal with embeddings (`pip install numpy`).")
+ import numpy
+
+ return numpy
+
+
+def _import_pil_image():
+ """Make sure `PIL` is installed on the machine."""
+ if not is_pillow_available():
+ raise ImportError(
+ "Please install Pillow to use deal with images (`pip install Pillow`). If you don't want the image to be"
+ " post-processed, use `client.post(...)` and get the raw response from the server."
+ )
+ from PIL import Image
+
+ return Image
+
+
+## ENCODING / DECODING UTILS
+
+
+@overload
+def _open_as_binary(
+ content: ContentT,
+) -> ContextManager[BinaryT]: ... # means "if input is not None, output is not None"
+
+
+@overload
+def _open_as_binary(
+ content: Literal[None],
+) -> ContextManager[Literal[None]]: ... # means "if input is None, output is None"
+
+
+@contextmanager # type: ignore
+def _open_as_binary(content: Optional[ContentT]) -> Generator[Optional[BinaryT], None, None]:
+ """Open `content` as a binary file, either from a URL, a local path, or raw bytes.
+
+ Do nothing if `content` is None,
+
+ TODO: handle a PIL.Image as input
+ TODO: handle base64 as input
+ """
+ # If content is a string => must be either a URL or a path
+ if isinstance(content, str):
+ if content.startswith("https://") or content.startswith("http://"):
+ logger.debug(f"Downloading content from {content}")
+ yield get_session().get(content).content # TODO: retrieve as stream and pipe to post request ?
+ return
+ content = Path(content)
+ if not content.exists():
+ raise FileNotFoundError(
+ f"File not found at {content}. If `data` is a string, it must either be a URL or a path to a local"
+ " file. To pass raw content, please encode it as bytes first."
+ )
+
+ # If content is a Path => open it
+ if isinstance(content, Path):
+ logger.debug(f"Opening content from {content}")
+ with content.open("rb") as f:
+ yield f
+ else:
+ # Otherwise: already a file-like object or None
+ yield content
+
+
+def _b64_encode(content: ContentT) -> str:
+ """Encode a raw file (image, audio) into base64. Can be bytes, an opened file, a path or a URL."""
+ with _open_as_binary(content) as data:
+ data_as_bytes = data if isinstance(data, bytes) else data.read()
+ return base64.b64encode(data_as_bytes).decode()
+
+
+def _b64_to_image(encoded_image: str) -> "Image":
+ """Parse a base64-encoded string into a PIL Image."""
+ Image = _import_pil_image()
+ return Image.open(io.BytesIO(base64.b64decode(encoded_image)))
+
+
+def _bytes_to_list(content: bytes) -> List:
+ """Parse bytes from a Response object into a Python list.
+
+ Expects the response body to be JSON-encoded data.
+
+ NOTE: This is exactly the same implementation as `_bytes_to_dict` and will not complain if the returned data is a
+ dictionary. The only advantage of having both is to help the user (and mypy) understand what kind of data to expect.
+ """
+ return json.loads(content.decode())
+
+
+def _bytes_to_dict(content: bytes) -> Dict:
+ """Parse bytes from a Response object into a Python dictionary.
+
+ Expects the response body to be JSON-encoded data.
+
+ NOTE: This is exactly the same implementation as `_bytes_to_list` and will not complain if the returned data is a
+ list. The only advantage of having both is to help the user (and mypy) understand what kind of data to expect.
+ """
+ return json.loads(content.decode())
+
+
+def _bytes_to_image(content: bytes) -> "Image":
+ """Parse bytes from a Response object into a PIL Image.
+
+ Expects the response body to be raw bytes. To deal with b64 encoded images, use `_b64_to_image` instead.
+ """
+ Image = _import_pil_image()
+ return Image.open(io.BytesIO(content))
+
+
+def _as_dict(response: Union[bytes, Dict]) -> Dict:
+ return json.loads(response) if isinstance(response, bytes) else response
+
+
+## PAYLOAD UTILS
+
+
+## STREAMING UTILS
+
+
+def _stream_text_generation_response(
+ bytes_output_as_lines: Iterable[bytes], details: bool
+) -> Union[Iterable[str], Iterable[TextGenerationStreamOutput]]:
+ """Used in `InferenceClient.text_generation`."""
+ # Parse ServerSentEvents
+ for byte_payload in bytes_output_as_lines:
+ try:
+ output = _format_text_generation_stream_output(byte_payload, details)
+ except StopIteration:
+ break
+ if output is not None:
+ yield output
+
+
+async def _async_stream_text_generation_response(
+ bytes_output_as_lines: AsyncIterable[bytes], details: bool
+) -> Union[AsyncIterable[str], AsyncIterable[TextGenerationStreamOutput]]:
+ """Used in `AsyncInferenceClient.text_generation`."""
+ # Parse ServerSentEvents
+ async for byte_payload in bytes_output_as_lines:
+ try:
+ output = _format_text_generation_stream_output(byte_payload, details)
+ except StopIteration:
+ break
+ if output is not None:
+ yield output
+
+
+def _format_text_generation_stream_output(
+ byte_payload: bytes, details: bool
+) -> Optional[Union[str, TextGenerationStreamOutput]]:
+ if not byte_payload.startswith(b"data:"):
+ return None # empty line
+
+ if byte_payload.strip() == b"data: [DONE]":
+ raise StopIteration("[DONE] signal received.")
+
+ # Decode payload
+ payload = byte_payload.decode("utf-8")
+ json_payload = json.loads(payload.lstrip("data:").rstrip("/n"))
+
+ # Either an error as being returned
+ if json_payload.get("error") is not None:
+ raise _parse_text_generation_error(json_payload["error"], json_payload.get("error_type"))
+
+ # Or parse token payload
+ output = TextGenerationStreamOutput.parse_obj_as_instance(json_payload)
+ return output.token.text if not details else output
+
+
+def _stream_chat_completion_response(
+ bytes_lines: Iterable[bytes],
+) -> Iterable[ChatCompletionStreamOutput]:
+ """Used in `InferenceClient.chat_completion` if model is served with TGI."""
+ for item in bytes_lines:
+ try:
+ output = _format_chat_completion_stream_output(item)
+ except StopIteration:
+ break
+ if output is not None:
+ yield output
+
+
+async def _async_stream_chat_completion_response(
+ bytes_lines: AsyncIterable[bytes],
+) -> AsyncIterable[ChatCompletionStreamOutput]:
+ """Used in `AsyncInferenceClient.chat_completion`."""
+ async for item in bytes_lines:
+ try:
+ output = _format_chat_completion_stream_output(item)
+ except StopIteration:
+ break
+ if output is not None:
+ yield output
+
+
+def _format_chat_completion_stream_output(
+ byte_payload: bytes,
+) -> Optional[ChatCompletionStreamOutput]:
+ if not byte_payload.startswith(b"data:"):
+ return None # empty line
+
+ if byte_payload.strip() == b"data: [DONE]":
+ raise StopIteration("[DONE] signal received.")
+
+ # Decode payload
+ payload = byte_payload.decode("utf-8")
+ json_payload = json.loads(payload.lstrip("data:").rstrip("/n"))
+
+ # Either an error as being returned
+ if json_payload.get("error") is not None:
+ raise _parse_text_generation_error(json_payload["error"], json_payload.get("error_type"))
+
+ # Or parse token payload
+ return ChatCompletionStreamOutput.parse_obj_as_instance(json_payload)
+
+
+async def _async_yield_from(client: "ClientSession", response: "ClientResponse") -> AsyncIterable[bytes]:
+ async for byte_payload in response.content:
+ yield byte_payload.strip()
+ await client.close()
+
+
+# "TGI servers" are servers running with the `text-generation-inference` backend.
+# This backend is the go-to solution to run large language models at scale. However,
+# for some smaller models (e.g. "gpt2") the default `transformers` + `api-inference`
+# solution is still in use.
+#
+# Both approaches have very similar APIs, but not exactly the same. What we do first in
+# the `text_generation` method is to assume the model is served via TGI. If we realize
+# it's not the case (i.e. we receive an HTTP 400 Bad Request), we fallback to the
+# default API with a warning message. When that's the case, We remember the unsupported
+# attributes for this model in the `_UNSUPPORTED_TEXT_GENERATION_KWARGS` global variable.
+#
+# In addition, TGI servers have a built-in API route for chat-completion, which is not
+# available on the default API. We use this route to provide a more consistent behavior
+# when available.
+#
+# For more details, see https://github.com/huggingface/text-generation-inference and
+# https://huggingface.co/docs/api-inference/detailed_parameters#text-generation-task.
+
+_UNSUPPORTED_TEXT_GENERATION_KWARGS: Dict[Optional[str], List[str]] = {}
+
+
+def _set_unsupported_text_generation_kwargs(model: Optional[str], unsupported_kwargs: List[str]) -> None:
+ _UNSUPPORTED_TEXT_GENERATION_KWARGS.setdefault(model, []).extend(unsupported_kwargs)
+
+
+def _get_unsupported_text_generation_kwargs(model: Optional[str]) -> List[str]:
+ return _UNSUPPORTED_TEXT_GENERATION_KWARGS.get(model, [])
+
+
+# TEXT GENERATION ERRORS
+# ----------------------
+# Text-generation errors are parsed separately to handle as much as possible the errors returned by the text generation
+# inference project (https://github.com/huggingface/text-generation-inference).
+# ----------------------
+
+
+def raise_text_generation_error(http_error: HTTPError) -> NoReturn:
+ """
+ Try to parse text-generation-inference error message and raise HTTPError in any case.
+
+ Args:
+ error (`HTTPError`):
+ The HTTPError that have been raised.
+ """
+ # Try to parse a Text Generation Inference error
+
+ try:
+ # Hacky way to retrieve payload in case of aiohttp error
+ payload = getattr(http_error, "response_error_payload", None) or http_error.response.json()
+ error = payload.get("error")
+ error_type = payload.get("error_type")
+ except Exception: # no payload
+ raise http_error
+
+ # If error_type => more information than `hf_raise_for_status`
+ if error_type is not None:
+ exception = _parse_text_generation_error(error, error_type)
+ raise exception from http_error
+
+ # Otherwise, fallback to default error
+ raise http_error
+
+
+def _parse_text_generation_error(error: Optional[str], error_type: Optional[str]) -> TextGenerationError:
+ if error_type == "generation":
+ return GenerationError(error) # type: ignore
+ if error_type == "incomplete_generation":
+ return IncompleteGenerationError(error) # type: ignore
+ if error_type == "overloaded":
+ return OverloadedError(error) # type: ignore
+ if error_type == "validation":
+ return ValidationError(error) # type: ignore
+ return UnknownError(error) # type: ignore
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/__init__.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/_async_client.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/_async_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f350b19e597f09acd89d6cbd602023b48dcbcb2
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/_async_client.py
@@ -0,0 +1,3628 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# WARNING
+# This entire file has been adapted from the sync-client code in `src/huggingface_hub/inference/_client.py`.
+# Any change in InferenceClient will be automatically reflected in AsyncInferenceClient.
+# To re-generate the code, run `make style` or `python ./utils/generate_async_inference_client.py --update`.
+# WARNING
+import asyncio
+import base64
+import logging
+import re
+import warnings
+from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Literal, Optional, Set, Union, overload
+
+from huggingface_hub import constants
+from huggingface_hub.errors import InferenceTimeoutError
+from huggingface_hub.inference._common import (
+ TASKS_EXPECTING_IMAGES,
+ ContentT,
+ ModelStatus,
+ RequestParameters,
+ _async_stream_chat_completion_response,
+ _async_stream_text_generation_response,
+ _b64_encode,
+ _b64_to_image,
+ _bytes_to_dict,
+ _bytes_to_image,
+ _bytes_to_list,
+ _get_unsupported_text_generation_kwargs,
+ _import_numpy,
+ _open_as_binary,
+ _set_unsupported_text_generation_kwargs,
+ raise_text_generation_error,
+)
+from huggingface_hub.inference._generated.types import (
+ AudioClassificationOutputElement,
+ AudioClassificationOutputTransform,
+ AudioToAudioOutputElement,
+ AutomaticSpeechRecognitionOutput,
+ ChatCompletionInputGrammarType,
+ ChatCompletionInputStreamOptions,
+ ChatCompletionInputTool,
+ ChatCompletionInputToolChoiceClass,
+ ChatCompletionInputToolChoiceEnum,
+ ChatCompletionOutput,
+ ChatCompletionStreamOutput,
+ DocumentQuestionAnsweringOutputElement,
+ FillMaskOutputElement,
+ ImageClassificationOutputElement,
+ ImageClassificationOutputTransform,
+ ImageSegmentationOutputElement,
+ ImageSegmentationSubtask,
+ ImageToImageTargetSize,
+ ImageToTextOutput,
+ ObjectDetectionOutputElement,
+ Padding,
+ QuestionAnsweringOutputElement,
+ SummarizationOutput,
+ SummarizationTruncationStrategy,
+ TableQuestionAnsweringOutputElement,
+ TextClassificationOutputElement,
+ TextClassificationOutputTransform,
+ TextGenerationInputGrammarType,
+ TextGenerationOutput,
+ TextGenerationStreamOutput,
+ TextToSpeechEarlyStoppingEnum,
+ TokenClassificationAggregationStrategy,
+ TokenClassificationOutputElement,
+ TranslationOutput,
+ TranslationTruncationStrategy,
+ VisualQuestionAnsweringOutputElement,
+ ZeroShotClassificationOutputElement,
+ ZeroShotImageClassificationOutputElement,
+)
+from huggingface_hub.inference._providers import PROVIDER_T, HFInferenceTask, get_provider_helper
+from huggingface_hub.utils import build_hf_headers, get_session, hf_raise_for_status
+from huggingface_hub.utils._deprecation import _deprecate_arguments, _deprecate_method
+
+from .._common import _async_yield_from, _import_aiohttp
+
+
+if TYPE_CHECKING:
+ import numpy as np
+ from aiohttp import ClientResponse, ClientSession
+ from PIL.Image import Image
+
+logger = logging.getLogger(__name__)
+
+
+MODEL_KWARGS_NOT_USED_REGEX = re.compile(r"The following `model_kwargs` are not used by the model: \[(.*?)\]")
+
+
+class AsyncInferenceClient:
+ """
+ Initialize a new Inference Client.
+
+ [`InferenceClient`] aims to provide a unified experience to perform inference. The client can be used
+ seamlessly with either the (free) Inference API, self-hosted Inference Endpoints, or third-party Inference Providers.
+
+ Args:
+ model (`str`, `optional`):
+ The model to run inference with. Can be a model id hosted on the Hugging Face Hub, e.g. `meta-llama/Meta-Llama-3-8B-Instruct`
+ or a URL to a deployed Inference Endpoint. Defaults to None, in which case a recommended model is
+ automatically selected for the task.
+ Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2
+ arguments are mutually exclusive. If using `base_url` for chat completion, the `/chat/completions` suffix
+ path will be appended to the base URL (see the [TGI Messages API](https://huggingface.co/docs/text-generation-inference/en/messages_api)
+ documentation for details). When passing a URL as `model`, the client will not append any suffix path to it.
+ provider (`str`, *optional*):
+ Name of the provider to use for inference. Can be `"black-forest-labs"`, `"fal-ai"`, `"fireworks-ai"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"replicate"`, "sambanova"` or `"together"`.
+ defaults to hf-inference (Hugging Face Serverless Inference API).
+ If model is a URL or `base_url` is passed, then `provider` is not used.
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Pass `token=False` if you don't want to send your token to the server.
+ Note: for better compatibility with OpenAI's client, `token` has been aliased as `api_key`. Those 2
+ arguments are mutually exclusive and have the exact same behavior.
+ timeout (`float`, `optional`):
+ The maximum number of seconds to wait for a response from the server. Loading a new model in Inference
+ API can take up to several minutes. Defaults to None, meaning it will loop until the server is available.
+ headers (`Dict[str, str]`, `optional`):
+ Additional headers to send to the server. By default only the authorization and user-agent headers are sent.
+ Values in this dictionary will override the default values.
+ cookies (`Dict[str, str]`, `optional`):
+ Additional cookies to send to the server.
+ trust_env ('bool', 'optional'):
+ Trust environment settings for proxy configuration if the parameter is `True` (`False` by default).
+ proxies (`Any`, `optional`):
+ Proxies to use for the request.
+ base_url (`str`, `optional`):
+ Base URL to run inference. This is a duplicated argument from `model` to make [`InferenceClient`]
+ follow the same pattern as `openai.OpenAI` client. Cannot be used if `model` is set. Defaults to None.
+ api_key (`str`, `optional`):
+ Token to use for authentication. This is a duplicated argument from `token` to make [`InferenceClient`]
+ follow the same pattern as `openai.OpenAI` client. Cannot be used if `token` is set. Defaults to None.
+ """
+
+ def __init__(
+ self,
+ model: Optional[str] = None,
+ *,
+ provider: Optional[PROVIDER_T] = None,
+ token: Optional[str] = None,
+ timeout: Optional[float] = None,
+ headers: Optional[Dict[str, str]] = None,
+ cookies: Optional[Dict[str, str]] = None,
+ trust_env: bool = False,
+ proxies: Optional[Any] = None,
+ # OpenAI compatibility
+ base_url: Optional[str] = None,
+ api_key: Optional[str] = None,
+ ) -> None:
+ if model is not None and base_url is not None:
+ raise ValueError(
+ "Received both `model` and `base_url` arguments. Please provide only one of them."
+ " `base_url` is an alias for `model` to make the API compatible with OpenAI's client."
+ " If using `base_url` for chat completion, the `/chat/completions` suffix path will be appended to the base url."
+ " When passing a URL as `model`, the client will not append any suffix path to it."
+ )
+ if token is not None and api_key is not None:
+ raise ValueError(
+ "Received both `token` and `api_key` arguments. Please provide only one of them."
+ " `api_key` is an alias for `token` to make the API compatible with OpenAI's client."
+ " It has the exact same behavior as `token`."
+ )
+
+ self.model: Optional[str] = base_url or model
+ self.token: Optional[str] = token if token is not None else api_key
+ self.headers = headers if headers is not None else {}
+
+ # Configure provider
+ self.provider = provider if provider is not None else "hf-inference"
+
+ self.cookies = cookies
+ self.timeout = timeout
+ self.trust_env = trust_env
+ self.proxies = proxies
+
+ # Keep track of the sessions to close them properly
+ self._sessions: Dict["ClientSession", Set["ClientResponse"]] = dict()
+
+ def __repr__(self):
+ return f""
+
+ @overload
+ async def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[False] = ...,
+ ) -> bytes: ...
+
+ @overload
+ async def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[True] = ...,
+ ) -> AsyncIterable[bytes]: ...
+
+ @overload
+ async def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, AsyncIterable[bytes]]: ...
+
+ @_deprecate_method(
+ version="0.31.0",
+ message=(
+ "Making direct POST requests to the inference server is not supported anymore. "
+ "Please use task methods instead (e.g. `InferenceClient.chat_completion`). "
+ "If your use case is not supported, please open an issue in https://github.com/huggingface/huggingface_hub."
+ ),
+ )
+ async def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, AsyncIterable[bytes]]:
+ """
+ Make a POST request to the inference server.
+
+ This method is deprecated and will be removed in the future.
+ Please use task methods instead (e.g. `InferenceClient.chat_completion`).
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(
+ "Cannot use `post` with another provider than `hf-inference`. "
+ "`InferenceClient.post` is deprecated and should not be used directly anymore."
+ )
+ provider_helper = HFInferenceTask(task or "unknown")
+ mapped_model = provider_helper._prepare_mapped_model(model or self.model)
+ url = provider_helper._prepare_url(self.token, mapped_model) # type: ignore[arg-type]
+ headers = provider_helper._prepare_headers(self.headers, self.token) # type: ignore[arg-type]
+ return await self._inner_post(
+ request_parameters=RequestParameters(
+ url=url,
+ task=task or "unknown",
+ model=model or "unknown",
+ json=json,
+ data=data,
+ headers=headers,
+ ),
+ stream=stream,
+ )
+
+ @overload
+ async def _inner_post( # type: ignore[misc]
+ self, request_parameters: RequestParameters, *, stream: Literal[False] = ...
+ ) -> bytes: ...
+
+ @overload
+ async def _inner_post( # type: ignore[misc]
+ self, request_parameters: RequestParameters, *, stream: Literal[True] = ...
+ ) -> AsyncIterable[bytes]: ...
+
+ @overload
+ async def _inner_post(
+ self, request_parameters: RequestParameters, *, stream: bool = False
+ ) -> Union[bytes, AsyncIterable[bytes]]: ...
+
+ async def _inner_post(
+ self, request_parameters: RequestParameters, *, stream: bool = False
+ ) -> Union[bytes, AsyncIterable[bytes]]:
+ """Make a request to the inference server."""
+
+ aiohttp = _import_aiohttp()
+
+ # TODO: this should be handled in provider helpers directly
+ if request_parameters.task in TASKS_EXPECTING_IMAGES and "Accept" not in request_parameters.headers:
+ request_parameters.headers["Accept"] = "image/png"
+
+ while True:
+ with _open_as_binary(request_parameters.data) as data_as_binary:
+ # Do not use context manager as we don't want to close the connection immediately when returning
+ # a stream
+ session = self._get_client_session(headers=request_parameters.headers)
+
+ try:
+ response = await session.post(
+ request_parameters.url, json=request_parameters.json, data=data_as_binary, proxy=self.proxies
+ )
+ response_error_payload = None
+ if response.status != 200:
+ try:
+ response_error_payload = await response.json() # get payload before connection closed
+ except Exception:
+ pass
+ response.raise_for_status()
+ if stream:
+ return _async_yield_from(session, response)
+ else:
+ content = await response.read()
+ await session.close()
+ return content
+ except asyncio.TimeoutError as error:
+ await session.close()
+ # Convert any `TimeoutError` to a `InferenceTimeoutError`
+ raise InferenceTimeoutError(f"Inference call timed out: {request_parameters.url}") from error # type: ignore
+ except aiohttp.ClientResponseError as error:
+ error.response_error_payload = response_error_payload
+ await session.close()
+ raise error
+ except Exception:
+ await session.close()
+ raise
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback):
+ await self.close()
+
+ def __del__(self):
+ if len(self._sessions) > 0:
+ warnings.warn(
+ "Deleting 'AsyncInferenceClient' client but some sessions are still open. "
+ "This can happen if you've stopped streaming data from the server before the stream was complete. "
+ "To close the client properly, you must call `await client.close()` "
+ "or use an async context (e.g. `async with AsyncInferenceClient(): ...`."
+ )
+
+ async def close(self):
+ """Close all open sessions.
+
+ By default, 'aiohttp.ClientSession' objects are closed automatically when a call is completed. However, if you
+ are streaming data from the server and you stop before the stream is complete, you must call this method to
+ close the session properly.
+
+ Another possibility is to use an async context (e.g. `async with AsyncInferenceClient(): ...`).
+ """
+ await asyncio.gather(*[session.close() for session in self._sessions.keys()])
+
+ async def audio_classification(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ top_k: Optional[int] = None,
+ function_to_apply: Optional["AudioClassificationOutputTransform"] = None,
+ ) -> List[AudioClassificationOutputElement]:
+ """
+ Perform audio classification on the provided audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content to classify. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model to use for audio classification. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio classification will be used.
+ top_k (`int`, *optional*):
+ When specified, limits the output to the top K most probable classes.
+ function_to_apply (`"AudioClassificationOutputTransform"`, *optional*):
+ The function to apply to the model outputs in order to retrieve the scores.
+
+ Returns:
+ `List[AudioClassificationOutputElement]`: List of [`AudioClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.audio_classification("audio.flac")
+ [
+ AudioClassificationOutputElement(score=0.4976358711719513, label='hap'),
+ AudioClassificationOutputElement(score=0.3677836060523987, label='neu'),
+ ...
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="audio-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=audio,
+ parameters={"function_to_apply": function_to_apply, "top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return AudioClassificationOutputElement.parse_obj_as_list(response)
+
+ async def audio_to_audio(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[AudioToAudioOutputElement]:
+ """
+ Performs multiple tasks related to audio-to-audio depending on the model (eg: speech enhancement, source separation).
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content for the model. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model can be any model which takes an audio file and returns another audio file. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio_to_audio will be used.
+
+ Returns:
+ `List[AudioToAudioOutputElement]`: A list of [`AudioToAudioOutputElement`] items containing audios label, content-type, and audio content in blob.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> audio_output = await client.audio_to_audio("audio.flac")
+ >>> async for i, item in enumerate(audio_output):
+ >>> with open(f"output_{i}.flac", "wb") as f:
+ f.write(item.blob)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="audio-to-audio")
+ request_parameters = provider_helper.prepare_request(
+ inputs=audio,
+ parameters={},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ audio_output = AudioToAudioOutputElement.parse_obj_as_list(response)
+ for item in audio_output:
+ item.blob = base64.b64decode(item.blob)
+ return audio_output
+
+ async def automatic_speech_recognition(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> AutomaticSpeechRecognitionOutput:
+ """
+ Perform automatic speech recognition (ASR or audio-to-text) on the given audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The content to transcribe. It can be raw audio bytes, local audio file, or a URL to an audio file.
+ model (`str`, *optional*):
+ The model to use for ASR. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for ASR will be used.
+ extra_body (`Dict`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+ Returns:
+ [`AutomaticSpeechRecognitionOutput`]: An item containing the transcribed text and optionally the timestamp chunks.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.automatic_speech_recognition("hello_world.flac").text
+ "hello world"
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="automatic-speech-recognition")
+ request_parameters = provider_helper.prepare_request(
+ inputs=audio,
+ parameters={**(extra_body or {})},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return AutomaticSpeechRecognitionOutput.parse_obj_as_instance(response)
+
+ @overload
+ async def chat_completion( # type: ignore
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[False] = False,
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> ChatCompletionOutput: ...
+
+ @overload
+ async def chat_completion( # type: ignore
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[True] = True,
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> AsyncIterable[ChatCompletionStreamOutput]: ...
+
+ @overload
+ async def chat_completion(
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> Union[ChatCompletionOutput, AsyncIterable[ChatCompletionStreamOutput]]: ...
+
+ async def chat_completion(
+ self,
+ messages: List[Dict],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ # Parameters from ChatCompletionInput (handled manually)
+ frequency_penalty: Optional[float] = None,
+ logit_bias: Optional[List[float]] = None,
+ logprobs: Optional[bool] = None,
+ max_tokens: Optional[int] = None,
+ n: Optional[int] = None,
+ presence_penalty: Optional[float] = None,
+ response_format: Optional[ChatCompletionInputGrammarType] = None,
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None,
+ temperature: Optional[float] = None,
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None,
+ tool_prompt: Optional[str] = None,
+ tools: Optional[List[ChatCompletionInputTool]] = None,
+ top_logprobs: Optional[int] = None,
+ top_p: Optional[float] = None,
+ extra_body: Optional[Dict] = None,
+ ) -> Union[ChatCompletionOutput, AsyncIterable[ChatCompletionStreamOutput]]:
+ """
+ A method for completing conversations using a specified language model.
+
+
+
+ The `client.chat_completion` method is aliased as `client.chat.completions.create` for compatibility with OpenAI's client.
+ Inputs and outputs are strictly the same and using either syntax will yield the same results.
+ Check out the [Inference guide](https://huggingface.co/docs/huggingface_hub/guides/inference#openai-compatibility)
+ for more details about OpenAI's compatibility.
+
+
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ messages (List of [`ChatCompletionInputMessage`]):
+ Conversation history consisting of roles and content pairs.
+ model (`str`, *optional*):
+ The model to use for chat-completion. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for chat-based text-generation will be used.
+ See https://huggingface.co/tasks/text-generation for more details.
+ If `model` is a model ID, it is passed to the server as the `model` parameter. If you want to define a
+ custom URL while setting `model` in the request payload, you must set `base_url` when initializing [`InferenceClient`].
+ frequency_penalty (`float`, *optional*):
+ Penalizes new tokens based on their existing frequency
+ in the text so far. Range: [-2.0, 2.0]. Defaults to 0.0.
+ logit_bias (`List[float]`, *optional*):
+ Adjusts the likelihood of specific tokens appearing in the generated output.
+ logprobs (`bool`, *optional*):
+ Whether to return log probabilities of the output tokens or not. If true, returns the log
+ probabilities of each output token returned in the content of message.
+ max_tokens (`int`, *optional*):
+ Maximum number of tokens allowed in the response. Defaults to 100.
+ n (`int`, *optional*):
+ The number of completions to generate for each prompt.
+ presence_penalty (`float`, *optional*):
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the
+ text so far, increasing the model's likelihood to talk about new topics.
+ response_format ([`ChatCompletionInputGrammarType`], *optional*):
+ Grammar constraints. Can be either a JSONSchema or a regex.
+ seed (Optional[`int`], *optional*):
+ Seed for reproducible control flow. Defaults to None.
+ stop (`List[str]`, *optional*):
+ Up to four strings which trigger the end of the response.
+ Defaults to None.
+ stream (`bool`, *optional*):
+ Enable realtime streaming of responses. Defaults to False.
+ stream_options ([`ChatCompletionInputStreamOptions`], *optional*):
+ Options for streaming completions.
+ temperature (`float`, *optional*):
+ Controls randomness of the generations. Lower values ensure
+ less random completions. Range: [0, 2]. Defaults to 1.0.
+ top_logprobs (`int`, *optional*):
+ An integer between 0 and 5 specifying the number of most likely tokens to return at each token
+ position, each with an associated log probability. logprobs must be set to true if this parameter is
+ used.
+ top_p (`float`, *optional*):
+ Fraction of the most likely next words to sample from.
+ Must be between 0 and 1. Defaults to 1.0.
+ tool_choice ([`ChatCompletionInputToolChoiceClass`] or [`ChatCompletionInputToolChoiceEnum`], *optional*):
+ The tool to use for the completion. Defaults to "auto".
+ tool_prompt (`str`, *optional*):
+ A prompt to be appended before the tools.
+ tools (List of [`ChatCompletionInputTool`], *optional*):
+ A list of tools the model may call. Currently, only functions are supported as a tool. Use this to
+ provide a list of functions the model may generate JSON inputs for.
+ extra_body (`Dict`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+ Returns:
+ [`ChatCompletionOutput`] or Iterable of [`ChatCompletionStreamOutput`]:
+ Generated text returned from the server:
+ - if `stream=False`, the generated text is returned as a [`ChatCompletionOutput`] (default).
+ - if `stream=True`, the generated text is returned token by token as a sequence of [`ChatCompletionStreamOutput`].
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> messages = [{"role": "user", "content": "What is the capital of France?"}]
+ >>> client = AsyncInferenceClient("meta-llama/Meta-Llama-3-8B-Instruct")
+ >>> await client.chat_completion(messages, max_tokens=100)
+ ChatCompletionOutput(
+ choices=[
+ ChatCompletionOutputComplete(
+ finish_reason='eos_token',
+ index=0,
+ message=ChatCompletionOutputMessage(
+ role='assistant',
+ content='The capital of France is Paris.',
+ name=None,
+ tool_calls=None
+ ),
+ logprobs=None
+ )
+ ],
+ created=1719907176,
+ id='',
+ model='meta-llama/Meta-Llama-3-8B-Instruct',
+ object='text_completion',
+ system_fingerprint='2.0.4-sha-f426a33',
+ usage=ChatCompletionOutputUsage(
+ completion_tokens=8,
+ prompt_tokens=17,
+ total_tokens=25
+ )
+ )
+ ```
+
+ Example using streaming:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> messages = [{"role": "user", "content": "What is the capital of France?"}]
+ >>> client = AsyncInferenceClient("meta-llama/Meta-Llama-3-8B-Instruct")
+ >>> async for token in await client.chat_completion(messages, max_tokens=10, stream=True):
+ ... print(token)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content='The', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' capital', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ (...)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' may', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ```
+
+ Example using OpenAI's syntax:
+ ```py
+ # Must be run in an async context
+ # instead of `from openai import OpenAI`
+ from huggingface_hub import AsyncInferenceClient
+
+ # instead of `client = OpenAI(...)`
+ client = AsyncInferenceClient(
+ base_url=...,
+ api_key=...,
+ )
+
+ output = await client.chat.completions.create(
+ model="meta-llama/Meta-Llama-3-8B-Instruct",
+ messages=[
+ {"role": "system", "content": "You are a helpful assistant."},
+ {"role": "user", "content": "Count to 10"},
+ ],
+ stream=True,
+ max_tokens=1024,
+ )
+
+ for chunk in output:
+ print(chunk.choices[0].delta.content)
+ ```
+
+ Example using a third-party provider directly with extra (provider-specific) parameters. Usage will be billed on your Together AI account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="together", # Use Together AI provider
+ ... api_key="", # Pass your Together API key directly
+ ... )
+ >>> client.chat_completion(
+ ... model="meta-llama/Meta-Llama-3-8B-Instruct",
+ ... messages=[{"role": "user", "content": "What is the capital of France?"}],
+ ... extra_body={"safety_model": "Meta-Llama/Llama-Guard-7b"},
+ ... )
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="sambanova", # Use Sambanova provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> client.chat_completion(
+ ... model="meta-llama/Meta-Llama-3-8B-Instruct",
+ ... messages=[{"role": "user", "content": "What is the capital of France?"}],
+ ... )
+ ```
+
+ Example using Image + Text as input:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+
+ # provide a remote URL
+ >>> image_url ="https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg"
+ # or a base64-encoded image
+ >>> image_path = "/path/to/image.jpeg"
+ >>> with open(image_path, "rb") as f:
+ ... base64_image = base64.b64encode(f.read()).decode("utf-8")
+ >>> image_url = f"data:image/jpeg;base64,{base64_image}"
+
+ >>> client = AsyncInferenceClient("meta-llama/Llama-3.2-11B-Vision-Instruct")
+ >>> output = await client.chat.completions.create(
+ ... messages=[
+ ... {
+ ... "role": "user",
+ ... "content": [
+ ... {
+ ... "type": "image_url",
+ ... "image_url": {"url": image_url},
+ ... },
+ ... {
+ ... "type": "text",
+ ... "text": "Describe this image in one sentence.",
+ ... },
+ ... ],
+ ... },
+ ... ],
+ ... )
+ >>> output
+ The image depicts the iconic Statue of Liberty situated in New York Harbor, New York, on a clear day.
+ ```
+
+ Example using tools:
+ ```py
+ # Must be run in an async context
+ >>> client = AsyncInferenceClient("meta-llama/Meta-Llama-3-70B-Instruct")
+ >>> messages = [
+ ... {
+ ... "role": "system",
+ ... "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.",
+ ... },
+ ... {
+ ... "role": "user",
+ ... "content": "What's the weather like the next 3 days in San Francisco, CA?",
+ ... },
+ ... ]
+ >>> tools = [
+ ... {
+ ... "type": "function",
+ ... "function": {
+ ... "name": "get_current_weather",
+ ... "description": "Get the current weather",
+ ... "parameters": {
+ ... "type": "object",
+ ... "properties": {
+ ... "location": {
+ ... "type": "string",
+ ... "description": "The city and state, e.g. San Francisco, CA",
+ ... },
+ ... "format": {
+ ... "type": "string",
+ ... "enum": ["celsius", "fahrenheit"],
+ ... "description": "The temperature unit to use. Infer this from the users location.",
+ ... },
+ ... },
+ ... "required": ["location", "format"],
+ ... },
+ ... },
+ ... },
+ ... {
+ ... "type": "function",
+ ... "function": {
+ ... "name": "get_n_day_weather_forecast",
+ ... "description": "Get an N-day weather forecast",
+ ... "parameters": {
+ ... "type": "object",
+ ... "properties": {
+ ... "location": {
+ ... "type": "string",
+ ... "description": "The city and state, e.g. San Francisco, CA",
+ ... },
+ ... "format": {
+ ... "type": "string",
+ ... "enum": ["celsius", "fahrenheit"],
+ ... "description": "The temperature unit to use. Infer this from the users location.",
+ ... },
+ ... "num_days": {
+ ... "type": "integer",
+ ... "description": "The number of days to forecast",
+ ... },
+ ... },
+ ... "required": ["location", "format", "num_days"],
+ ... },
+ ... },
+ ... },
+ ... ]
+
+ >>> response = await client.chat_completion(
+ ... model="meta-llama/Meta-Llama-3-70B-Instruct",
+ ... messages=messages,
+ ... tools=tools,
+ ... tool_choice="auto",
+ ... max_tokens=500,
+ ... )
+ >>> response.choices[0].message.tool_calls[0].function
+ ChatCompletionOutputFunctionDefinition(
+ arguments={
+ 'location': 'San Francisco, CA',
+ 'format': 'fahrenheit',
+ 'num_days': 3
+ },
+ name='get_n_day_weather_forecast',
+ description=None
+ )
+ ```
+
+ Example using response_format:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient("meta-llama/Meta-Llama-3-70B-Instruct")
+ >>> messages = [
+ ... {
+ ... "role": "user",
+ ... "content": "I saw a puppy a cat and a raccoon during my bike ride in the park. What did I saw and when?",
+ ... },
+ ... ]
+ >>> response_format = {
+ ... "type": "json",
+ ... "value": {
+ ... "properties": {
+ ... "location": {"type": "string"},
+ ... "activity": {"type": "string"},
+ ... "animals_seen": {"type": "integer", "minimum": 1, "maximum": 5},
+ ... "animals": {"type": "array", "items": {"type": "string"}},
+ ... },
+ ... "required": ["location", "activity", "animals_seen", "animals"],
+ ... },
+ ... }
+ >>> response = await client.chat_completion(
+ ... messages=messages,
+ ... response_format=response_format,
+ ... max_tokens=500,
+ )
+ >>> response.choices[0].message.content
+ '{\n\n"activity": "bike ride",\n"animals": ["puppy", "cat", "raccoon"],\n"animals_seen": 3,\n"location": "park"}'
+ ```
+ """
+ # Get the provider helper
+ provider_helper = get_provider_helper(self.provider, task="conversational")
+
+ # Since `chat_completion(..., model=xxx)` is also a payload parameter for the server, we need to handle 'model' differently.
+ # `self.model` takes precedence over 'model' argument for building URL.
+ # `model` takes precedence for payload value.
+ model_id_or_url = self.model or model
+ payload_model = model or self.model
+
+ # Prepare the payload
+ parameters = {
+ "model": payload_model,
+ "frequency_penalty": frequency_penalty,
+ "logit_bias": logit_bias,
+ "logprobs": logprobs,
+ "max_tokens": max_tokens,
+ "n": n,
+ "presence_penalty": presence_penalty,
+ "response_format": response_format,
+ "seed": seed,
+ "stop": stop,
+ "temperature": temperature,
+ "tool_choice": tool_choice,
+ "tool_prompt": tool_prompt,
+ "tools": tools,
+ "top_logprobs": top_logprobs,
+ "top_p": top_p,
+ "stream": stream,
+ "stream_options": stream_options,
+ **(extra_body or {}),
+ }
+ request_parameters = provider_helper.prepare_request(
+ inputs=messages,
+ parameters=parameters,
+ headers=self.headers,
+ model=model_id_or_url,
+ api_key=self.token,
+ )
+ data = await self._inner_post(request_parameters, stream=stream)
+
+ if stream:
+ return _async_stream_chat_completion_response(data) # type: ignore[arg-type]
+
+ return ChatCompletionOutput.parse_obj_as_instance(data) # type: ignore[arg-type]
+
+ async def document_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ doc_stride: Optional[int] = None,
+ handle_impossible_answer: Optional[bool] = None,
+ lang: Optional[str] = None,
+ max_answer_len: Optional[int] = None,
+ max_question_len: Optional[int] = None,
+ max_seq_len: Optional[int] = None,
+ top_k: Optional[int] = None,
+ word_boxes: Optional[List[Union[List[float], str]]] = None,
+ ) -> List[DocumentQuestionAnsweringOutputElement]:
+ """
+ Answer questions on document images.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the document question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended document question answering model will be used.
+ Defaults to None.
+ doc_stride (`int`, *optional*):
+ If the words in the document are too long to fit with the question for the model, it will be split in
+ several chunks with some overlap. This argument controls the size of that overlap.
+ handle_impossible_answer (`bool`, *optional*):
+ Whether to accept impossible as an answer
+ lang (`str`, *optional*):
+ Language to use while running OCR. Defaults to english.
+ max_answer_len (`int`, *optional*):
+ The maximum length of predicted answers (e.g., only answers with a shorter length are considered).
+ max_question_len (`int`, *optional*):
+ The maximum length of the question after tokenization. It will be truncated if needed.
+ max_seq_len (`int`, *optional*):
+ The maximum length of the total sentence (context + question) in tokens of each chunk passed to the
+ model. The context will be split in several chunks (using doc_stride as overlap) if needed.
+ top_k (`int`, *optional*):
+ The number of answers to return (will be chosen by order of likelihood). Can return less than top_k
+ answers if there are not enough options available within the context.
+ word_boxes (`List[Union[List[float], str`, *optional*):
+ A list of words and bounding boxes (normalized 0->1000). If provided, the inference will skip the OCR
+ step and use the provided bounding boxes instead.
+ Returns:
+ `List[DocumentQuestionAnsweringOutputElement]`: a list of [`DocumentQuestionAnsweringOutputElement`] items containing the predicted label, associated probability, word ids, and page number.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.document_question_answering(image="https://huggingface.co/spaces/impira/docquery/resolve/2359223c1837a7587402bda0f2643382a6eefeab/invoice.png", question="What is the invoice number?")
+ [DocumentQuestionAnsweringOutputElement(answer='us-001', end=16, score=0.9999666213989258, start=16)]
+ ```
+ """
+ inputs: Dict[str, Any] = {"question": question, "image": _b64_encode(image)}
+ provider_helper = get_provider_helper(self.provider, task="document-question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=inputs,
+ parameters={
+ "doc_stride": doc_stride,
+ "handle_impossible_answer": handle_impossible_answer,
+ "lang": lang,
+ "max_answer_len": max_answer_len,
+ "max_question_len": max_question_len,
+ "max_seq_len": max_seq_len,
+ "top_k": top_k,
+ "word_boxes": word_boxes,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return DocumentQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ async def feature_extraction(
+ self,
+ text: str,
+ *,
+ normalize: Optional[bool] = None,
+ prompt_name: Optional[str] = None,
+ truncate: Optional[bool] = None,
+ truncation_direction: Optional[Literal["Left", "Right"]] = None,
+ model: Optional[str] = None,
+ ) -> "np.ndarray":
+ """
+ Generate embeddings for a given text.
+
+ Args:
+ text (`str`):
+ The text to embed.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+ normalize (`bool`, *optional*):
+ Whether to normalize the embeddings or not.
+ Only available on server powered by Text-Embedding-Inference.
+ prompt_name (`str`, *optional*):
+ The name of the prompt that should be used by for encoding. If not set, no prompt will be applied.
+ Must be a key in the `Sentence Transformers` configuration `prompts` dictionary.
+ For example if ``prompt_name`` is "query" and the ``prompts`` is {"query": "query: ",...},
+ then the sentence "What is the capital of France?" will be encoded as "query: What is the capital of France?"
+ because the prompt text will be prepended before any text to encode.
+ truncate (`bool`, *optional*):
+ Whether to truncate the embeddings or not.
+ Only available on server powered by Text-Embedding-Inference.
+ truncation_direction (`Literal["Left", "Right"]`, *optional*):
+ Which side of the input should be truncated when `truncate=True` is passed.
+
+ Returns:
+ `np.ndarray`: The embedding representing the input text as a float32 numpy array.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.feature_extraction("Hi, who are you?")
+ array([[ 2.424802 , 2.93384 , 1.1750331 , ..., 1.240499, -0.13776633, -0.7889173 ],
+ [-0.42943227, -0.6364878 , -1.693462 , ..., 0.41978157, -2.4336355 , 0.6162071 ],
+ ...,
+ [ 0.28552425, -0.928395 , -1.2077185 , ..., 0.76810825, -2.1069427 , 0.6236161 ]], dtype=float32)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="feature-extraction")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "normalize": normalize,
+ "prompt_name": prompt_name,
+ "truncate": truncate,
+ "truncation_direction": truncation_direction,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ np = _import_numpy()
+ return np.array(_bytes_to_dict(response), dtype="float32")
+
+ async def fill_mask(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ targets: Optional[List[str]] = None,
+ top_k: Optional[int] = None,
+ ) -> List[FillMaskOutputElement]:
+ """
+ Fill in a hole with a missing word (token to be precise).
+
+ Args:
+ text (`str`):
+ a string to be filled from, must contain the [MASK] token (check model card for exact name of the mask).
+ model (`str`, *optional*):
+ The model to use for the fill mask task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended fill mask model will be used.
+ targets (`List[str`, *optional*):
+ When passed, the model will limit the scores to the passed targets instead of looking up in the whole
+ vocabulary. If the provided targets are not in the model vocab, they will be tokenized and the first
+ resulting token will be used (with a warning, and that might be slower).
+ top_k (`int`, *optional*):
+ When passed, overrides the number of predictions to return.
+ Returns:
+ `List[FillMaskOutputElement]`: a list of [`FillMaskOutputElement`] items containing the predicted label, associated
+ probability, token reference, and completed text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.fill_mask("The goal of life is .")
+ [
+ FillMaskOutputElement(score=0.06897063553333282, token=11098, token_str=' happiness', sequence='The goal of life is happiness.'),
+ FillMaskOutputElement(score=0.06554922461509705, token=45075, token_str=' immortality', sequence='The goal of life is immortality.')
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="fill-mask")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={"targets": targets, "top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return FillMaskOutputElement.parse_obj_as_list(response)
+
+ async def image_classification(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ function_to_apply: Optional["ImageClassificationOutputTransform"] = None,
+ top_k: Optional[int] = None,
+ ) -> List[ImageClassificationOutputElement]:
+ """
+ Perform image classification on the given image using the specified model.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to classify. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image classification. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image classification will be used.
+ function_to_apply (`"ImageClassificationOutputTransform"`, *optional*):
+ The function to apply to the model outputs in order to retrieve the scores.
+ top_k (`int`, *optional*):
+ When specified, limits the output to the top K most probable classes.
+ Returns:
+ `List[ImageClassificationOutputElement]`: a list of [`ImageClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.image_classification("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ [ImageClassificationOutputElement(label='Blenheim spaniel', score=0.9779096841812134), ...]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="image-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={"function_to_apply": function_to_apply, "top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return ImageClassificationOutputElement.parse_obj_as_list(response)
+
+ async def image_segmentation(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ mask_threshold: Optional[float] = None,
+ overlap_mask_area_threshold: Optional[float] = None,
+ subtask: Optional["ImageSegmentationSubtask"] = None,
+ threshold: Optional[float] = None,
+ ) -> List[ImageSegmentationOutputElement]:
+ """
+ Perform image segmentation on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to segment. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image segmentation. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image segmentation will be used.
+ mask_threshold (`float`, *optional*):
+ Threshold to use when turning the predicted masks into binary values.
+ overlap_mask_area_threshold (`float`, *optional*):
+ Mask overlap threshold to eliminate small, disconnected segments.
+ subtask (`"ImageSegmentationSubtask"`, *optional*):
+ Segmentation task to be performed, depending on model capabilities.
+ threshold (`float`, *optional*):
+ Probability threshold to filter out predicted masks.
+ Returns:
+ `List[ImageSegmentationOutputElement]`: A list of [`ImageSegmentationOutputElement`] items containing the segmented masks and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.image_segmentation("cat.jpg")
+ [ImageSegmentationOutputElement(score=0.989008, label='LABEL_184', mask=), ...]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="audio-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={
+ "mask_threshold": mask_threshold,
+ "overlap_mask_area_threshold": overlap_mask_area_threshold,
+ "subtask": subtask,
+ "threshold": threshold,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ output = ImageSegmentationOutputElement.parse_obj_as_list(response)
+ for item in output:
+ item.mask = _b64_to_image(item.mask) # type: ignore [assignment]
+ return output
+
+ async def image_to_image(
+ self,
+ image: ContentT,
+ prompt: Optional[str] = None,
+ *,
+ negative_prompt: Optional[str] = None,
+ num_inference_steps: Optional[int] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ target_size: Optional[ImageToImageTargetSize] = None,
+ **kwargs,
+ ) -> "Image":
+ """
+ Perform image-to-image translation using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for translation. It can be raw bytes, an image file, or a URL to an online image.
+ prompt (`str`, *optional*):
+ The text prompt to guide the image generation.
+ negative_prompt (`str`, *optional*):
+ One prompt to guide what NOT to include in image generation.
+ num_inference_steps (`int`, *optional*):
+ For diffusion models. The number of denoising steps. More denoising steps usually lead to a higher
+ quality image at the expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ For diffusion models. A higher guidance scale value encourages the model to generate images closely
+ linked to the text prompt at the expense of lower image quality.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+ target_size (`ImageToImageTargetSize`, *optional*):
+ The size in pixel of the output image.
+
+ Returns:
+ `Image`: The translated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> image = await client.image_to_image("cat.jpg", prompt="turn the cat into a tiger")
+ >>> image.save("tiger.jpg")
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="image-to-image")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={
+ "prompt": prompt,
+ "negative_prompt": negative_prompt,
+ "target_size": target_size,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ **kwargs,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return _bytes_to_image(response)
+
+ async def image_to_text(self, image: ContentT, *, model: Optional[str] = None) -> ImageToTextOutput:
+ """
+ Takes an input image and return text.
+
+ Models can have very different outputs depending on your use case (image captioning, optical character recognition
+ (OCR), Pix2Struct, etc). Please have a look to the model card to learn more about a model's specificities.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image..
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ [`ImageToTextOutput`]: The generated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.image_to_text("cat.jpg")
+ 'a cat standing in a grassy field '
+ >>> await client.image_to_text("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ 'a dog laying on the grass next to a flower pot '
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="image-to-text")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ output = ImageToTextOutput.parse_obj(response)
+ return output[0] if isinstance(output, list) else output
+
+ async def object_detection(
+ self, image: ContentT, *, model: Optional[str] = None, threshold: Optional[float] = None
+ ) -> List[ObjectDetectionOutputElement]:
+ """
+ Perform object detection on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to detect objects on. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for object detection. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for object detection (DETR) will be used.
+ threshold (`float`, *optional*):
+ The probability necessary to make a prediction.
+ Returns:
+ `List[ObjectDetectionOutputElement]`: A list of [`ObjectDetectionOutputElement`] items containing the bounding boxes and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If the request output is not a List.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.object_detection("people.jpg")
+ [ObjectDetectionOutputElement(score=0.9486683011054993, label='person', box=ObjectDetectionBoundingBox(xmin=59, ymin=39, xmax=420, ymax=510)), ...]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="object-detection")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={"threshold": threshold},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return ObjectDetectionOutputElement.parse_obj_as_list(response)
+
+ async def question_answering(
+ self,
+ question: str,
+ context: str,
+ *,
+ model: Optional[str] = None,
+ align_to_words: Optional[bool] = None,
+ doc_stride: Optional[int] = None,
+ handle_impossible_answer: Optional[bool] = None,
+ max_answer_len: Optional[int] = None,
+ max_question_len: Optional[int] = None,
+ max_seq_len: Optional[int] = None,
+ top_k: Optional[int] = None,
+ ) -> Union[QuestionAnsweringOutputElement, List[QuestionAnsweringOutputElement]]:
+ """
+ Retrieve the answer to a question from a given text.
+
+ Args:
+ question (`str`):
+ Question to be answered.
+ context (`str`):
+ The context of the question.
+ model (`str`):
+ The model to use for the question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint.
+ align_to_words (`bool`, *optional*):
+ Attempts to align the answer to real words. Improves quality on space separated languages. Might hurt
+ on non-space-separated languages (like Japanese or Chinese)
+ doc_stride (`int`, *optional*):
+ If the context is too long to fit with the question for the model, it will be split in several chunks
+ with some overlap. This argument controls the size of that overlap.
+ handle_impossible_answer (`bool`, *optional*):
+ Whether to accept impossible as an answer.
+ max_answer_len (`int`, *optional*):
+ The maximum length of predicted answers (e.g., only answers with a shorter length are considered).
+ max_question_len (`int`, *optional*):
+ The maximum length of the question after tokenization. It will be truncated if needed.
+ max_seq_len (`int`, *optional*):
+ The maximum length of the total sentence (context + question) in tokens of each chunk passed to the
+ model. The context will be split in several chunks (using docStride as overlap) if needed.
+ top_k (`int`, *optional*):
+ The number of answers to return (will be chosen by order of likelihood). Note that we return less than
+ topk answers if there are not enough options available within the context.
+
+ Returns:
+ Union[`QuestionAnsweringOutputElement`, List[`QuestionAnsweringOutputElement`]]:
+ When top_k is 1 or not provided, it returns a single `QuestionAnsweringOutputElement`.
+ When top_k is greater than 1, it returns a list of `QuestionAnsweringOutputElement`.
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.question_answering(question="What's my name?", context="My name is Clara and I live in Berkeley.")
+ QuestionAnsweringOutputElement(answer='Clara', end=16, score=0.9326565265655518, start=11)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={
+ "align_to_words": align_to_words,
+ "doc_stride": doc_stride,
+ "handle_impossible_answer": handle_impossible_answer,
+ "max_answer_len": max_answer_len,
+ "max_question_len": max_question_len,
+ "max_seq_len": max_seq_len,
+ "top_k": top_k,
+ },
+ extra_payload={"question": question, "context": context},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ # Parse the response as a single `QuestionAnsweringOutputElement` when top_k is 1 or not provided, or a list of `QuestionAnsweringOutputElement` to ensure backward compatibility.
+ output = QuestionAnsweringOutputElement.parse_obj(response)
+ return output
+
+ async def sentence_similarity(
+ self, sentence: str, other_sentences: List[str], *, model: Optional[str] = None
+ ) -> List[float]:
+ """
+ Compute the semantic similarity between a sentence and a list of other sentences by comparing their embeddings.
+
+ Args:
+ sentence (`str`):
+ The main sentence to compare to others.
+ other_sentences (`List[str]`):
+ The list of sentences to compare to.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[float]`: The embedding representing the input text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.sentence_similarity(
+ ... "Machine learning is so easy.",
+ ... other_sentences=[
+ ... "Deep learning is so straightforward.",
+ ... "This is so difficult, like rocket science.",
+ ... "I can't believe how much I struggled with this.",
+ ... ],
+ ... )
+ [0.7785726189613342, 0.45876261591911316, 0.2906220555305481]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="sentence-similarity")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={},
+ extra_payload={"source_sentence": sentence, "sentences": other_sentences},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return _bytes_to_list(response)
+
+ async def summarization(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ clean_up_tokenization_spaces: Optional[bool] = None,
+ generate_parameters: Optional[Dict[str, Any]] = None,
+ truncation: Optional["SummarizationTruncationStrategy"] = None,
+ ) -> SummarizationOutput:
+ """
+ Generate a summary of a given text using a specified model.
+
+ Args:
+ text (`str`):
+ The input text to summarize.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for summarization will be used.
+ clean_up_tokenization_spaces (`bool`, *optional*):
+ Whether to clean up the potential extra spaces in the text output.
+ generate_parameters (`Dict[str, Any]`, *optional*):
+ Additional parametrization of the text generation algorithm.
+ truncation (`"SummarizationTruncationStrategy"`, *optional*):
+ The truncation strategy to use.
+ Returns:
+ [`SummarizationOutput`]: The generated summary text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.summarization("The Eiffel tower...")
+ SummarizationOutput(generated_text="The Eiffel tower is one of the most famous landmarks in the world....")
+ ```
+ """
+ parameters = {
+ "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
+ "generate_parameters": generate_parameters,
+ "truncation": truncation,
+ }
+ provider_helper = get_provider_helper(self.provider, task="summarization")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters=parameters,
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return SummarizationOutput.parse_obj_as_list(response)[0]
+
+ async def table_question_answering(
+ self,
+ table: Dict[str, Any],
+ query: str,
+ *,
+ model: Optional[str] = None,
+ padding: Optional["Padding"] = None,
+ sequential: Optional[bool] = None,
+ truncation: Optional[bool] = None,
+ ) -> TableQuestionAnsweringOutputElement:
+ """
+ Retrieve the answer to a question from information given in a table.
+
+ Args:
+ table (`str`):
+ A table of data represented as a dict of lists where entries are headers and the lists are all the
+ values, all lists must have the same size.
+ query (`str`):
+ The query in plain text that you want to ask the table.
+ model (`str`):
+ The model to use for the table-question-answering task. Can be a model ID hosted on the Hugging Face
+ Hub or a URL to a deployed Inference Endpoint.
+ padding (`"Padding"`, *optional*):
+ Activates and controls padding.
+ sequential (`bool`, *optional*):
+ Whether to do inference sequentially or as a batch. Batching is faster, but models like SQA require the
+ inference to be done sequentially to extract relations within sequences, given their conversational
+ nature.
+ truncation (`bool`, *optional*):
+ Activates and controls truncation.
+
+ Returns:
+ [`TableQuestionAnsweringOutputElement`]: a table question answering output containing the answer, coordinates, cells and the aggregator used.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> query = "How many stars does the transformers repository have?"
+ >>> table = {"Repository": ["Transformers", "Datasets", "Tokenizers"], "Stars": ["36542", "4512", "3934"]}
+ >>> await client.table_question_answering(table, query, model="google/tapas-base-finetuned-wtq")
+ TableQuestionAnsweringOutputElement(answer='36542', coordinates=[[0, 1]], cells=['36542'], aggregator='AVERAGE')
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="table-question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={"model": model, "padding": padding, "sequential": sequential, "truncation": truncation},
+ extra_payload={"query": query, "table": table},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return TableQuestionAnsweringOutputElement.parse_obj_as_instance(response)
+
+ async def tabular_classification(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[str]:
+ """
+ Classifying a target category (a group) based on a set of attributes.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes to classify.
+ model (`str`, *optional*):
+ The model to use for the tabular classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of labels, one per row in the initial table.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> table = {
+ ... "fixed_acidity": ["7.4", "7.8", "10.3"],
+ ... "volatile_acidity": ["0.7", "0.88", "0.32"],
+ ... "citric_acid": ["0", "0", "0.45"],
+ ... "residual_sugar": ["1.9", "2.6", "6.4"],
+ ... "chlorides": ["0.076", "0.098", "0.073"],
+ ... "free_sulfur_dioxide": ["11", "25", "5"],
+ ... "total_sulfur_dioxide": ["34", "67", "13"],
+ ... "density": ["0.9978", "0.9968", "0.9976"],
+ ... "pH": ["3.51", "3.2", "3.23"],
+ ... "sulphates": ["0.56", "0.68", "0.82"],
+ ... "alcohol": ["9.4", "9.8", "12.6"],
+ ... }
+ >>> await client.tabular_classification(table=table, model="julien-c/wine-quality")
+ ["5", "5", "5"]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="tabular-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ extra_payload={"table": table},
+ parameters={},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return _bytes_to_list(response)
+
+ async def tabular_regression(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[float]:
+ """
+ Predicting a numerical target value given a set of attributes/features in a table.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes stored in a table. The attributes used to predict the target can be both numerical and categorical.
+ model (`str`, *optional*):
+ The model to use for the tabular regression task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular regression model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of predicted numerical target values.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> table = {
+ ... "Height": ["11.52", "12.48", "12.3778"],
+ ... "Length1": ["23.2", "24", "23.9"],
+ ... "Length2": ["25.4", "26.3", "26.5"],
+ ... "Length3": ["30", "31.2", "31.1"],
+ ... "Species": ["Bream", "Bream", "Bream"],
+ ... "Width": ["4.02", "4.3056", "4.6961"],
+ ... }
+ >>> await client.tabular_regression(table, model="scikit-learn/Fish-Weight")
+ [110, 120, 130]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="tabular-regression")
+ request_parameters = provider_helper.prepare_request(
+ inputs=None,
+ parameters={},
+ extra_payload={"table": table},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return _bytes_to_list(response)
+
+ async def text_classification(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ top_k: Optional[int] = None,
+ function_to_apply: Optional["TextClassificationOutputTransform"] = None,
+ ) -> List[TextClassificationOutputElement]:
+ """
+ Perform text classification (e.g. sentiment-analysis) on the given text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the text classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended text classification model will be used.
+ Defaults to None.
+ top_k (`int`, *optional*):
+ When specified, limits the output to the top K most probable classes.
+ function_to_apply (`"TextClassificationOutputTransform"`, *optional*):
+ The function to apply to the model outputs in order to retrieve the scores.
+
+ Returns:
+ `List[TextClassificationOutputElement]`: a list of [`TextClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.text_classification("I like you")
+ [
+ TextClassificationOutputElement(label='POSITIVE', score=0.9998695850372314),
+ TextClassificationOutputElement(label='NEGATIVE', score=0.0001304351753788069),
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "function_to_apply": function_to_apply,
+ "top_k": top_k,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return TextClassificationOutputElement.parse_obj_as_list(response)[0] # type: ignore [return-value]
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> str: ...
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> TextGenerationOutput: ...
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> AsyncIterable[str]: ...
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> AsyncIterable[TextGenerationStreamOutput]: ...
+
+ @overload
+ async def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: bool = ...,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> Union[TextGenerationOutput, AsyncIterable[TextGenerationStreamOutput]]: ...
+
+ async def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: bool = False,
+ stream: bool = False,
+ model: Optional[str] = None,
+ # Parameters from `TextGenerationInputGenerateParameters` (maintained manually)
+ adapter_id: Optional[str] = None,
+ best_of: Optional[int] = None,
+ decoder_input_details: Optional[bool] = None,
+ do_sample: Optional[bool] = False, # Manual default value
+ frequency_penalty: Optional[float] = None,
+ grammar: Optional[TextGenerationInputGrammarType] = None,
+ max_new_tokens: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: Optional[bool] = False, # Manual default value
+ seed: Optional[int] = None,
+ stop: Optional[List[str]] = None,
+ stop_sequences: Optional[List[str]] = None, # Deprecated, use `stop` instead
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_n_tokens: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: Optional[bool] = None,
+ ) -> Union[str, TextGenerationOutput, AsyncIterable[str], AsyncIterable[TextGenerationStreamOutput]]:
+ """
+ Given a prompt, generate the following text.
+
+
+
+ If you want to generate a response from chat messages, you should use the [`InferenceClient.chat_completion`] method.
+ It accepts a list of messages instead of a single text prompt and handles the chat templating for you.
+
+
+
+ Args:
+ prompt (`str`):
+ Input text.
+ details (`bool`, *optional*):
+ By default, text_generation returns a string. Pass `details=True` if you want a detailed output (tokens,
+ probabilities, seed, finish reason, etc.). Only available for models running on with the
+ `text-generation-inference` backend.
+ stream (`bool`, *optional*):
+ By default, text_generation returns the full generated text. Pass `stream=True` if you want a stream of
+ tokens to be returned. Only available for models running on with the `text-generation-inference`
+ backend.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+ adapter_id (`str`, *optional*):
+ Lora adapter id.
+ best_of (`int`, *optional*):
+ Generate best_of sequences and return the one if the highest token logprobs.
+ decoder_input_details (`bool`, *optional*):
+ Return the decoder input token logprobs and ids. You must set `details=True` as well for it to be taken
+ into account. Defaults to `False`.
+ do_sample (`bool`, *optional*):
+ Activate logits sampling
+ frequency_penalty (`float`, *optional*):
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in
+ the text so far, decreasing the model's likelihood to repeat the same line verbatim.
+ grammar ([`TextGenerationInputGrammarType`], *optional*):
+ Grammar constraints. Can be either a JSONSchema or a regex.
+ max_new_tokens (`int`, *optional*):
+ Maximum number of generated tokens. Defaults to 100.
+ repetition_penalty (`float`, *optional*):
+ The parameter for repetition penalty. 1.0 means no penalty. See [this
+ paper](https://arxiv.org/pdf/1909.05858.pdf) for more details.
+ return_full_text (`bool`, *optional*):
+ Whether to prepend the prompt to the generated text
+ seed (`int`, *optional*):
+ Random sampling seed
+ stop (`List[str]`, *optional*):
+ Stop generating tokens if a member of `stop` is generated.
+ stop_sequences (`List[str]`, *optional*):
+ Deprecated argument. Use `stop` instead.
+ temperature (`float`, *optional*):
+ The value used to module the logits distribution.
+ top_n_tokens (`int`, *optional*):
+ Return information about the `top_n_tokens` most likely tokens at each generation step, instead of
+ just the sampled token.
+ top_k (`int`, *optional`):
+ The number of highest probability vocabulary tokens to keep for top-k-filtering.
+ top_p (`float`, *optional`):
+ If set to < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or
+ higher are kept for generation.
+ truncate (`int`, *optional`):
+ Truncate inputs tokens to the given size.
+ typical_p (`float`, *optional`):
+ Typical Decoding mass
+ See [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666) for more information
+ watermark (`bool`, *optional`):
+ Watermarking with [A Watermark for Large Language Models](https://arxiv.org/abs/2301.10226)
+
+ Returns:
+ `Union[str, TextGenerationOutput, Iterable[str], Iterable[TextGenerationStreamOutput]]`:
+ Generated text returned from the server:
+ - if `stream=False` and `details=False`, the generated text is returned as a `str` (default)
+ - if `stream=True` and `details=False`, the generated text is returned token by token as a `Iterable[str]`
+ - if `stream=False` and `details=True`, the generated text is returned with more details as a [`~huggingface_hub.TextGenerationOutput`]
+ - if `details=True` and `stream=True`, the generated text is returned token by token as a iterable of [`~huggingface_hub.TextGenerationStreamOutput`]
+
+ Raises:
+ `ValidationError`:
+ If input values are not valid. No HTTP call is made to the server.
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ # Case 1: generate text
+ >>> await client.text_generation("The huggingface_hub library is ", max_new_tokens=12)
+ '100% open source and built to be easy to use.'
+
+ # Case 2: iterate over the generated tokens. Useful for large generation.
+ >>> async for token in await client.text_generation("The huggingface_hub library is ", max_new_tokens=12, stream=True):
+ ... print(token)
+ 100
+ %
+ open
+ source
+ and
+ built
+ to
+ be
+ easy
+ to
+ use
+ .
+
+ # Case 3: get more details about the generation process.
+ >>> await client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True)
+ TextGenerationOutput(
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationDetails(
+ finish_reason='length',
+ generated_tokens=12,
+ seed=None,
+ prefill=[
+ TextGenerationPrefillOutputToken(id=487, text='The', logprob=None),
+ TextGenerationPrefillOutputToken(id=53789, text=' hugging', logprob=-13.171875),
+ (...)
+ TextGenerationPrefillOutputToken(id=204, text=' ', logprob=-7.0390625)
+ ],
+ tokens=[
+ TokenElement(id=1425, text='100', logprob=-1.0175781, special=False),
+ TokenElement(id=16, text='%', logprob=-0.0463562, special=False),
+ (...)
+ TokenElement(id=25, text='.', logprob=-0.5703125, special=False)
+ ],
+ best_of_sequences=None
+ )
+ )
+
+ # Case 4: iterate over the generated tokens with more details.
+ # Last object is more complete, containing the full generated text and the finish reason.
+ >>> async for details in await client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True, stream=True):
+ ... print(details)
+ ...
+ TextGenerationStreamOutput(token=TokenElement(id=1425, text='100', logprob=-1.0175781, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=16, text='%', logprob=-0.0463562, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1314, text=' open', logprob=-1.3359375, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3178, text=' source', logprob=-0.28100586, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=273, text=' and', logprob=-0.5961914, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3426, text=' built', logprob=-1.9423828, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-1.4121094, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=314, text=' be', logprob=-1.5224609, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1833, text=' easy', logprob=-2.1132812, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-0.08520508, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=745, text=' use', logprob=-0.39453125, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(
+ id=25,
+ text='.',
+ logprob=-0.5703125,
+ special=False),
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationStreamOutputStreamDetails(finish_reason='length', generated_tokens=12, seed=None)
+ )
+
+ # Case 5: generate constrained output using grammar
+ >>> response = await client.text_generation(
+ ... prompt="I saw a puppy a cat and a raccoon during my bike ride in the park",
+ ... model="HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1",
+ ... max_new_tokens=100,
+ ... repetition_penalty=1.3,
+ ... grammar={
+ ... "type": "json",
+ ... "value": {
+ ... "properties": {
+ ... "location": {"type": "string"},
+ ... "activity": {"type": "string"},
+ ... "animals_seen": {"type": "integer", "minimum": 1, "maximum": 5},
+ ... "animals": {"type": "array", "items": {"type": "string"}},
+ ... },
+ ... "required": ["location", "activity", "animals_seen", "animals"],
+ ... },
+ ... },
+ ... )
+ >>> json.loads(response)
+ {
+ "activity": "bike riding",
+ "animals": ["puppy", "cat", "raccoon"],
+ "animals_seen": 3,
+ "location": "park"
+ }
+ ```
+ """
+ if decoder_input_details and not details:
+ warnings.warn(
+ "`decoder_input_details=True` has been passed to the server but `details=False` is set meaning that"
+ " the output from the server will be truncated."
+ )
+ decoder_input_details = False
+
+ if stop_sequences is not None:
+ warnings.warn(
+ "`stop_sequences` is a deprecated argument for `text_generation` task"
+ " and will be removed in version '0.28.0'. Use `stop` instead.",
+ FutureWarning,
+ )
+ if stop is None:
+ stop = stop_sequences # use deprecated arg if provided
+
+ # Build payload
+ parameters = {
+ "adapter_id": adapter_id,
+ "best_of": best_of,
+ "decoder_input_details": decoder_input_details,
+ "details": details,
+ "do_sample": do_sample,
+ "frequency_penalty": frequency_penalty,
+ "grammar": grammar,
+ "max_new_tokens": max_new_tokens,
+ "repetition_penalty": repetition_penalty,
+ "return_full_text": return_full_text,
+ "seed": seed,
+ "stop": stop if stop is not None else [],
+ "temperature": temperature,
+ "top_k": top_k,
+ "top_n_tokens": top_n_tokens,
+ "top_p": top_p,
+ "truncate": truncate,
+ "typical_p": typical_p,
+ "watermark": watermark,
+ }
+
+ # Remove some parameters if not a TGI server
+ unsupported_kwargs = _get_unsupported_text_generation_kwargs(model)
+ if len(unsupported_kwargs) > 0:
+ # The server does not support some parameters
+ # => means it is not a TGI server
+ # => remove unsupported parameters and warn the user
+
+ ignored_parameters = []
+ for key in unsupported_kwargs:
+ if parameters.get(key):
+ ignored_parameters.append(key)
+ parameters.pop(key, None)
+ if len(ignored_parameters) > 0:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Ignoring following parameters:"
+ f" {', '.join(ignored_parameters)}.",
+ UserWarning,
+ )
+ if details:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Parameter `details=True` will"
+ " be ignored meaning only the generated text will be returned.",
+ UserWarning,
+ )
+ details = False
+ if stream:
+ raise ValueError(
+ "API endpoint/model for text-generation is not served via TGI. Cannot return output as a stream."
+ " Please pass `stream=False` as input."
+ )
+
+ provider_helper = get_provider_helper(self.provider, task="text-generation")
+ request_parameters = provider_helper.prepare_request(
+ inputs=prompt,
+ parameters=parameters,
+ extra_payload={"stream": stream},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+
+ # Handle errors separately for more precise error messages
+ try:
+ bytes_output = await self._inner_post(request_parameters, stream=stream)
+ except _import_aiohttp().ClientResponseError as e:
+ match = MODEL_KWARGS_NOT_USED_REGEX.search(e.response_error_payload["error"])
+ if e.status == 400 and match:
+ unused_params = [kwarg.strip("' ") for kwarg in match.group(1).split(",")]
+ _set_unsupported_text_generation_kwargs(model, unused_params)
+ return await self.text_generation( # type: ignore
+ prompt=prompt,
+ details=details,
+ stream=stream,
+ model=model or self.model,
+ adapter_id=adapter_id,
+ best_of=best_of,
+ decoder_input_details=decoder_input_details,
+ do_sample=do_sample,
+ frequency_penalty=frequency_penalty,
+ grammar=grammar,
+ max_new_tokens=max_new_tokens,
+ repetition_penalty=repetition_penalty,
+ return_full_text=return_full_text,
+ seed=seed,
+ stop=stop,
+ temperature=temperature,
+ top_k=top_k,
+ top_n_tokens=top_n_tokens,
+ top_p=top_p,
+ truncate=truncate,
+ typical_p=typical_p,
+ watermark=watermark,
+ )
+ raise_text_generation_error(e)
+
+ # Parse output
+ if stream:
+ return _async_stream_text_generation_response(bytes_output, details) # type: ignore
+
+ data = _bytes_to_dict(bytes_output) # type: ignore[arg-type]
+
+ # Data can be a single element (dict) or an iterable of dicts where we select the first element of.
+ if isinstance(data, list):
+ data = data[0]
+
+ return TextGenerationOutput.parse_obj_as_instance(data) if details else data["generated_text"]
+
+ async def text_to_image(
+ self,
+ prompt: str,
+ *,
+ negative_prompt: Optional[str] = None,
+ height: Optional[int] = None,
+ width: Optional[int] = None,
+ num_inference_steps: Optional[int] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ scheduler: Optional[str] = None,
+ seed: Optional[int] = None,
+ extra_body: Optional[Dict[str, Any]] = None,
+ ) -> "Image":
+ """
+ Generate an image based on a given text using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ prompt (`str`):
+ The prompt to generate an image from.
+ negative_prompt (`str`, *optional*):
+ One prompt to guide what NOT to include in image generation.
+ height (`int`, *optional*):
+ The height in pixels of the output image
+ width (`int`, *optional*):
+ The width in pixels of the output image
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
+ expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ A higher guidance scale value encourages the model to generate images closely linked to the text
+ prompt, but values too high may cause saturation and other artifacts.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended text-to-image model will be used.
+ Defaults to None.
+ scheduler (`str`, *optional*):
+ Override the scheduler with a compatible one.
+ seed (`int`, *optional*):
+ Seed for the random number generator.
+ extra_body (`Dict[str, Any]`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+
+ Returns:
+ `Image`: The generated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ >>> image = await client.text_to_image("An astronaut riding a horse on the moon.")
+ >>> image.save("astronaut.png")
+
+ >>> image = await client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... negative_prompt="low resolution, blurry",
+ ... model="stabilityai/stable-diffusion-2-1",
+ ... )
+ >>> image.save("better_astronaut.png")
+ ```
+ Example using a third-party provider directly. Usage will be billed on your fal.ai account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="fal-ai", # Use fal.ai provider
+ ... api_key="fal-ai-api-key", # Pass your fal.ai API key
+ ... )
+ >>> image = client.text_to_image(
+ ... "A majestic lion in a fantasy forest",
+ ... model="black-forest-labs/FLUX.1-schnell",
+ ... )
+ >>> image.save("lion.png")
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Use replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> image = client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... model="black-forest-labs/FLUX.1-dev",
+ ... )
+ >>> image.save("astronaut.png")
+ ```
+
+ Example using Replicate provider with extra parameters
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Use replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> image = client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... model="black-forest-labs/FLUX.1-schnell",
+ ... extra_body={"output_quality": 100},
+ ... )
+ >>> image.save("astronaut.png")
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-to-image")
+ request_parameters = provider_helper.prepare_request(
+ inputs=prompt,
+ parameters={
+ "negative_prompt": negative_prompt,
+ "height": height,
+ "width": width,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ "scheduler": scheduler,
+ "seed": seed,
+ **(extra_body or {}),
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ response = provider_helper.get_response(response)
+ return _bytes_to_image(response)
+
+ async def text_to_video(
+ self,
+ prompt: str,
+ *,
+ model: Optional[str] = None,
+ guidance_scale: Optional[float] = None,
+ negative_prompt: Optional[List[str]] = None,
+ num_frames: Optional[float] = None,
+ num_inference_steps: Optional[int] = None,
+ seed: Optional[int] = None,
+ extra_body: Optional[Dict[str, Any]] = None,
+ ) -> bytes:
+ """
+ Generate a video based on a given text.
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ prompt (`str`):
+ The prompt to generate a video from.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended text-to-video model will be used.
+ Defaults to None.
+ guidance_scale (`float`, *optional*):
+ A higher guidance scale value encourages the model to generate videos closely linked to the text
+ prompt, but values too high may cause saturation and other artifacts.
+ negative_prompt (`List[str]`, *optional*):
+ One or several prompt to guide what NOT to include in video generation.
+ num_frames (`float`, *optional*):
+ The num_frames parameter determines how many video frames are generated.
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality video at the
+ expense of slower inference.
+ seed (`int`, *optional*):
+ Seed for the random number generator.
+ extra_body (`Dict[str, Any]`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+
+ Returns:
+ `bytes`: The generated video.
+
+ Example:
+
+ Example using a third-party provider directly. Usage will be billed on your fal.ai account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="fal-ai", # Using fal.ai provider
+ ... api_key="fal-ai-api-key", # Pass your fal.ai API key
+ ... )
+ >>> video = client.text_to_video(
+ ... "A majestic lion running in a fantasy forest",
+ ... model="tencent/HunyuanVideo",
+ ... )
+ >>> with open("lion.mp4", "wb") as file:
+ ... file.write(video)
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Using replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> video = client.text_to_video(
+ ... "A cat running in a park",
+ ... model="genmo/mochi-1-preview",
+ ... )
+ >>> with open("cat.mp4", "wb") as file:
+ ... file.write(video)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-to-video")
+ request_parameters = provider_helper.prepare_request(
+ inputs=prompt,
+ parameters={
+ "guidance_scale": guidance_scale,
+ "negative_prompt": negative_prompt,
+ "num_frames": num_frames,
+ "num_inference_steps": num_inference_steps,
+ "seed": seed,
+ **(extra_body or {}),
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ response = provider_helper.get_response(response)
+ return response
+
+ async def text_to_speech(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ do_sample: Optional[bool] = None,
+ early_stopping: Optional[Union[bool, "TextToSpeechEarlyStoppingEnum"]] = None,
+ epsilon_cutoff: Optional[float] = None,
+ eta_cutoff: Optional[float] = None,
+ max_length: Optional[int] = None,
+ max_new_tokens: Optional[int] = None,
+ min_length: Optional[int] = None,
+ min_new_tokens: Optional[int] = None,
+ num_beam_groups: Optional[int] = None,
+ num_beams: Optional[int] = None,
+ penalty_alpha: Optional[float] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ typical_p: Optional[float] = None,
+ use_cache: Optional[bool] = None,
+ extra_body: Optional[Dict[str, Any]] = None,
+ ) -> bytes:
+ """
+ Synthesize an audio of a voice pronouncing a given text.
+
+
+ You can pass provider-specific parameters to the model by using the `extra_body` argument.
+
+
+ Args:
+ text (`str`):
+ The text to synthesize.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended text-to-speech model will be used.
+ Defaults to None.
+ do_sample (`bool`, *optional*):
+ Whether to use sampling instead of greedy decoding when generating new tokens.
+ early_stopping (`Union[bool, "TextToSpeechEarlyStoppingEnum"]`, *optional*):
+ Controls the stopping condition for beam-based methods.
+ epsilon_cutoff (`float`, *optional*):
+ If set to float strictly between 0 and 1, only tokens with a conditional probability greater than
+ epsilon_cutoff will be sampled. In the paper, suggested values range from 3e-4 to 9e-4, depending on
+ the size of the model. See [Truncation Sampling as Language Model
+ Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ eta_cutoff (`float`, *optional*):
+ Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to float strictly
+ between 0 and 1, a token is only considered if it is greater than either eta_cutoff or sqrt(eta_cutoff)
+ * exp(-entropy(softmax(next_token_logits))). The latter term is intuitively the expected next token
+ probability, scaled by sqrt(eta_cutoff). In the paper, suggested values range from 3e-4 to 2e-3,
+ depending on the size of the model. See [Truncation Sampling as Language Model
+ Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ max_length (`int`, *optional*):
+ The maximum length (in tokens) of the generated text, including the input.
+ max_new_tokens (`int`, *optional*):
+ The maximum number of tokens to generate. Takes precedence over max_length.
+ min_length (`int`, *optional*):
+ The minimum length (in tokens) of the generated text, including the input.
+ min_new_tokens (`int`, *optional*):
+ The minimum number of tokens to generate. Takes precedence over min_length.
+ num_beam_groups (`int`, *optional*):
+ Number of groups to divide num_beams into in order to ensure diversity among different groups of beams.
+ See [this paper](https://hf.co/papers/1610.02424) for more details.
+ num_beams (`int`, *optional*):
+ Number of beams to use for beam search.
+ penalty_alpha (`float`, *optional*):
+ The value balances the model confidence and the degeneration penalty in contrastive search decoding.
+ temperature (`float`, *optional*):
+ The value used to modulate the next token probabilities.
+ top_k (`int`, *optional*):
+ The number of highest probability vocabulary tokens to keep for top-k-filtering.
+ top_p (`float`, *optional*):
+ If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to
+ top_p or higher are kept for generation.
+ typical_p (`float`, *optional*):
+ Local typicality measures how similar the conditional probability of predicting a target token next is
+ to the expected conditional probability of predicting a random token next, given the partial text
+ already generated. If set to float < 1, the smallest set of the most locally typical tokens with
+ probabilities that add up to typical_p or higher are kept for generation. See [this
+ paper](https://hf.co/papers/2202.00666) for more details.
+ use_cache (`bool`, *optional*):
+ Whether the model should use the past last key/values attentions to speed up decoding
+ extra_body (`Dict[str, Any]`, *optional*):
+ Additional provider-specific parameters to pass to the model. Refer to the provider's documentation
+ for supported parameters.
+ Returns:
+ `bytes`: The generated audio.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from pathlib import Path
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ >>> audio = await client.text_to_speech("Hello world")
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+
+ Example using a third-party provider directly. Usage will be billed on your Replicate account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate",
+ ... api_key="your-replicate-api-key", # Pass your Replicate API key directly
+ ... )
+ >>> audio = client.text_to_speech(
+ ... text="Hello world",
+ ... model="OuteAI/OuteTTS-0.3-500M",
+ ... )
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+
+ Example using a third-party provider through Hugging Face Routing. Usage will be billed on your Hugging Face account.
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate",
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> audio =client.text_to_speech(
+ ... text="Hello world",
+ ... model="OuteAI/OuteTTS-0.3-500M",
+ ... )
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+ Example using Replicate provider with extra parameters
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient(
+ ... provider="replicate", # Use replicate provider
+ ... api_key="hf_...", # Pass your HF token
+ ... )
+ >>> audio = client.text_to_speech(
+ ... "Hello, my name is Kororo, an awesome text-to-speech model.",
+ ... model="hexgrad/Kokoro-82M",
+ ... extra_body={"voice": "af_nicole"},
+ ... )
+ >>> Path("hello.flac").write_bytes(audio)
+ ```
+
+ Example music-gen using "YuE-s1-7B-anneal-en-cot" on fal.ai
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> lyrics = '''
+ ... [verse]
+ ... In the town where I was born
+ ... Lived a man who sailed to sea
+ ... And he told us of his life
+ ... In the land of submarines
+ ... So we sailed on to the sun
+ ... 'Til we found a sea of green
+ ... And we lived beneath the waves
+ ... In our yellow submarine
+
+ ... [chorus]
+ ... We all live in a yellow submarine
+ ... Yellow submarine, yellow submarine
+ ... We all live in a yellow submarine
+ ... Yellow submarine, yellow submarine
+ ... '''
+ >>> genres = "pavarotti-style tenor voice"
+ >>> client = InferenceClient(
+ ... provider="fal-ai",
+ ... model="m-a-p/YuE-s1-7B-anneal-en-cot",
+ ... api_key=...,
+ ... )
+ >>> audio = client.text_to_speech(lyrics, extra_body={"genres": genres})
+ >>> with open("output.mp3", "wb") as f:
+ ... f.write(audio)
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="text-to-speech")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "do_sample": do_sample,
+ "early_stopping": early_stopping,
+ "epsilon_cutoff": epsilon_cutoff,
+ "eta_cutoff": eta_cutoff,
+ "max_length": max_length,
+ "max_new_tokens": max_new_tokens,
+ "min_length": min_length,
+ "min_new_tokens": min_new_tokens,
+ "num_beam_groups": num_beam_groups,
+ "num_beams": num_beams,
+ "penalty_alpha": penalty_alpha,
+ "temperature": temperature,
+ "top_k": top_k,
+ "top_p": top_p,
+ "typical_p": typical_p,
+ "use_cache": use_cache,
+ **(extra_body or {}),
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ response = provider_helper.get_response(response)
+ return response
+
+ async def token_classification(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ aggregation_strategy: Optional["TokenClassificationAggregationStrategy"] = None,
+ ignore_labels: Optional[List[str]] = None,
+ stride: Optional[int] = None,
+ ) -> List[TokenClassificationOutputElement]:
+ """
+ Perform token classification on the given text.
+ Usually used for sentence parsing, either grammatical, or Named Entity Recognition (NER) to understand keywords contained within text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the token classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended token classification model will be used.
+ Defaults to None.
+ aggregation_strategy (`"TokenClassificationAggregationStrategy"`, *optional*):
+ The strategy used to fuse tokens based on model predictions
+ ignore_labels (`List[str`, *optional*):
+ A list of labels to ignore
+ stride (`int`, *optional*):
+ The number of overlapping tokens between chunks when splitting the input text.
+
+ Returns:
+ `List[TokenClassificationOutputElement]`: List of [`TokenClassificationOutputElement`] items containing the entity group, confidence score, word, start and end index.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.token_classification("My name is Sarah Jessica Parker but you can call me Jessica")
+ [
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9971321225166321,
+ word='Sarah Jessica Parker',
+ start=11,
+ end=31,
+ ),
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9773476123809814,
+ word='Jessica',
+ start=52,
+ end=59,
+ )
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="token-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "aggregation_strategy": aggregation_strategy,
+ "ignore_labels": ignore_labels,
+ "stride": stride,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return TokenClassificationOutputElement.parse_obj_as_list(response)
+
+ async def translation(
+ self,
+ text: str,
+ *,
+ model: Optional[str] = None,
+ src_lang: Optional[str] = None,
+ tgt_lang: Optional[str] = None,
+ clean_up_tokenization_spaces: Optional[bool] = None,
+ truncation: Optional["TranslationTruncationStrategy"] = None,
+ generate_parameters: Optional[Dict[str, Any]] = None,
+ ) -> TranslationOutput:
+ """
+ Convert text from one language to another.
+
+ Check out https://huggingface.co/tasks/translation for more information on how to choose the best model for
+ your specific use case. Source and target languages usually depend on the model.
+ However, it is possible to specify source and target languages for certain models. If you are working with one of these models,
+ you can use `src_lang` and `tgt_lang` arguments to pass the relevant information.
+
+ Args:
+ text (`str`):
+ A string to be translated.
+ model (`str`, *optional*):
+ The model to use for the translation task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended translation model will be used.
+ Defaults to None.
+ src_lang (`str`, *optional*):
+ The source language of the text. Required for models that can translate from multiple languages.
+ tgt_lang (`str`, *optional*):
+ Target language to translate to. Required for models that can translate to multiple languages.
+ clean_up_tokenization_spaces (`bool`, *optional*):
+ Whether to clean up the potential extra spaces in the text output.
+ truncation (`"TranslationTruncationStrategy"`, *optional*):
+ The truncation strategy to use.
+ generate_parameters (`Dict[str, Any]`, *optional*):
+ Additional parametrization of the text generation algorithm.
+
+ Returns:
+ [`TranslationOutput`]: The generated translated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If only one of the `src_lang` and `tgt_lang` arguments are provided.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.translation("My name is Wolfgang and I live in Berlin")
+ 'Mein Name ist Wolfgang und ich lebe in Berlin.'
+ >>> await client.translation("My name is Wolfgang and I live in Berlin", model="Helsinki-NLP/opus-mt-en-fr")
+ TranslationOutput(translation_text='Je m'appelle Wolfgang et je vis à Berlin.')
+ ```
+
+ Specifying languages:
+ ```py
+ >>> client.translation("My name is Sarah Jessica Parker but you can call me Jessica", model="facebook/mbart-large-50-many-to-many-mmt", src_lang="en_XX", tgt_lang="fr_XX")
+ "Mon nom est Sarah Jessica Parker mais vous pouvez m'appeler Jessica"
+ ```
+ """
+ # Throw error if only one of `src_lang` and `tgt_lang` was given
+ if src_lang is not None and tgt_lang is None:
+ raise ValueError("You cannot specify `src_lang` without specifying `tgt_lang`.")
+
+ if src_lang is None and tgt_lang is not None:
+ raise ValueError("You cannot specify `tgt_lang` without specifying `src_lang`.")
+
+ provider_helper = get_provider_helper(self.provider, task="translation")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "src_lang": src_lang,
+ "tgt_lang": tgt_lang,
+ "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
+ "truncation": truncation,
+ "generate_parameters": generate_parameters,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return TranslationOutput.parse_obj_as_list(response)[0]
+
+ async def visual_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ top_k: Optional[int] = None,
+ ) -> List[VisualQuestionAnsweringOutputElement]:
+ """
+ Answering open-ended questions based on an image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the visual question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended visual question answering model will be used.
+ Defaults to None.
+ top_k (`int`, *optional*):
+ The number of answers to return (will be chosen by order of likelihood). Note that we return less than
+ topk answers if there are not enough options available within the context.
+ Returns:
+ `List[VisualQuestionAnsweringOutputElement]`: a list of [`VisualQuestionAnsweringOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.visual_question_answering(
+ ... image="https://huggingface.co/datasets/mishig/sample_images/resolve/main/tiger.jpg",
+ ... question="What is the animal doing?"
+ ... )
+ [
+ VisualQuestionAnsweringOutputElement(score=0.778609573841095, answer='laying down'),
+ VisualQuestionAnsweringOutputElement(score=0.6957435607910156, answer='sitting'),
+ ]
+ ```
+ """
+ provider_helper = get_provider_helper(self.provider, task="visual-question-answering")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={"top_k": top_k},
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ extra_payload={"question": question, "image": _b64_encode(image)},
+ )
+ response = await self._inner_post(request_parameters)
+ return VisualQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ @_deprecate_arguments(
+ version="0.30.0",
+ deprecated_args=["labels"],
+ custom_message="`labels`has been renamed to `candidate_labels` and will be removed in huggingface_hub>=0.30.0.",
+ )
+ async def zero_shot_classification(
+ self,
+ text: str,
+ # temporarily keeping it optional for backward compatibility.
+ candidate_labels: List[str] = None, # type: ignore
+ *,
+ multi_label: Optional[bool] = False,
+ hypothesis_template: Optional[str] = None,
+ model: Optional[str] = None,
+ # deprecated argument
+ labels: List[str] = None, # type: ignore
+ ) -> List[ZeroShotClassificationOutputElement]:
+ """
+ Provide as input a text and a set of candidate labels to classify the input text.
+
+ Args:
+ text (`str`):
+ The input text to classify.
+ candidate_labels (`List[str]`):
+ The set of possible class labels to classify the text into.
+ labels (`List[str]`, *optional*):
+ (deprecated) List of strings. Each string is the verbalization of a possible label for the input text.
+ multi_label (`bool`, *optional*):
+ Whether multiple candidate labels can be true. If false, the scores are normalized such that the sum of
+ the label likelihoods for each sequence is 1. If true, the labels are considered independent and
+ probabilities are normalized for each candidate.
+ hypothesis_template (`str`, *optional*):
+ The sentence used in conjunction with `candidate_labels` to attempt the text classification by
+ replacing the placeholder with the candidate labels.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. If not provided, the default recommended zero-shot classification model will be used.
+
+
+ Returns:
+ `List[ZeroShotClassificationOutputElement]`: List of [`ZeroShotClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example with `multi_label=False`:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> text = (
+ ... "A new model offers an explanation for how the Galilean satellites formed around the solar system's"
+ ... "largest world. Konstantin Batygin did not set out to solve one of the solar system's most puzzling"
+ ... " mysteries when he went for a run up a hill in Nice, France."
+ ... )
+ >>> labels = ["space & cosmos", "scientific discovery", "microbiology", "robots", "archeology"]
+ >>> await client.zero_shot_classification(text, labels)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.7961668968200684),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.18570658564567566),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.00730885099619627),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.006258360575884581),
+ ZeroShotClassificationOutputElement(label='robots', score=0.004559356719255447),
+ ]
+ >>> await client.zero_shot_classification(text, labels, multi_label=True)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.9829297661781311),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.755190908908844),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.0005462635890580714),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.00047131875180639327),
+ ZeroShotClassificationOutputElement(label='robots', score=0.00030448526376858354),
+ ]
+ ```
+
+ Example with `multi_label=True` and a custom `hypothesis_template`:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.zero_shot_classification(
+ ... text="I really like our dinner and I'm very happy. I don't like the weather though.",
+ ... labels=["positive", "negative", "pessimistic", "optimistic"],
+ ... multi_label=True,
+ ... hypothesis_template="This text is {} towards the weather"
+ ... )
+ [
+ ZeroShotClassificationOutputElement(label='negative', score=0.9231801629066467),
+ ZeroShotClassificationOutputElement(label='pessimistic', score=0.8760990500450134),
+ ZeroShotClassificationOutputElement(label='optimistic', score=0.0008674879791215062),
+ ZeroShotClassificationOutputElement(label='positive', score=0.0005250611575320363)
+ ]
+ ```
+ """
+ # handle deprecation
+ if labels is not None:
+ if candidate_labels is not None:
+ raise ValueError(
+ "Cannot specify both `labels` and `candidate_labels`. Use `candidate_labels` instead."
+ )
+ candidate_labels = labels
+ elif candidate_labels is None:
+ raise ValueError("Must specify `candidate_labels`")
+
+ provider_helper = get_provider_helper(self.provider, task="zero-shot-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=text,
+ parameters={
+ "candidate_labels": candidate_labels,
+ "multi_label": multi_label,
+ "hypothesis_template": hypothesis_template,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ output = _bytes_to_dict(response)
+ return [
+ ZeroShotClassificationOutputElement.parse_obj_as_instance({"label": label, "score": score})
+ for label, score in zip(output["labels"], output["scores"])
+ ]
+
+ @_deprecate_arguments(
+ version="0.30.0",
+ deprecated_args=["labels"],
+ custom_message="`labels`has been renamed to `candidate_labels` and will be removed in huggingface_hub>=0.30.0.",
+ )
+ async def zero_shot_image_classification(
+ self,
+ image: ContentT,
+ # temporarily keeping it optional for backward compatibility.
+ candidate_labels: List[str] = None, # type: ignore
+ *,
+ model: Optional[str] = None,
+ hypothesis_template: Optional[str] = None,
+ # deprecated argument
+ labels: List[str] = None, # type: ignore
+ ) -> List[ZeroShotImageClassificationOutputElement]:
+ """
+ Provide input image and text labels to predict text labels for the image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image.
+ candidate_labels (`List[str]`):
+ The candidate labels for this image
+ labels (`List[str]`, *optional*):
+ (deprecated) List of string possible labels. There must be at least 2 labels.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. If not provided, the default recommended zero-shot image classification model will be used.
+ hypothesis_template (`str`, *optional*):
+ The sentence used in conjunction with `candidate_labels` to attempt the image classification by
+ replacing the placeholder with the candidate labels.
+
+ Returns:
+ `List[ZeroShotImageClassificationOutputElement]`: List of [`ZeroShotImageClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ >>> await client.zero_shot_image_classification(
+ ... "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg",
+ ... labels=["dog", "cat", "horse"],
+ ... )
+ [ZeroShotImageClassificationOutputElement(label='dog', score=0.956),...]
+ ```
+ """
+ # handle deprecation
+ if labels is not None:
+ if candidate_labels is not None:
+ raise ValueError(
+ "Cannot specify both `labels` and `candidate_labels`. Use `candidate_labels` instead."
+ )
+ candidate_labels = labels
+ elif candidate_labels is None:
+ raise ValueError("Must specify `candidate_labels`")
+ # Raise ValueError if input is less than 2 labels
+ if len(candidate_labels) < 2:
+ raise ValueError("You must specify at least 2 classes to compare.")
+
+ provider_helper = get_provider_helper(self.provider, task="zero-shot-image-classification")
+ request_parameters = provider_helper.prepare_request(
+ inputs=image,
+ parameters={
+ "candidate_labels": candidate_labels,
+ "hypothesis_template": hypothesis_template,
+ },
+ headers=self.headers,
+ model=model or self.model,
+ api_key=self.token,
+ )
+ response = await self._inner_post(request_parameters)
+ return ZeroShotImageClassificationOutputElement.parse_obj_as_list(response)
+
+ @_deprecate_method(
+ version="0.33.0",
+ message=(
+ "HF Inference API is getting revamped and will only support warm models in the future (no cold start allowed)."
+ " Use `HfApi.list_models(..., inference_provider='...')` to list warm models per provider."
+ ),
+ )
+ async def list_deployed_models(
+ self, frameworks: Union[None, str, Literal["all"], List[str]] = None
+ ) -> Dict[str, List[str]]:
+ """
+ List models deployed on the HF Serverless Inference API service.
+
+ This helper checks deployed models framework by framework. By default, it will check the 4 main frameworks that
+ are supported and account for 95% of the hosted models. However, if you want a complete list of models you can
+ specify `frameworks="all"` as input. Alternatively, if you know before-hand which framework you are interested
+ in, you can also restrict to search to this one (e.g. `frameworks="text-generation-inference"`). The more
+ frameworks are checked, the more time it will take.
+
+
+
+ This endpoint method does not return a live list of all models available for the HF Inference API service.
+ It searches over a cached list of models that were recently available and the list may not be up to date.
+ If you want to know the live status of a specific model, use [`~InferenceClient.get_model_status`].
+
+
+
+
+
+ This endpoint method is mostly useful for discoverability. If you already know which model you want to use and want to
+ check its availability, you can directly use [`~InferenceClient.get_model_status`].
+
+
+
+ Args:
+ frameworks (`Literal["all"]` or `List[str]` or `str`, *optional*):
+ The frameworks to filter on. By default only a subset of the available frameworks are tested. If set to
+ "all", all available frameworks will be tested. It is also possible to provide a single framework or a
+ custom set of frameworks to check.
+
+ Returns:
+ `Dict[str, List[str]]`: A dictionary mapping task names to a sorted list of model IDs.
+
+ Example:
+ ```py
+ # Must be run in an async contextthon
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ # Discover zero-shot-classification models currently deployed
+ >>> models = await client.list_deployed_models()
+ >>> models["zero-shot-classification"]
+ ['Narsil/deberta-large-mnli-zero-cls', 'facebook/bart-large-mnli', ...]
+
+ # List from only 1 framework
+ >>> await client.list_deployed_models("text-generation-inference")
+ {'text-generation': ['bigcode/starcoder', 'meta-llama/Llama-2-70b-chat-hf', ...], ...}
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Listing deployed models is not supported on '{self.provider}'.")
+
+ # Resolve which frameworks to check
+ if frameworks is None:
+ frameworks = constants.MAIN_INFERENCE_API_FRAMEWORKS
+ elif frameworks == "all":
+ frameworks = constants.ALL_INFERENCE_API_FRAMEWORKS
+ elif isinstance(frameworks, str):
+ frameworks = [frameworks]
+ frameworks = list(set(frameworks))
+
+ # Fetch them iteratively
+ models_by_task: Dict[str, List[str]] = {}
+
+ def _unpack_response(framework: str, items: List[Dict]) -> None:
+ for model in items:
+ if framework == "sentence-transformers":
+ # Model running with the `sentence-transformers` framework can work with both tasks even if not
+ # branded as such in the API response
+ models_by_task.setdefault("feature-extraction", []).append(model["model_id"])
+ models_by_task.setdefault("sentence-similarity", []).append(model["model_id"])
+ else:
+ models_by_task.setdefault(model["task"], []).append(model["model_id"])
+
+ for framework in frameworks:
+ response = get_session().get(
+ f"{constants.INFERENCE_ENDPOINT}/framework/{framework}", headers=build_hf_headers(token=self.token)
+ )
+ hf_raise_for_status(response)
+ _unpack_response(framework, response.json())
+
+ # Sort alphabetically for discoverability and return
+ for task, models in models_by_task.items():
+ models_by_task[task] = sorted(set(models), key=lambda x: x.lower())
+ return models_by_task
+
+ def _get_client_session(self, headers: Optional[Dict] = None) -> "ClientSession":
+ aiohttp = _import_aiohttp()
+ client_headers = self.headers.copy()
+ if headers is not None:
+ client_headers.update(headers)
+
+ # Return a new aiohttp ClientSession with correct settings.
+ session = aiohttp.ClientSession(
+ headers=client_headers,
+ cookies=self.cookies,
+ timeout=aiohttp.ClientTimeout(self.timeout),
+ trust_env=self.trust_env,
+ )
+
+ # Keep track of sessions to close them later
+ self._sessions[session] = set()
+
+ # Override the `._request` method to register responses to be closed
+ session._wrapped_request = session._request
+
+ async def _request(method, url, **kwargs):
+ response = await session._wrapped_request(method, url, **kwargs)
+ self._sessions[session].add(response)
+ return response
+
+ session._request = _request
+
+ # Override the 'close' method to
+ # 1. close ongoing responses
+ # 2. deregister the session when closed
+ session._close = session.close
+
+ async def close_session():
+ for response in self._sessions[session]:
+ response.close()
+ await session._close()
+ self._sessions.pop(session, None)
+
+ session.close = close_session
+ return session
+
+ async def get_endpoint_info(self, *, model: Optional[str] = None) -> Dict[str, Any]:
+ """
+ Get information about the deployed endpoint.
+
+ This endpoint is only available on endpoints powered by Text-Generation-Inference (TGI) or Text-Embedding-Inference (TEI).
+ Endpoints powered by `transformers` return an empty payload.
+
+ Args:
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `Dict[str, Any]`: Information about the endpoint.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient("meta-llama/Meta-Llama-3-70B-Instruct")
+ >>> await client.get_endpoint_info()
+ {
+ 'model_id': 'meta-llama/Meta-Llama-3-70B-Instruct',
+ 'model_sha': None,
+ 'model_dtype': 'torch.float16',
+ 'model_device_type': 'cuda',
+ 'model_pipeline_tag': None,
+ 'max_concurrent_requests': 128,
+ 'max_best_of': 2,
+ 'max_stop_sequences': 4,
+ 'max_input_length': 8191,
+ 'max_total_tokens': 8192,
+ 'waiting_served_ratio': 0.3,
+ 'max_batch_total_tokens': 1259392,
+ 'max_waiting_tokens': 20,
+ 'max_batch_size': None,
+ 'validation_workers': 32,
+ 'max_client_batch_size': 4,
+ 'version': '2.0.2',
+ 'sha': 'dccab72549635c7eb5ddb17f43f0b7cdff07c214',
+ 'docker_label': 'sha-dccab72'
+ }
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Getting endpoint info is not supported on '{self.provider}'.")
+
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if model.startswith(("http://", "https://")):
+ url = model.rstrip("/") + "/info"
+ else:
+ url = f"{constants.INFERENCE_ENDPOINT}/models/{model}/info"
+
+ async with self._get_client_session(headers=build_hf_headers(token=self.token)) as client:
+ response = await client.get(url, proxy=self.proxies)
+ response.raise_for_status()
+ return await response.json()
+
+ async def health_check(self, model: Optional[str] = None) -> bool:
+ """
+ Check the health of the deployed endpoint.
+
+ Health check is only available with Inference Endpoints powered by Text-Generation-Inference (TGI) or Text-Embedding-Inference (TEI).
+ For Inference API, please use [`InferenceClient.get_model_status`] instead.
+
+ Args:
+ model (`str`, *optional*):
+ URL of the Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `bool`: True if everything is working fine.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient("https://jzgu0buei5.us-east-1.aws.endpoints.huggingface.cloud")
+ >>> await client.health_check()
+ True
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Health check is not supported on '{self.provider}'.")
+
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if not model.startswith(("http://", "https://")):
+ raise ValueError(
+ "Model must be an Inference Endpoint URL. For serverless Inference API, please use `InferenceClient.get_model_status`."
+ )
+ url = model.rstrip("/") + "/health"
+
+ async with self._get_client_session(headers=build_hf_headers(token=self.token)) as client:
+ response = await client.get(url, proxy=self.proxies)
+ return response.status == 200
+
+ @_deprecate_method(
+ version="0.33.0",
+ message=(
+ "HF Inference API is getting revamped and will only support warm models in the future (no cold start allowed)."
+ " Use `HfApi.model_info` to get the model status both with HF Inference API and external providers."
+ ),
+ )
+ async def get_model_status(self, model: Optional[str] = None) -> ModelStatus:
+ """
+ Get the status of a model hosted on the HF Inference API.
+
+
+
+ This endpoint is mostly useful when you already know which model you want to use and want to check its
+ availability. If you want to discover already deployed models, you should rather use [`~InferenceClient.list_deployed_models`].
+
+
+
+ Args:
+ model (`str`, *optional*):
+ Identifier of the model for witch the status gonna be checked. If model is not provided,
+ the model associated with this instance of [`InferenceClient`] will be used. Only HF Inference API service can be checked so the
+ identifier cannot be a URL.
+
+
+ Returns:
+ [`ModelStatus`]: An instance of ModelStatus dataclass, containing information,
+ about the state of the model: load, state, compute type and framework.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.get_model_status("meta-llama/Meta-Llama-3-8B-Instruct")
+ ModelStatus(loaded=True, state='Loaded', compute_type='gpu', framework='text-generation-inference')
+ ```
+ """
+ if self.provider != "hf-inference":
+ raise ValueError(f"Getting model status is not supported on '{self.provider}'.")
+
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if model.startswith("https://"):
+ raise NotImplementedError("Model status is only available for Inference API endpoints.")
+ url = f"{constants.INFERENCE_ENDPOINT}/status/{model}"
+
+ async with self._get_client_session(headers=build_hf_headers(token=self.token)) as client:
+ response = await client.get(url, proxy=self.proxies)
+ response.raise_for_status()
+ response_data = await response.json()
+
+ if "error" in response_data:
+ raise ValueError(response_data["error"])
+
+ return ModelStatus(
+ loaded=response_data["loaded"],
+ state=response_data["state"],
+ compute_type=response_data["compute_type"],
+ framework=response_data["framework"],
+ )
+
+ @property
+ def chat(self) -> "ProxyClientChat":
+ return ProxyClientChat(self)
+
+
+class _ProxyClient:
+ """Proxy class to be able to call `client.chat.completion.create(...)` as OpenAI client."""
+
+ def __init__(self, client: AsyncInferenceClient):
+ self._client = client
+
+
+class ProxyClientChat(_ProxyClient):
+ """Proxy class to be able to call `client.chat.completion.create(...)` as OpenAI client."""
+
+ @property
+ def completions(self) -> "ProxyClientChatCompletions":
+ return ProxyClientChatCompletions(self._client)
+
+
+class ProxyClientChatCompletions(_ProxyClient):
+ """Proxy class to be able to call `client.chat.completion.create(...)` as OpenAI client."""
+
+ @property
+ def create(self):
+ return self._client.chat_completion
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/__init__.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..edbc967cb634014ceeb9108be9f86aa7387010e9
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/__init__.py
@@ -0,0 +1,187 @@
+# This file is auto-generated by `utils/generate_inference_types.py`.
+# Do not modify it manually.
+#
+# ruff: noqa: F401
+
+from .audio_classification import (
+ AudioClassificationInput,
+ AudioClassificationOutputElement,
+ AudioClassificationOutputTransform,
+ AudioClassificationParameters,
+)
+from .audio_to_audio import AudioToAudioInput, AudioToAudioOutputElement
+from .automatic_speech_recognition import (
+ AutomaticSpeechRecognitionEarlyStoppingEnum,
+ AutomaticSpeechRecognitionGenerationParameters,
+ AutomaticSpeechRecognitionInput,
+ AutomaticSpeechRecognitionOutput,
+ AutomaticSpeechRecognitionOutputChunk,
+ AutomaticSpeechRecognitionParameters,
+)
+from .base import BaseInferenceType
+from .chat_completion import (
+ ChatCompletionInput,
+ ChatCompletionInputFunctionDefinition,
+ ChatCompletionInputFunctionName,
+ ChatCompletionInputGrammarType,
+ ChatCompletionInputGrammarTypeType,
+ ChatCompletionInputMessage,
+ ChatCompletionInputMessageChunk,
+ ChatCompletionInputMessageChunkType,
+ ChatCompletionInputStreamOptions,
+ ChatCompletionInputTool,
+ ChatCompletionInputToolChoiceClass,
+ ChatCompletionInputToolChoiceEnum,
+ ChatCompletionInputURL,
+ ChatCompletionOutput,
+ ChatCompletionOutputComplete,
+ ChatCompletionOutputFunctionDefinition,
+ ChatCompletionOutputLogprob,
+ ChatCompletionOutputLogprobs,
+ ChatCompletionOutputMessage,
+ ChatCompletionOutputToolCall,
+ ChatCompletionOutputTopLogprob,
+ ChatCompletionOutputUsage,
+ ChatCompletionStreamOutput,
+ ChatCompletionStreamOutputChoice,
+ ChatCompletionStreamOutputDelta,
+ ChatCompletionStreamOutputDeltaToolCall,
+ ChatCompletionStreamOutputFunction,
+ ChatCompletionStreamOutputLogprob,
+ ChatCompletionStreamOutputLogprobs,
+ ChatCompletionStreamOutputTopLogprob,
+ ChatCompletionStreamOutputUsage,
+)
+from .depth_estimation import DepthEstimationInput, DepthEstimationOutput
+from .document_question_answering import (
+ DocumentQuestionAnsweringInput,
+ DocumentQuestionAnsweringInputData,
+ DocumentQuestionAnsweringOutputElement,
+ DocumentQuestionAnsweringParameters,
+)
+from .feature_extraction import FeatureExtractionInput, FeatureExtractionInputTruncationDirection
+from .fill_mask import FillMaskInput, FillMaskOutputElement, FillMaskParameters
+from .image_classification import (
+ ImageClassificationInput,
+ ImageClassificationOutputElement,
+ ImageClassificationOutputTransform,
+ ImageClassificationParameters,
+)
+from .image_segmentation import (
+ ImageSegmentationInput,
+ ImageSegmentationOutputElement,
+ ImageSegmentationParameters,
+ ImageSegmentationSubtask,
+)
+from .image_to_image import ImageToImageInput, ImageToImageOutput, ImageToImageParameters, ImageToImageTargetSize
+from .image_to_text import (
+ ImageToTextEarlyStoppingEnum,
+ ImageToTextGenerationParameters,
+ ImageToTextInput,
+ ImageToTextOutput,
+ ImageToTextParameters,
+)
+from .object_detection import (
+ ObjectDetectionBoundingBox,
+ ObjectDetectionInput,
+ ObjectDetectionOutputElement,
+ ObjectDetectionParameters,
+)
+from .question_answering import (
+ QuestionAnsweringInput,
+ QuestionAnsweringInputData,
+ QuestionAnsweringOutputElement,
+ QuestionAnsweringParameters,
+)
+from .sentence_similarity import SentenceSimilarityInput, SentenceSimilarityInputData
+from .summarization import (
+ SummarizationInput,
+ SummarizationOutput,
+ SummarizationParameters,
+ SummarizationTruncationStrategy,
+)
+from .table_question_answering import (
+ Padding,
+ TableQuestionAnsweringInput,
+ TableQuestionAnsweringInputData,
+ TableQuestionAnsweringOutputElement,
+ TableQuestionAnsweringParameters,
+)
+from .text2text_generation import (
+ Text2TextGenerationInput,
+ Text2TextGenerationOutput,
+ Text2TextGenerationParameters,
+ Text2TextGenerationTruncationStrategy,
+)
+from .text_classification import (
+ TextClassificationInput,
+ TextClassificationOutputElement,
+ TextClassificationOutputTransform,
+ TextClassificationParameters,
+)
+from .text_generation import (
+ TextGenerationInput,
+ TextGenerationInputGenerateParameters,
+ TextGenerationInputGrammarType,
+ TextGenerationOutput,
+ TextGenerationOutputBestOfSequence,
+ TextGenerationOutputDetails,
+ TextGenerationOutputFinishReason,
+ TextGenerationOutputPrefillToken,
+ TextGenerationOutputToken,
+ TextGenerationStreamOutput,
+ TextGenerationStreamOutputStreamDetails,
+ TextGenerationStreamOutputToken,
+ TypeEnum,
+)
+from .text_to_audio import (
+ TextToAudioEarlyStoppingEnum,
+ TextToAudioGenerationParameters,
+ TextToAudioInput,
+ TextToAudioOutput,
+ TextToAudioParameters,
+)
+from .text_to_image import TextToImageInput, TextToImageOutput, TextToImageParameters
+from .text_to_speech import (
+ TextToSpeechEarlyStoppingEnum,
+ TextToSpeechGenerationParameters,
+ TextToSpeechInput,
+ TextToSpeechOutput,
+ TextToSpeechParameters,
+)
+from .text_to_video import TextToVideoInput, TextToVideoOutput, TextToVideoParameters
+from .token_classification import (
+ TokenClassificationAggregationStrategy,
+ TokenClassificationInput,
+ TokenClassificationOutputElement,
+ TokenClassificationParameters,
+)
+from .translation import TranslationInput, TranslationOutput, TranslationParameters, TranslationTruncationStrategy
+from .video_classification import (
+ VideoClassificationInput,
+ VideoClassificationOutputElement,
+ VideoClassificationOutputTransform,
+ VideoClassificationParameters,
+)
+from .visual_question_answering import (
+ VisualQuestionAnsweringInput,
+ VisualQuestionAnsweringInputData,
+ VisualQuestionAnsweringOutputElement,
+ VisualQuestionAnsweringParameters,
+)
+from .zero_shot_classification import (
+ ZeroShotClassificationInput,
+ ZeroShotClassificationOutputElement,
+ ZeroShotClassificationParameters,
+)
+from .zero_shot_image_classification import (
+ ZeroShotImageClassificationInput,
+ ZeroShotImageClassificationOutputElement,
+ ZeroShotImageClassificationParameters,
+)
+from .zero_shot_object_detection import (
+ ZeroShotObjectDetectionBoundingBox,
+ ZeroShotObjectDetectionInput,
+ ZeroShotObjectDetectionOutputElement,
+ ZeroShotObjectDetectionParameters,
+)
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/audio_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/audio_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..053055787bce933e1fbd393cfbc00d81c43c8c2d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/audio_classification.py
@@ -0,0 +1,43 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+AudioClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass_with_extra
+class AudioClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Audio Classification"""
+
+ function_to_apply: Optional["AudioClassificationOutputTransform"] = None
+ """The function to apply to the model outputs in order to retrieve the scores."""
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass_with_extra
+class AudioClassificationInput(BaseInferenceType):
+ """Inputs for Audio Classification inference"""
+
+ inputs: str
+ """The input audio data as a base64-encoded string. If no `parameters` are provided, you can
+ also provide the audio data as a raw bytes payload.
+ """
+ parameters: Optional[AudioClassificationParameters] = None
+ """Additional inference parameters for Audio Classification"""
+
+
+@dataclass_with_extra
+class AudioClassificationOutputElement(BaseInferenceType):
+ """Outputs for Audio Classification inference"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/audio_to_audio.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/audio_to_audio.py
new file mode 100644
index 0000000000000000000000000000000000000000..43f376b5345fab6b854b028d1c17416c020d7bc1
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/audio_to_audio.py
@@ -0,0 +1,30 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class AudioToAudioInput(BaseInferenceType):
+ """Inputs for Audio to Audio inference"""
+
+ inputs: Any
+ """The input audio data"""
+
+
+@dataclass_with_extra
+class AudioToAudioOutputElement(BaseInferenceType):
+ """Outputs of inference for the Audio To Audio task
+ A generated audio file with its label.
+ """
+
+ blob: Any
+ """The generated audio file."""
+ content_type: str
+ """The content type of audio file."""
+ label: str
+ """The label of the audio file."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e885b6c6efe389cc5fb93a6ef7c9f956b1c3acc
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py
@@ -0,0 +1,114 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import List, Literal, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+AutomaticSpeechRecognitionEarlyStoppingEnum = Literal["never"]
+
+
+@dataclass_with_extra
+class AutomaticSpeechRecognitionGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process"""
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "AutomaticSpeechRecognitionEarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over max_length."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over min_length."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass_with_extra
+class AutomaticSpeechRecognitionParameters(BaseInferenceType):
+ """Additional inference parameters for Automatic Speech Recognition"""
+
+ return_timestamps: Optional[bool] = None
+ """Whether to output corresponding timestamps with the generated text"""
+ # Will be deprecated in the future when the renaming to `generation_parameters` is implemented in transformers
+ generate_kwargs: Optional[AutomaticSpeechRecognitionGenerationParameters] = None
+ """Parametrization of the text generation process"""
+
+
+@dataclass_with_extra
+class AutomaticSpeechRecognitionInput(BaseInferenceType):
+ """Inputs for Automatic Speech Recognition inference"""
+
+ inputs: str
+ """The input audio data as a base64-encoded string. If no `parameters` are provided, you can
+ also provide the audio data as a raw bytes payload.
+ """
+ parameters: Optional[AutomaticSpeechRecognitionParameters] = None
+ """Additional inference parameters for Automatic Speech Recognition"""
+
+
+@dataclass_with_extra
+class AutomaticSpeechRecognitionOutputChunk(BaseInferenceType):
+ text: str
+ """A chunk of text identified by the model"""
+ timestamp: List[float]
+ """The start and end timestamps corresponding with the text"""
+
+
+@dataclass_with_extra
+class AutomaticSpeechRecognitionOutput(BaseInferenceType):
+ """Outputs of inference for the Automatic Speech Recognition task"""
+
+ text: str
+ """The recognized text."""
+ chunks: Optional[List[AutomaticSpeechRecognitionOutputChunk]] = None
+ """When returnTimestamps is enabled, chunks contains a list of audio chunks identified by
+ the model.
+ """
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/base.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f0c4687ceccbfb738da3f38c583c2516d065a01
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/base.py
@@ -0,0 +1,161 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a base class for all inference types."""
+
+import inspect
+import json
+from dataclasses import asdict, dataclass
+from typing import Any, Dict, List, Type, TypeVar, Union, get_args
+
+
+T = TypeVar("T", bound="BaseInferenceType")
+
+
+def _repr_with_extra(self):
+ fields = list(self.__dataclass_fields__.keys())
+ other_fields = list(k for k in self.__dict__ if k not in fields)
+ return f"{self.__class__.__name__}({', '.join(f'{k}={self.__dict__[k]!r}' for k in fields + other_fields)})"
+
+
+def dataclass_with_extra(cls: Type[T]) -> Type[T]:
+ """Decorator to add a custom __repr__ method to a dataclass, showing all fields, including extra ones.
+
+ This decorator only works with dataclasses that inherit from `BaseInferenceType`.
+ """
+ cls = dataclass(cls)
+ cls.__repr__ = _repr_with_extra # type: ignore[method-assign]
+ return cls
+
+
+@dataclass
+class BaseInferenceType(dict):
+ """Base class for all inference types.
+
+ Object is a dataclass and a dict for backward compatibility but plan is to remove the dict part in the future.
+
+ Handle parsing from dict, list and json strings in a permissive way to ensure future-compatibility (e.g. all fields
+ are made optional, and non-expected fields are added as dict attributes).
+ """
+
+ @classmethod
+ def parse_obj_as_list(cls: Type[T], data: Union[bytes, str, List, Dict]) -> List[T]:
+ """Alias to parse server response and return a single instance.
+
+ See `parse_obj` for more details.
+ """
+ output = cls.parse_obj(data)
+ if not isinstance(output, list):
+ raise ValueError(f"Invalid input data for {cls}. Expected a list, but got {type(output)}.")
+ return output
+
+ @classmethod
+ def parse_obj_as_instance(cls: Type[T], data: Union[bytes, str, List, Dict]) -> T:
+ """Alias to parse server response and return a single instance.
+
+ See `parse_obj` for more details.
+ """
+ output = cls.parse_obj(data)
+ if isinstance(output, list):
+ raise ValueError(f"Invalid input data for {cls}. Expected a single instance, but got a list.")
+ return output
+
+ @classmethod
+ def parse_obj(cls: Type[T], data: Union[bytes, str, List, Dict]) -> Union[List[T], T]:
+ """Parse server response as a dataclass or list of dataclasses.
+
+ To enable future-compatibility, we want to handle cases where the server return more fields than expected.
+ In such cases, we don't want to raise an error but still create the dataclass object. Remaining fields are
+ added as dict attributes.
+ """
+ # Parse server response (from bytes)
+ if isinstance(data, bytes):
+ data = data.decode()
+ if isinstance(data, str):
+ data = json.loads(data)
+
+ # If a list, parse each item individually
+ if isinstance(data, List):
+ return [cls.parse_obj(d) for d in data] # type: ignore [misc]
+
+ # At this point, we expect a dict
+ if not isinstance(data, dict):
+ raise ValueError(f"Invalid data type: {type(data)}")
+
+ init_values = {}
+ other_values = {}
+ for key, value in data.items():
+ key = normalize_key(key)
+ if key in cls.__dataclass_fields__ and cls.__dataclass_fields__[key].init:
+ if isinstance(value, dict) or isinstance(value, list):
+ field_type = cls.__dataclass_fields__[key].type
+
+ # if `field_type` is a `BaseInferenceType`, parse it
+ if inspect.isclass(field_type) and issubclass(field_type, BaseInferenceType):
+ value = field_type.parse_obj(value)
+
+ # otherwise, recursively parse nested dataclasses (if possible)
+ # `get_args` returns handle Union and Optional for us
+ else:
+ expected_types = get_args(field_type)
+ for expected_type in expected_types:
+ if getattr(expected_type, "_name", None) == "List":
+ expected_type = get_args(expected_type)[
+ 0
+ ] # assume same type for all items in the list
+ if inspect.isclass(expected_type) and issubclass(expected_type, BaseInferenceType):
+ value = expected_type.parse_obj(value)
+ break
+ init_values[key] = value
+ else:
+ other_values[key] = value
+
+ # Make all missing fields default to None
+ # => ensure that dataclass initialization will never fail even if the server does not return all fields.
+ for key in cls.__dataclass_fields__:
+ if key not in init_values:
+ init_values[key] = None
+
+ # Initialize dataclass with expected values
+ item = cls(**init_values)
+
+ # Add remaining fields as dict attributes
+ item.update(other_values)
+
+ # Add remaining fields as extra dataclass fields.
+ # They won't be part of the dataclass fields but will be accessible as attributes.
+ # Use @dataclass_with_extra to show them in __repr__.
+ item.__dict__.update(other_values)
+ return item
+
+ def __post_init__(self):
+ self.update(asdict(self))
+
+ def __setitem__(self, __key: Any, __value: Any) -> None:
+ # Hacky way to keep dataclass values in sync when dict is updated
+ super().__setitem__(__key, __value)
+ if __key in self.__dataclass_fields__ and getattr(self, __key, None) != __value:
+ self.__setattr__(__key, __value)
+ return
+
+ def __setattr__(self, __name: str, __value: Any) -> None:
+ # Hacky way to keep dict values is sync when dataclass is updated
+ super().__setattr__(__name, __value)
+ if self.get(__name) != __value:
+ self[__name] = __value
+ return
+
+
+def normalize_key(key: str) -> str:
+ # e.g "content-type" -> "content_type", "Accept" -> "accept"
+ return key.replace("-", "_").replace(" ", "_").lower()
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/chat_completion.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/chat_completion.py
new file mode 100644
index 0000000000000000000000000000000000000000..a817d3f80fdfa9bcfd684379e5bd80143b5772ba
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/chat_completion.py
@@ -0,0 +1,301 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, List, Literal, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class ChatCompletionInputURL(BaseInferenceType):
+ url: str
+
+
+ChatCompletionInputMessageChunkType = Literal["text", "image_url"]
+
+
+@dataclass_with_extra
+class ChatCompletionInputMessageChunk(BaseInferenceType):
+ type: "ChatCompletionInputMessageChunkType"
+ image_url: Optional[ChatCompletionInputURL] = None
+ text: Optional[str] = None
+
+
+@dataclass_with_extra
+class ChatCompletionInputMessage(BaseInferenceType):
+ content: Union[List[ChatCompletionInputMessageChunk], str]
+ role: str
+ name: Optional[str] = None
+
+
+ChatCompletionInputGrammarTypeType = Literal["json", "regex"]
+
+
+@dataclass_with_extra
+class ChatCompletionInputGrammarType(BaseInferenceType):
+ type: "ChatCompletionInputGrammarTypeType"
+ value: Any
+ """A string that represents a [JSON Schema](https://json-schema.org/).
+ JSON Schema is a declarative language that allows to annotate JSON documents
+ with types and descriptions.
+ """
+
+
+@dataclass_with_extra
+class ChatCompletionInputStreamOptions(BaseInferenceType):
+ include_usage: bool
+ """If set, an additional chunk will be streamed before the data: [DONE] message. The usage
+ field on this chunk shows the token usage statistics for the entire request, and the
+ choices field will always be an empty array. All other chunks will also include a usage
+ field, but with a null value.
+ """
+
+
+@dataclass_with_extra
+class ChatCompletionInputFunctionName(BaseInferenceType):
+ name: str
+
+
+@dataclass_with_extra
+class ChatCompletionInputToolChoiceClass(BaseInferenceType):
+ function: ChatCompletionInputFunctionName
+
+
+ChatCompletionInputToolChoiceEnum = Literal["auto", "none", "required"]
+
+
+@dataclass_with_extra
+class ChatCompletionInputFunctionDefinition(BaseInferenceType):
+ arguments: Any
+ name: str
+ description: Optional[str] = None
+
+
+@dataclass_with_extra
+class ChatCompletionInputTool(BaseInferenceType):
+ function: ChatCompletionInputFunctionDefinition
+ type: str
+
+
+@dataclass_with_extra
+class ChatCompletionInput(BaseInferenceType):
+ """Chat Completion Input.
+ Auto-generated from TGI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
+ """
+
+ messages: List[ChatCompletionInputMessage]
+ """A list of messages comprising the conversation so far."""
+ frequency_penalty: Optional[float] = None
+ """Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing
+ frequency in the text so far,
+ decreasing the model's likelihood to repeat the same line verbatim.
+ """
+ logit_bias: Optional[List[float]] = None
+ """UNUSED
+ Modify the likelihood of specified tokens appearing in the completion. Accepts a JSON
+ object that maps tokens
+ (specified by their token ID in the tokenizer) to an associated bias value from -100 to
+ 100. Mathematically,
+ the bias is added to the logits generated by the model prior to sampling. The exact
+ effect will vary per model,
+ but values between -1 and 1 should decrease or increase likelihood of selection; values
+ like -100 or 100 should
+ result in a ban or exclusive selection of the relevant token.
+ """
+ logprobs: Optional[bool] = None
+ """Whether to return log probabilities of the output tokens or not. If true, returns the log
+ probabilities of each
+ output token returned in the content of message.
+ """
+ max_tokens: Optional[int] = None
+ """The maximum number of tokens that can be generated in the chat completion."""
+ model: Optional[str] = None
+ """[UNUSED] ID of the model to use. See the model endpoint compatibility table for details
+ on which models work with the Chat API.
+ """
+ n: Optional[int] = None
+ """UNUSED
+ How many chat completion choices to generate for each input message. Note that you will
+ be charged based on the
+ number of generated tokens across all of the choices. Keep n as 1 to minimize costs.
+ """
+ presence_penalty: Optional[float] = None
+ """Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they
+ appear in the text so far,
+ increasing the model's likelihood to talk about new topics
+ """
+ response_format: Optional[ChatCompletionInputGrammarType] = None
+ seed: Optional[int] = None
+ stop: Optional[List[str]] = None
+ """Up to 4 sequences where the API will stop generating further tokens."""
+ stream: Optional[bool] = None
+ stream_options: Optional[ChatCompletionInputStreamOptions] = None
+ temperature: Optional[float] = None
+ """What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the
+ output more random, while
+ lower values like 0.2 will make it more focused and deterministic.
+ We generally recommend altering this or `top_p` but not both.
+ """
+ tool_choice: Optional[Union[ChatCompletionInputToolChoiceClass, "ChatCompletionInputToolChoiceEnum"]] = None
+ tool_prompt: Optional[str] = None
+ """A prompt to be appended before the tools"""
+ tools: Optional[List[ChatCompletionInputTool]] = None
+ """A list of tools the model may call. Currently, only functions are supported as a tool.
+ Use this to provide a list of
+ functions the model may generate JSON inputs for.
+ """
+ top_logprobs: Optional[int] = None
+ """An integer between 0 and 5 specifying the number of most likely tokens to return at each
+ token position, each with
+ an associated log probability. logprobs must be set to true if this parameter is used.
+ """
+ top_p: Optional[float] = None
+ """An alternative to sampling with temperature, called nucleus sampling, where the model
+ considers the results of the
+ tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%
+ probability mass are considered.
+ """
+
+
+@dataclass_with_extra
+class ChatCompletionOutputTopLogprob(BaseInferenceType):
+ logprob: float
+ token: str
+
+
+@dataclass_with_extra
+class ChatCompletionOutputLogprob(BaseInferenceType):
+ logprob: float
+ token: str
+ top_logprobs: List[ChatCompletionOutputTopLogprob]
+
+
+@dataclass_with_extra
+class ChatCompletionOutputLogprobs(BaseInferenceType):
+ content: List[ChatCompletionOutputLogprob]
+
+
+@dataclass_with_extra
+class ChatCompletionOutputFunctionDefinition(BaseInferenceType):
+ arguments: Any
+ name: str
+ description: Optional[str] = None
+
+
+@dataclass_with_extra
+class ChatCompletionOutputToolCall(BaseInferenceType):
+ function: ChatCompletionOutputFunctionDefinition
+ id: str
+ type: str
+
+
+@dataclass_with_extra
+class ChatCompletionOutputMessage(BaseInferenceType):
+ role: str
+ content: Optional[str] = None
+ tool_calls: Optional[List[ChatCompletionOutputToolCall]] = None
+
+
+@dataclass_with_extra
+class ChatCompletionOutputComplete(BaseInferenceType):
+ finish_reason: str
+ index: int
+ message: ChatCompletionOutputMessage
+ logprobs: Optional[ChatCompletionOutputLogprobs] = None
+
+
+@dataclass_with_extra
+class ChatCompletionOutputUsage(BaseInferenceType):
+ completion_tokens: int
+ prompt_tokens: int
+ total_tokens: int
+
+
+@dataclass_with_extra
+class ChatCompletionOutput(BaseInferenceType):
+ """Chat Completion Output.
+ Auto-generated from TGI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
+ """
+
+ choices: List[ChatCompletionOutputComplete]
+ created: int
+ id: str
+ model: str
+ system_fingerprint: str
+ usage: ChatCompletionOutputUsage
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputFunction(BaseInferenceType):
+ arguments: str
+ name: Optional[str] = None
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputDeltaToolCall(BaseInferenceType):
+ function: ChatCompletionStreamOutputFunction
+ id: str
+ index: int
+ type: str
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputDelta(BaseInferenceType):
+ role: str
+ content: Optional[str] = None
+ tool_calls: Optional[ChatCompletionStreamOutputDeltaToolCall] = None
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputTopLogprob(BaseInferenceType):
+ logprob: float
+ token: str
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputLogprob(BaseInferenceType):
+ logprob: float
+ token: str
+ top_logprobs: List[ChatCompletionStreamOutputTopLogprob]
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputLogprobs(BaseInferenceType):
+ content: List[ChatCompletionStreamOutputLogprob]
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputChoice(BaseInferenceType):
+ delta: ChatCompletionStreamOutputDelta
+ index: int
+ finish_reason: Optional[str] = None
+ logprobs: Optional[ChatCompletionStreamOutputLogprobs] = None
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutputUsage(BaseInferenceType):
+ completion_tokens: int
+ prompt_tokens: int
+ total_tokens: int
+
+
+@dataclass_with_extra
+class ChatCompletionStreamOutput(BaseInferenceType):
+ """Chat Completion Stream Output.
+ Auto-generated from TGI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
+ """
+
+ choices: List[ChatCompletionStreamOutputChoice]
+ created: int
+ id: str
+ model: str
+ system_fingerprint: str
+ usage: Optional[ChatCompletionStreamOutputUsage] = None
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/depth_estimation.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/depth_estimation.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e09bdffa194f97444e484de6e930f67ac030207
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/depth_estimation.py
@@ -0,0 +1,28 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Dict, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class DepthEstimationInput(BaseInferenceType):
+ """Inputs for Depth Estimation inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters for Depth Estimation"""
+
+
+@dataclass_with_extra
+class DepthEstimationOutput(BaseInferenceType):
+ """Outputs of inference for the Depth Estimation task"""
+
+ depth: Any
+ """The predicted depth as an image"""
+ predicted_depth: Any
+ """The predicted depth as a tensor"""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/document_question_answering.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/document_question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..2457d2c8c237f055f660e0e8291d846bb036949d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/document_question_answering.py
@@ -0,0 +1,80 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, List, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class DocumentQuestionAnsweringInputData(BaseInferenceType):
+ """One (document, question) pair to answer"""
+
+ image: Any
+ """The image on which the question is asked"""
+ question: str
+ """A question to ask of the document"""
+
+
+@dataclass_with_extra
+class DocumentQuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters for Document Question Answering"""
+
+ doc_stride: Optional[int] = None
+ """If the words in the document are too long to fit with the question for the model, it will
+ be split in several chunks with some overlap. This argument controls the size of that
+ overlap.
+ """
+ handle_impossible_answer: Optional[bool] = None
+ """Whether to accept impossible as an answer"""
+ lang: Optional[str] = None
+ """Language to use while running OCR. Defaults to english."""
+ max_answer_len: Optional[int] = None
+ """The maximum length of predicted answers (e.g., only answers with a shorter length are
+ considered).
+ """
+ max_question_len: Optional[int] = None
+ """The maximum length of the question after tokenization. It will be truncated if needed."""
+ max_seq_len: Optional[int] = None
+ """The maximum length of the total sentence (context + question) in tokens of each chunk
+ passed to the model. The context will be split in several chunks (using doc_stride as
+ overlap) if needed.
+ """
+ top_k: Optional[int] = None
+ """The number of answers to return (will be chosen by order of likelihood). Can return less
+ than top_k answers if there are not enough options available within the context.
+ """
+ word_boxes: Optional[List[Union[List[float], str]]] = None
+ """A list of words and bounding boxes (normalized 0->1000). If provided, the inference will
+ skip the OCR step and use the provided bounding boxes instead.
+ """
+
+
+@dataclass_with_extra
+class DocumentQuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Document Question Answering inference"""
+
+ inputs: DocumentQuestionAnsweringInputData
+ """One (document, question) pair to answer"""
+ parameters: Optional[DocumentQuestionAnsweringParameters] = None
+ """Additional inference parameters for Document Question Answering"""
+
+
+@dataclass_with_extra
+class DocumentQuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Document Question Answering task"""
+
+ answer: str
+ """The answer to the question."""
+ end: int
+ """The end word index of the answer (in the OCR’d version of the input or provided word
+ boxes).
+ """
+ score: float
+ """The probability associated to the answer."""
+ start: int
+ """The start word index of the answer (in the OCR’d version of the input or provided word
+ boxes).
+ """
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/feature_extraction.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/feature_extraction.py
new file mode 100644
index 0000000000000000000000000000000000000000..e965ddbac2af0a5bf73e662a7c18c847611d18a1
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/feature_extraction.py
@@ -0,0 +1,36 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import List, Literal, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+FeatureExtractionInputTruncationDirection = Literal["Left", "Right"]
+
+
+@dataclass_with_extra
+class FeatureExtractionInput(BaseInferenceType):
+ """Feature Extraction Input.
+ Auto-generated from TEI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tei-import.ts.
+ """
+
+ inputs: Union[List[str], str]
+ """The text or list of texts to embed."""
+ normalize: Optional[bool] = None
+ prompt_name: Optional[str] = None
+ """The name of the prompt that should be used by for encoding. If not set, no prompt
+ will be applied.
+ Must be a key in the `sentence-transformers` configuration `prompts` dictionary.
+ For example if ``prompt_name`` is "query" and the ``prompts`` is {"query": "query: ",
+ ...},
+ then the sentence "What is the capital of France?" will be encoded as
+ "query: What is the capital of France?" because the prompt text will be prepended before
+ any text to encode.
+ """
+ truncate: Optional[bool] = None
+ truncation_direction: Optional["FeatureExtractionInputTruncationDirection"] = None
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/fill_mask.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/fill_mask.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfcdc56bc507e50280d38e0f63b024ada6a7ea94
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/fill_mask.py
@@ -0,0 +1,47 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, List, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class FillMaskParameters(BaseInferenceType):
+ """Additional inference parameters for Fill Mask"""
+
+ targets: Optional[List[str]] = None
+ """When passed, the model will limit the scores to the passed targets instead of looking up
+ in the whole vocabulary. If the provided targets are not in the model vocab, they will be
+ tokenized and the first resulting token will be used (with a warning, and that might be
+ slower).
+ """
+ top_k: Optional[int] = None
+ """When passed, overrides the number of predictions to return."""
+
+
+@dataclass_with_extra
+class FillMaskInput(BaseInferenceType):
+ """Inputs for Fill Mask inference"""
+
+ inputs: str
+ """The text with masked tokens"""
+ parameters: Optional[FillMaskParameters] = None
+ """Additional inference parameters for Fill Mask"""
+
+
+@dataclass_with_extra
+class FillMaskOutputElement(BaseInferenceType):
+ """Outputs of inference for the Fill Mask task"""
+
+ score: float
+ """The corresponding probability"""
+ sequence: str
+ """The corresponding input with the mask token prediction."""
+ token: int
+ """The predicted token id (to replace the masked one)."""
+ token_str: Any
+ fill_mask_output_token_str: Optional[str] = None
+ """The predicted token (to replace the masked one)."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fdda6c83ff4c7aee5dc7794f0530e89d6b43047
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_classification.py
@@ -0,0 +1,43 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+ImageClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass_with_extra
+class ImageClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Image Classification"""
+
+ function_to_apply: Optional["ImageClassificationOutputTransform"] = None
+ """The function to apply to the model outputs in order to retrieve the scores."""
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass_with_extra
+class ImageClassificationInput(BaseInferenceType):
+ """Inputs for Image Classification inference"""
+
+ inputs: str
+ """The input image data as a base64-encoded string. If no `parameters` are provided, you can
+ also provide the image data as a raw bytes payload.
+ """
+ parameters: Optional[ImageClassificationParameters] = None
+ """Additional inference parameters for Image Classification"""
+
+
+@dataclass_with_extra
+class ImageClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Image Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_segmentation.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_segmentation.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dbf61db83ec2ae6ceafd901c4425567cd2e5b03
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_segmentation.py
@@ -0,0 +1,51 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+ImageSegmentationSubtask = Literal["instance", "panoptic", "semantic"]
+
+
+@dataclass_with_extra
+class ImageSegmentationParameters(BaseInferenceType):
+ """Additional inference parameters for Image Segmentation"""
+
+ mask_threshold: Optional[float] = None
+ """Threshold to use when turning the predicted masks into binary values."""
+ overlap_mask_area_threshold: Optional[float] = None
+ """Mask overlap threshold to eliminate small, disconnected segments."""
+ subtask: Optional["ImageSegmentationSubtask"] = None
+ """Segmentation task to be performed, depending on model capabilities."""
+ threshold: Optional[float] = None
+ """Probability threshold to filter out predicted masks."""
+
+
+@dataclass_with_extra
+class ImageSegmentationInput(BaseInferenceType):
+ """Inputs for Image Segmentation inference"""
+
+ inputs: str
+ """The input image data as a base64-encoded string. If no `parameters` are provided, you can
+ also provide the image data as a raw bytes payload.
+ """
+ parameters: Optional[ImageSegmentationParameters] = None
+ """Additional inference parameters for Image Segmentation"""
+
+
+@dataclass_with_extra
+class ImageSegmentationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Image Segmentation task
+ A predicted mask / segment
+ """
+
+ label: str
+ """The label of the predicted segment."""
+ mask: str
+ """The corresponding mask as a black-and-white image (base64-encoded)."""
+ score: Optional[float] = None
+ """The score or confidence degree the model has."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_to_image.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_to_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..0deb0374dcab5a9a13616d2860a313fc17b35f7b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_to_image.py
@@ -0,0 +1,54 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class ImageToImageTargetSize(BaseInferenceType):
+ """The size in pixel of the output image."""
+
+ height: int
+ width: int
+
+
+@dataclass_with_extra
+class ImageToImageParameters(BaseInferenceType):
+ """Additional inference parameters for Image To Image"""
+
+ guidance_scale: Optional[float] = None
+ """For diffusion models. A higher guidance scale value encourages the model to generate
+ images closely linked to the text prompt at the expense of lower image quality.
+ """
+ negative_prompt: Optional[str] = None
+ """One prompt to guide what NOT to include in image generation."""
+ num_inference_steps: Optional[int] = None
+ """For diffusion models. The number of denoising steps. More denoising steps usually lead to
+ a higher quality image at the expense of slower inference.
+ """
+ target_size: Optional[ImageToImageTargetSize] = None
+ """The size in pixel of the output image."""
+
+
+@dataclass_with_extra
+class ImageToImageInput(BaseInferenceType):
+ """Inputs for Image To Image inference"""
+
+ inputs: str
+ """The input image data as a base64-encoded string. If no `parameters` are provided, you can
+ also provide the image data as a raw bytes payload.
+ """
+ parameters: Optional[ImageToImageParameters] = None
+ """Additional inference parameters for Image To Image"""
+
+
+@dataclass_with_extra
+class ImageToImageOutput(BaseInferenceType):
+ """Outputs of inference for the Image To Image task"""
+
+ image: Any
+ """The output image returned as raw bytes in the payload."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_to_text.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_to_text.py
new file mode 100644
index 0000000000000000000000000000000000000000..550d95e1f7516b68031a1a3372e775f11d0fdb0b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/image_to_text.py
@@ -0,0 +1,101 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Literal, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+ImageToTextEarlyStoppingEnum = Literal["never"]
+
+
+@dataclass_with_extra
+class ImageToTextGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process"""
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "ImageToTextEarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over max_length."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over min_length."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass_with_extra
+class ImageToTextParameters(BaseInferenceType):
+ """Additional inference parameters for Image To Text"""
+
+ max_new_tokens: Optional[int] = None
+ """The amount of maximum tokens to generate."""
+ # Will be deprecated in the future when the renaming to `generation_parameters` is implemented in transformers
+ generate_kwargs: Optional[ImageToTextGenerationParameters] = None
+ """Parametrization of the text generation process"""
+
+
+@dataclass_with_extra
+class ImageToTextInput(BaseInferenceType):
+ """Inputs for Image To Text inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[ImageToTextParameters] = None
+ """Additional inference parameters for Image To Text"""
+
+
+@dataclass_with_extra
+class ImageToTextOutput(BaseInferenceType):
+ """Outputs of inference for the Image To Text task"""
+
+ generated_text: Any
+ image_to_text_output_generated_text: Optional[str] = None
+ """The generated text."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/object_detection.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/object_detection.py
new file mode 100644
index 0000000000000000000000000000000000000000..75f3ebcfe1199462d0df60879b5ba6e517f7001e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/object_detection.py
@@ -0,0 +1,58 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class ObjectDetectionParameters(BaseInferenceType):
+ """Additional inference parameters for Object Detection"""
+
+ threshold: Optional[float] = None
+ """The probability necessary to make a prediction."""
+
+
+@dataclass_with_extra
+class ObjectDetectionInput(BaseInferenceType):
+ """Inputs for Object Detection inference"""
+
+ inputs: str
+ """The input image data as a base64-encoded string. If no `parameters` are provided, you can
+ also provide the image data as a raw bytes payload.
+ """
+ parameters: Optional[ObjectDetectionParameters] = None
+ """Additional inference parameters for Object Detection"""
+
+
+@dataclass_with_extra
+class ObjectDetectionBoundingBox(BaseInferenceType):
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+
+ xmax: int
+ """The x-coordinate of the bottom-right corner of the bounding box."""
+ xmin: int
+ """The x-coordinate of the top-left corner of the bounding box."""
+ ymax: int
+ """The y-coordinate of the bottom-right corner of the bounding box."""
+ ymin: int
+ """The y-coordinate of the top-left corner of the bounding box."""
+
+
+@dataclass_with_extra
+class ObjectDetectionOutputElement(BaseInferenceType):
+ """Outputs of inference for the Object Detection task"""
+
+ box: ObjectDetectionBoundingBox
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+ label: str
+ """The predicted label for the bounding box."""
+ score: float
+ """The associated score / probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/question_answering.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..014ab41893c560a2c266bc04a1d60bc933be31c7
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/question_answering.py
@@ -0,0 +1,74 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class QuestionAnsweringInputData(BaseInferenceType):
+ """One (context, question) pair to answer"""
+
+ context: str
+ """The context to be used for answering the question"""
+ question: str
+ """The question to be answered"""
+
+
+@dataclass_with_extra
+class QuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters for Question Answering"""
+
+ align_to_words: Optional[bool] = None
+ """Attempts to align the answer to real words. Improves quality on space separated
+ languages. Might hurt on non-space-separated languages (like Japanese or Chinese)
+ """
+ doc_stride: Optional[int] = None
+ """If the context is too long to fit with the question for the model, it will be split in
+ several chunks with some overlap. This argument controls the size of that overlap.
+ """
+ handle_impossible_answer: Optional[bool] = None
+ """Whether to accept impossible as an answer."""
+ max_answer_len: Optional[int] = None
+ """The maximum length of predicted answers (e.g., only answers with a shorter length are
+ considered).
+ """
+ max_question_len: Optional[int] = None
+ """The maximum length of the question after tokenization. It will be truncated if needed."""
+ max_seq_len: Optional[int] = None
+ """The maximum length of the total sentence (context + question) in tokens of each chunk
+ passed to the model. The context will be split in several chunks (using docStride as
+ overlap) if needed.
+ """
+ top_k: Optional[int] = None
+ """The number of answers to return (will be chosen by order of likelihood). Note that we
+ return less than topk answers if there are not enough options available within the
+ context.
+ """
+
+
+@dataclass_with_extra
+class QuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Question Answering inference"""
+
+ inputs: QuestionAnsweringInputData
+ """One (context, question) pair to answer"""
+ parameters: Optional[QuestionAnsweringParameters] = None
+ """Additional inference parameters for Question Answering"""
+
+
+@dataclass_with_extra
+class QuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Question Answering task"""
+
+ answer: str
+ """The answer to the question."""
+ end: int
+ """The character position in the input where the answer ends."""
+ score: float
+ """The probability associated to the answer."""
+ start: int
+ """The character position in the input where the answer begins."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/sentence_similarity.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/sentence_similarity.py
new file mode 100644
index 0000000000000000000000000000000000000000..66e8bb4d9322d4847556b7a17dc17bd208a37d0c
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/sentence_similarity.py
@@ -0,0 +1,27 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Dict, List, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class SentenceSimilarityInputData(BaseInferenceType):
+ sentences: List[str]
+ """A list of strings which will be compared against the source_sentence."""
+ source_sentence: str
+ """The string that you wish to compare the other strings with. This can be a phrase,
+ sentence, or longer passage, depending on the model being used.
+ """
+
+
+@dataclass_with_extra
+class SentenceSimilarityInput(BaseInferenceType):
+ """Inputs for Sentence similarity inference"""
+
+ inputs: SentenceSimilarityInputData
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters for Sentence Similarity"""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/summarization.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/summarization.py
new file mode 100644
index 0000000000000000000000000000000000000000..33eae6fcba0e8724babf145f93be005868429c33
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/summarization.py
@@ -0,0 +1,41 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Dict, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+SummarizationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]
+
+
+@dataclass_with_extra
+class SummarizationParameters(BaseInferenceType):
+ """Additional inference parameters for summarization."""
+
+ clean_up_tokenization_spaces: Optional[bool] = None
+ """Whether to clean up the potential extra spaces in the text output."""
+ generate_parameters: Optional[Dict[str, Any]] = None
+ """Additional parametrization of the text generation algorithm."""
+ truncation: Optional["SummarizationTruncationStrategy"] = None
+ """The truncation strategy to use."""
+
+
+@dataclass_with_extra
+class SummarizationInput(BaseInferenceType):
+ """Inputs for Summarization inference"""
+
+ inputs: str
+ """The input text to summarize."""
+ parameters: Optional[SummarizationParameters] = None
+ """Additional inference parameters for summarization."""
+
+
+@dataclass_with_extra
+class SummarizationOutput(BaseInferenceType):
+ """Outputs of inference for the Summarization task"""
+
+ summary_text: str
+ """The summarized text."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/table_question_answering.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/table_question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..10e208eeeb50a689d2826a160432a2b005ec006c
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/table_question_answering.py
@@ -0,0 +1,62 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Dict, List, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class TableQuestionAnsweringInputData(BaseInferenceType):
+ """One (table, question) pair to answer"""
+
+ question: str
+ """The question to be answered about the table"""
+ table: Dict[str, List[str]]
+ """The table to serve as context for the questions"""
+
+
+Padding = Literal["do_not_pad", "longest", "max_length"]
+
+
+@dataclass_with_extra
+class TableQuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters for Table Question Answering"""
+
+ padding: Optional["Padding"] = None
+ """Activates and controls padding."""
+ sequential: Optional[bool] = None
+ """Whether to do inference sequentially or as a batch. Batching is faster, but models like
+ SQA require the inference to be done sequentially to extract relations within sequences,
+ given their conversational nature.
+ """
+ truncation: Optional[bool] = None
+ """Activates and controls truncation."""
+
+
+@dataclass_with_extra
+class TableQuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Table Question Answering inference"""
+
+ inputs: TableQuestionAnsweringInputData
+ """One (table, question) pair to answer"""
+ parameters: Optional[TableQuestionAnsweringParameters] = None
+ """Additional inference parameters for Table Question Answering"""
+
+
+@dataclass_with_extra
+class TableQuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Table Question Answering task"""
+
+ answer: str
+ """The answer of the question given the table. If there is an aggregator, the answer will be
+ preceded by `AGGREGATOR >`.
+ """
+ cells: List[str]
+ """List of strings made up of the answer cell values."""
+ coordinates: List[List[int]]
+ """Coordinates of the cells of the answers."""
+ aggregator: Optional[str] = None
+ """If the model has an aggregator, this returns the aggregator."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text2text_generation.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text2text_generation.py
new file mode 100644
index 0000000000000000000000000000000000000000..34ac74e21e8a30d889f1a251f648d4c365325be6
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text2text_generation.py
@@ -0,0 +1,42 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Dict, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+Text2TextGenerationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]
+
+
+@dataclass_with_extra
+class Text2TextGenerationParameters(BaseInferenceType):
+ """Additional inference parameters for Text2text Generation"""
+
+ clean_up_tokenization_spaces: Optional[bool] = None
+ """Whether to clean up the potential extra spaces in the text output."""
+ generate_parameters: Optional[Dict[str, Any]] = None
+ """Additional parametrization of the text generation algorithm"""
+ truncation: Optional["Text2TextGenerationTruncationStrategy"] = None
+ """The truncation strategy to use"""
+
+
+@dataclass_with_extra
+class Text2TextGenerationInput(BaseInferenceType):
+ """Inputs for Text2text Generation inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[Text2TextGenerationParameters] = None
+ """Additional inference parameters for Text2text Generation"""
+
+
+@dataclass_with_extra
+class Text2TextGenerationOutput(BaseInferenceType):
+ """Outputs of inference for the Text2text Generation task"""
+
+ generated_text: Any
+ text2_text_generation_output_generated_text: Optional[str] = None
+ """The generated text."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a172b23f844fa58f757a644d52138a18e7b6ddb
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_classification.py
@@ -0,0 +1,41 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+TextClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass_with_extra
+class TextClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Text Classification"""
+
+ function_to_apply: Optional["TextClassificationOutputTransform"] = None
+ """The function to apply to the model outputs in order to retrieve the scores."""
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass_with_extra
+class TextClassificationInput(BaseInferenceType):
+ """Inputs for Text Classification inference"""
+
+ inputs: str
+ """The text to classify"""
+ parameters: Optional[TextClassificationParameters] = None
+ """Additional inference parameters for Text Classification"""
+
+
+@dataclass_with_extra
+class TextClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Text Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_generation.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_generation.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ad5c0391ad601e723d3c9c6fc68d6fb96b26ea1
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_generation.py
@@ -0,0 +1,168 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, List, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+TypeEnum = Literal["json", "regex"]
+
+
+@dataclass_with_extra
+class TextGenerationInputGrammarType(BaseInferenceType):
+ type: "TypeEnum"
+ value: Any
+ """A string that represents a [JSON Schema](https://json-schema.org/).
+ JSON Schema is a declarative language that allows to annotate JSON documents
+ with types and descriptions.
+ """
+
+
+@dataclass_with_extra
+class TextGenerationInputGenerateParameters(BaseInferenceType):
+ adapter_id: Optional[str] = None
+ """Lora adapter id"""
+ best_of: Optional[int] = None
+ """Generate best_of sequences and return the one if the highest token logprobs."""
+ decoder_input_details: Optional[bool] = None
+ """Whether to return decoder input token logprobs and ids."""
+ details: Optional[bool] = None
+ """Whether to return generation details."""
+ do_sample: Optional[bool] = None
+ """Activate logits sampling."""
+ frequency_penalty: Optional[float] = None
+ """The parameter for frequency penalty. 1.0 means no penalty
+ Penalize new tokens based on their existing frequency in the text so far,
+ decreasing the model's likelihood to repeat the same line verbatim.
+ """
+ grammar: Optional[TextGenerationInputGrammarType] = None
+ max_new_tokens: Optional[int] = None
+ """Maximum number of tokens to generate."""
+ repetition_penalty: Optional[float] = None
+ """The parameter for repetition penalty. 1.0 means no penalty.
+ See [this paper](https://arxiv.org/pdf/1909.05858.pdf) for more details.
+ """
+ return_full_text: Optional[bool] = None
+ """Whether to prepend the prompt to the generated text"""
+ seed: Optional[int] = None
+ """Random sampling seed."""
+ stop: Optional[List[str]] = None
+ """Stop generating tokens if a member of `stop` is generated."""
+ temperature: Optional[float] = None
+ """The value used to module the logits distribution."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_n_tokens: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-n-filtering."""
+ top_p: Optional[float] = None
+ """Top-p value for nucleus sampling."""
+ truncate: Optional[int] = None
+ """Truncate inputs tokens to the given size."""
+ typical_p: Optional[float] = None
+ """Typical Decoding mass
+ See [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666)
+ for more information.
+ """
+ watermark: Optional[bool] = None
+ """Watermarking with [A Watermark for Large Language
+ Models](https://arxiv.org/abs/2301.10226).
+ """
+
+
+@dataclass_with_extra
+class TextGenerationInput(BaseInferenceType):
+ """Text Generation Input.
+ Auto-generated from TGI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
+ """
+
+ inputs: str
+ parameters: Optional[TextGenerationInputGenerateParameters] = None
+ stream: Optional[bool] = None
+
+
+TextGenerationOutputFinishReason = Literal["length", "eos_token", "stop_sequence"]
+
+
+@dataclass_with_extra
+class TextGenerationOutputPrefillToken(BaseInferenceType):
+ id: int
+ logprob: float
+ text: str
+
+
+@dataclass_with_extra
+class TextGenerationOutputToken(BaseInferenceType):
+ id: int
+ logprob: float
+ special: bool
+ text: str
+
+
+@dataclass_with_extra
+class TextGenerationOutputBestOfSequence(BaseInferenceType):
+ finish_reason: "TextGenerationOutputFinishReason"
+ generated_text: str
+ generated_tokens: int
+ prefill: List[TextGenerationOutputPrefillToken]
+ tokens: List[TextGenerationOutputToken]
+ seed: Optional[int] = None
+ top_tokens: Optional[List[List[TextGenerationOutputToken]]] = None
+
+
+@dataclass_with_extra
+class TextGenerationOutputDetails(BaseInferenceType):
+ finish_reason: "TextGenerationOutputFinishReason"
+ generated_tokens: int
+ prefill: List[TextGenerationOutputPrefillToken]
+ tokens: List[TextGenerationOutputToken]
+ best_of_sequences: Optional[List[TextGenerationOutputBestOfSequence]] = None
+ seed: Optional[int] = None
+ top_tokens: Optional[List[List[TextGenerationOutputToken]]] = None
+
+
+@dataclass_with_extra
+class TextGenerationOutput(BaseInferenceType):
+ """Text Generation Output.
+ Auto-generated from TGI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
+ """
+
+ generated_text: str
+ details: Optional[TextGenerationOutputDetails] = None
+
+
+@dataclass_with_extra
+class TextGenerationStreamOutputStreamDetails(BaseInferenceType):
+ finish_reason: "TextGenerationOutputFinishReason"
+ generated_tokens: int
+ input_length: int
+ seed: Optional[int] = None
+
+
+@dataclass_with_extra
+class TextGenerationStreamOutputToken(BaseInferenceType):
+ id: int
+ logprob: float
+ special: bool
+ text: str
+
+
+@dataclass_with_extra
+class TextGenerationStreamOutput(BaseInferenceType):
+ """Text Generation Stream Output.
+ Auto-generated from TGI specs.
+ For more details, check out
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-tgi-import.ts.
+ """
+
+ index: int
+ token: TextGenerationStreamOutputToken
+ details: Optional[TextGenerationStreamOutputStreamDetails] = None
+ generated_text: Optional[str] = None
+ top_tokens: Optional[List[TextGenerationStreamOutputToken]] = None
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_audio.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_audio.py
new file mode 100644
index 0000000000000000000000000000000000000000..963b4406e0c5a40e654ddb8a059731c051b6257a
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_audio.py
@@ -0,0 +1,100 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Literal, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+TextToAudioEarlyStoppingEnum = Literal["never"]
+
+
+@dataclass_with_extra
+class TextToAudioGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process"""
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "TextToAudioEarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over max_length."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over min_length."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass_with_extra
+class TextToAudioParameters(BaseInferenceType):
+ """Additional inference parameters for Text To Audio"""
+
+ # Will be deprecated in the future when the renaming to `generation_parameters` is implemented in transformers
+ generate_kwargs: Optional[TextToAudioGenerationParameters] = None
+ """Parametrization of the text generation process"""
+
+
+@dataclass_with_extra
+class TextToAudioInput(BaseInferenceType):
+ """Inputs for Text To Audio inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[TextToAudioParameters] = None
+ """Additional inference parameters for Text To Audio"""
+
+
+@dataclass_with_extra
+class TextToAudioOutput(BaseInferenceType):
+ """Outputs of inference for the Text To Audio task"""
+
+ audio: Any
+ """The generated audio waveform."""
+ sampling_rate: float
+ """The sampling rate of the generated audio waveform."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_image.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..20c963731371339975019ca5d40c95303d79209b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_image.py
@@ -0,0 +1,50 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class TextToImageParameters(BaseInferenceType):
+ """Additional inference parameters for Text To Image"""
+
+ guidance_scale: Optional[float] = None
+ """A higher guidance scale value encourages the model to generate images closely linked to
+ the text prompt, but values too high may cause saturation and other artifacts.
+ """
+ height: Optional[int] = None
+ """The height in pixels of the output image"""
+ negative_prompt: Optional[str] = None
+ """One prompt to guide what NOT to include in image generation."""
+ num_inference_steps: Optional[int] = None
+ """The number of denoising steps. More denoising steps usually lead to a higher quality
+ image at the expense of slower inference.
+ """
+ scheduler: Optional[str] = None
+ """Override the scheduler with a compatible one."""
+ seed: Optional[int] = None
+ """Seed for the random number generator."""
+ width: Optional[int] = None
+ """The width in pixels of the output image"""
+
+
+@dataclass_with_extra
+class TextToImageInput(BaseInferenceType):
+ """Inputs for Text To Image inference"""
+
+ inputs: str
+ """The input text data (sometimes called "prompt")"""
+ parameters: Optional[TextToImageParameters] = None
+ """Additional inference parameters for Text To Image"""
+
+
+@dataclass_with_extra
+class TextToImageOutput(BaseInferenceType):
+ """Outputs of inference for the Text To Image task"""
+
+ image: Any
+ """The generated image returned as raw bytes in the payload."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_speech.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_speech.py
new file mode 100644
index 0000000000000000000000000000000000000000..4399e2aea0fc86127cf9696c29f5ed8f5360d0d5
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_speech.py
@@ -0,0 +1,100 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Literal, Optional, Union
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+TextToSpeechEarlyStoppingEnum = Literal["never"]
+
+
+@dataclass_with_extra
+class TextToSpeechGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process"""
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "TextToSpeechEarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over max_length."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over min_length."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass_with_extra
+class TextToSpeechParameters(BaseInferenceType):
+ """Additional inference parameters for Text To Speech"""
+
+ # Will be deprecated in the future when the renaming to `generation_parameters` is implemented in transformers
+ generate_kwargs: Optional[TextToSpeechGenerationParameters] = None
+ """Parametrization of the text generation process"""
+
+
+@dataclass_with_extra
+class TextToSpeechInput(BaseInferenceType):
+ """Inputs for Text To Speech inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[TextToSpeechParameters] = None
+ """Additional inference parameters for Text To Speech"""
+
+
+@dataclass_with_extra
+class TextToSpeechOutput(BaseInferenceType):
+ """Outputs of inference for the Text To Speech task"""
+
+ audio: Any
+ """The generated audio"""
+ sampling_rate: Optional[float] = None
+ """The sampling rate of the generated audio waveform."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_video.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_video.py
new file mode 100644
index 0000000000000000000000000000000000000000..e54a1bc094e4aaf7132e502aa268bc052ab34f0a
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/text_to_video.py
@@ -0,0 +1,46 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, List, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class TextToVideoParameters(BaseInferenceType):
+ """Additional inference parameters for Text To Video"""
+
+ guidance_scale: Optional[float] = None
+ """A higher guidance scale value encourages the model to generate videos closely linked to
+ the text prompt, but values too high may cause saturation and other artifacts.
+ """
+ negative_prompt: Optional[List[str]] = None
+ """One or several prompt to guide what NOT to include in video generation."""
+ num_frames: Optional[float] = None
+ """The num_frames parameter determines how many video frames are generated."""
+ num_inference_steps: Optional[int] = None
+ """The number of denoising steps. More denoising steps usually lead to a higher quality
+ video at the expense of slower inference.
+ """
+ seed: Optional[int] = None
+ """Seed for the random number generator."""
+
+
+@dataclass_with_extra
+class TextToVideoInput(BaseInferenceType):
+ """Inputs for Text To Video inference"""
+
+ inputs: str
+ """The input text data (sometimes called "prompt")"""
+ parameters: Optional[TextToVideoParameters] = None
+ """Additional inference parameters for Text To Video"""
+
+
+@dataclass_with_extra
+class TextToVideoOutput(BaseInferenceType):
+ """Outputs of inference for the Text To Video task"""
+
+ video: Any
+ """The generated video returned as raw bytes in the payload."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/token_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/token_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..e039b6a1db7dcd54dbc9434d3254da0770c6799e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/token_classification.py
@@ -0,0 +1,51 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import List, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+TokenClassificationAggregationStrategy = Literal["none", "simple", "first", "average", "max"]
+
+
+@dataclass_with_extra
+class TokenClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Token Classification"""
+
+ aggregation_strategy: Optional["TokenClassificationAggregationStrategy"] = None
+ """The strategy used to fuse tokens based on model predictions"""
+ ignore_labels: Optional[List[str]] = None
+ """A list of labels to ignore"""
+ stride: Optional[int] = None
+ """The number of overlapping tokens between chunks when splitting the input text."""
+
+
+@dataclass_with_extra
+class TokenClassificationInput(BaseInferenceType):
+ """Inputs for Token Classification inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[TokenClassificationParameters] = None
+ """Additional inference parameters for Token Classification"""
+
+
+@dataclass_with_extra
+class TokenClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Token Classification task"""
+
+ end: int
+ """The character position in the input where this group ends."""
+ score: float
+ """The associated score / probability"""
+ start: int
+ """The character position in the input where this group begins."""
+ word: str
+ """The corresponding text"""
+ entity: Optional[str] = None
+ """The predicted label for a single token"""
+ entity_group: Optional[str] = None
+ """The predicted label for a group of one or more tokens"""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/translation.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/translation.py
new file mode 100644
index 0000000000000000000000000000000000000000..df95b7dbb1f4ce5b80cec034e004bb6e71387be8
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/translation.py
@@ -0,0 +1,49 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Dict, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+TranslationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]
+
+
+@dataclass_with_extra
+class TranslationParameters(BaseInferenceType):
+ """Additional inference parameters for Translation"""
+
+ clean_up_tokenization_spaces: Optional[bool] = None
+ """Whether to clean up the potential extra spaces in the text output."""
+ generate_parameters: Optional[Dict[str, Any]] = None
+ """Additional parametrization of the text generation algorithm."""
+ src_lang: Optional[str] = None
+ """The source language of the text. Required for models that can translate from multiple
+ languages.
+ """
+ tgt_lang: Optional[str] = None
+ """Target language to translate to. Required for models that can translate to multiple
+ languages.
+ """
+ truncation: Optional["TranslationTruncationStrategy"] = None
+ """The truncation strategy to use."""
+
+
+@dataclass_with_extra
+class TranslationInput(BaseInferenceType):
+ """Inputs for Translation inference"""
+
+ inputs: str
+ """The text to translate."""
+ parameters: Optional[TranslationParameters] = None
+ """Additional inference parameters for Translation"""
+
+
+@dataclass_with_extra
+class TranslationOutput(BaseInferenceType):
+ """Outputs of inference for the Translation task"""
+
+ translation_text: str
+ """The translated text."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/video_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/video_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1d7a15bb4ee5fa63aa6ebc3750191bd38549212
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/video_classification.py
@@ -0,0 +1,45 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Literal, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+VideoClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass_with_extra
+class VideoClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Video Classification"""
+
+ frame_sampling_rate: Optional[int] = None
+ """The sampling rate used to select frames from the video."""
+ function_to_apply: Optional["VideoClassificationOutputTransform"] = None
+ """The function to apply to the model outputs in order to retrieve the scores."""
+ num_frames: Optional[int] = None
+ """The number of sampled frames to consider for classification."""
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass_with_extra
+class VideoClassificationInput(BaseInferenceType):
+ """Inputs for Video Classification inference"""
+
+ inputs: Any
+ """The input video data"""
+ parameters: Optional[VideoClassificationParameters] = None
+ """Additional inference parameters for Video Classification"""
+
+
+@dataclass_with_extra
+class VideoClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Video Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/visual_question_answering.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/visual_question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..d368f1621289bc11a17be3e590cf8a040019d455
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/visual_question_answering.py
@@ -0,0 +1,49 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import Any, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class VisualQuestionAnsweringInputData(BaseInferenceType):
+ """One (image, question) pair to answer"""
+
+ image: Any
+ """The image."""
+ question: str
+ """The question to answer based on the image."""
+
+
+@dataclass_with_extra
+class VisualQuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters for Visual Question Answering"""
+
+ top_k: Optional[int] = None
+ """The number of answers to return (will be chosen by order of likelihood). Note that we
+ return less than topk answers if there are not enough options available within the
+ context.
+ """
+
+
+@dataclass_with_extra
+class VisualQuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Visual Question Answering inference"""
+
+ inputs: VisualQuestionAnsweringInputData
+ """One (image, question) pair to answer"""
+ parameters: Optional[VisualQuestionAnsweringParameters] = None
+ """Additional inference parameters for Visual Question Answering"""
+
+
+@dataclass_with_extra
+class VisualQuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Visual Question Answering task"""
+
+ score: float
+ """The associated score / probability"""
+ answer: Optional[str] = None
+ """The answer to the question"""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..47b32492e358edcc0de6aa09d53635b0a8156b25
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_classification.py
@@ -0,0 +1,45 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import List, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class ZeroShotClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Zero Shot Classification"""
+
+ candidate_labels: List[str]
+ """The set of possible class labels to classify the text into."""
+ hypothesis_template: Optional[str] = None
+ """The sentence used in conjunction with `candidate_labels` to attempt the text
+ classification by replacing the placeholder with the candidate labels.
+ """
+ multi_label: Optional[bool] = None
+ """Whether multiple candidate labels can be true. If false, the scores are normalized such
+ that the sum of the label likelihoods for each sequence is 1. If true, the labels are
+ considered independent and probabilities are normalized for each candidate.
+ """
+
+
+@dataclass_with_extra
+class ZeroShotClassificationInput(BaseInferenceType):
+ """Inputs for Zero Shot Classification inference"""
+
+ inputs: str
+ """The text to classify"""
+ parameters: ZeroShotClassificationParameters
+ """Additional inference parameters for Zero Shot Classification"""
+
+
+@dataclass_with_extra
+class ZeroShotClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Zero Shot Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..998d66b6b4e3356f0f09a0ad25ebdaf2e76cd03f
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py
@@ -0,0 +1,40 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import List, Optional
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class ZeroShotImageClassificationParameters(BaseInferenceType):
+ """Additional inference parameters for Zero Shot Image Classification"""
+
+ candidate_labels: List[str]
+ """The candidate labels for this image"""
+ hypothesis_template: Optional[str] = None
+ """The sentence used in conjunction with `candidate_labels` to attempt the image
+ classification by replacing the placeholder with the candidate labels.
+ """
+
+
+@dataclass_with_extra
+class ZeroShotImageClassificationInput(BaseInferenceType):
+ """Inputs for Zero Shot Image Classification inference"""
+
+ inputs: str
+ """The input image data to classify as a base64-encoded string."""
+ parameters: ZeroShotImageClassificationParameters
+ """Additional inference parameters for Zero Shot Image Classification"""
+
+
+@dataclass_with_extra
+class ZeroShotImageClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Zero Shot Image Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ef76b5fcb93e8126266e4b1464934d01024b1b7
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py
@@ -0,0 +1,52 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from typing import List
+
+from .base import BaseInferenceType, dataclass_with_extra
+
+
+@dataclass_with_extra
+class ZeroShotObjectDetectionParameters(BaseInferenceType):
+ """Additional inference parameters for Zero Shot Object Detection"""
+
+ candidate_labels: List[str]
+ """The candidate labels for this image"""
+
+
+@dataclass_with_extra
+class ZeroShotObjectDetectionInput(BaseInferenceType):
+ """Inputs for Zero Shot Object Detection inference"""
+
+ inputs: str
+ """The input image data as a base64-encoded string."""
+ parameters: ZeroShotObjectDetectionParameters
+ """Additional inference parameters for Zero Shot Object Detection"""
+
+
+@dataclass_with_extra
+class ZeroShotObjectDetectionBoundingBox(BaseInferenceType):
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+
+ xmax: int
+ xmin: int
+ ymax: int
+ ymin: int
+
+
+@dataclass_with_extra
+class ZeroShotObjectDetectionOutputElement(BaseInferenceType):
+ """Outputs of inference for the Zero Shot Object Detection task"""
+
+ box: ZeroShotObjectDetectionBoundingBox
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+ label: str
+ """A candidate label"""
+ score: float
+ """The associated score / probability"""
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/__init__.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a4a1ca715e05628ee9111a79abf15f9c00de66c
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/__init__.py
@@ -0,0 +1,125 @@
+from typing import Dict, Literal
+
+from ._common import TaskProviderHelper
+from .black_forest_labs import BlackForestLabsTextToImageTask
+from .fal_ai import (
+ FalAIAutomaticSpeechRecognitionTask,
+ FalAITextToImageTask,
+ FalAITextToSpeechTask,
+ FalAITextToVideoTask,
+)
+from .fireworks_ai import FireworksAIConversationalTask
+from .hf_inference import HFInferenceBinaryInputTask, HFInferenceConversational, HFInferenceTask
+from .hyperbolic import HyperbolicTextGenerationTask, HyperbolicTextToImageTask
+from .nebius import NebiusConversationalTask, NebiusTextGenerationTask, NebiusTextToImageTask
+from .novita import NovitaConversationalTask, NovitaTextGenerationTask
+from .replicate import ReplicateTask, ReplicateTextToSpeechTask
+from .sambanova import SambanovaConversationalTask
+from .together import TogetherConversationalTask, TogetherTextGenerationTask, TogetherTextToImageTask
+
+
+PROVIDER_T = Literal[
+ "black-forest-labs",
+ "fal-ai",
+ "fireworks-ai",
+ "hf-inference",
+ "hyperbolic",
+ "nebius",
+ "novita",
+ "replicate",
+ "sambanova",
+ "together",
+]
+
+PROVIDERS: Dict[PROVIDER_T, Dict[str, TaskProviderHelper]] = {
+ "black-forest-labs": {
+ "text-to-image": BlackForestLabsTextToImageTask(),
+ },
+ "fal-ai": {
+ "automatic-speech-recognition": FalAIAutomaticSpeechRecognitionTask(),
+ "text-to-image": FalAITextToImageTask(),
+ "text-to-speech": FalAITextToSpeechTask(),
+ "text-to-video": FalAITextToVideoTask(),
+ },
+ "fireworks-ai": {
+ "conversational": FireworksAIConversationalTask(),
+ },
+ "hf-inference": {
+ "text-to-image": HFInferenceTask("text-to-image"),
+ "conversational": HFInferenceConversational(),
+ "text-generation": HFInferenceTask("text-generation"),
+ "text-classification": HFInferenceTask("text-classification"),
+ "question-answering": HFInferenceTask("question-answering"),
+ "audio-classification": HFInferenceBinaryInputTask("audio-classification"),
+ "automatic-speech-recognition": HFInferenceBinaryInputTask("automatic-speech-recognition"),
+ "fill-mask": HFInferenceTask("fill-mask"),
+ "feature-extraction": HFInferenceTask("feature-extraction"),
+ "image-classification": HFInferenceBinaryInputTask("image-classification"),
+ "image-segmentation": HFInferenceBinaryInputTask("image-segmentation"),
+ "document-question-answering": HFInferenceTask("document-question-answering"),
+ "image-to-text": HFInferenceBinaryInputTask("image-to-text"),
+ "object-detection": HFInferenceBinaryInputTask("object-detection"),
+ "audio-to-audio": HFInferenceBinaryInputTask("audio-to-audio"),
+ "zero-shot-image-classification": HFInferenceBinaryInputTask("zero-shot-image-classification"),
+ "zero-shot-classification": HFInferenceTask("zero-shot-classification"),
+ "image-to-image": HFInferenceBinaryInputTask("image-to-image"),
+ "sentence-similarity": HFInferenceTask("sentence-similarity"),
+ "table-question-answering": HFInferenceTask("table-question-answering"),
+ "tabular-classification": HFInferenceTask("tabular-classification"),
+ "text-to-speech": HFInferenceTask("text-to-speech"),
+ "token-classification": HFInferenceTask("token-classification"),
+ "translation": HFInferenceTask("translation"),
+ "summarization": HFInferenceTask("summarization"),
+ "visual-question-answering": HFInferenceBinaryInputTask("visual-question-answering"),
+ },
+ "hyperbolic": {
+ "text-to-image": HyperbolicTextToImageTask(),
+ "conversational": HyperbolicTextGenerationTask("conversational"),
+ "text-generation": HyperbolicTextGenerationTask("text-generation"),
+ },
+ "nebius": {
+ "text-to-image": NebiusTextToImageTask(),
+ "conversational": NebiusConversationalTask(),
+ "text-generation": NebiusTextGenerationTask(),
+ },
+ "novita": {
+ "text-generation": NovitaTextGenerationTask(),
+ "conversational": NovitaConversationalTask(),
+ },
+ "replicate": {
+ "text-to-image": ReplicateTask("text-to-image"),
+ "text-to-speech": ReplicateTextToSpeechTask(),
+ "text-to-video": ReplicateTask("text-to-video"),
+ },
+ "sambanova": {
+ "conversational": SambanovaConversationalTask(),
+ },
+ "together": {
+ "text-to-image": TogetherTextToImageTask(),
+ "conversational": TogetherConversationalTask(),
+ "text-generation": TogetherTextGenerationTask(),
+ },
+}
+
+
+def get_provider_helper(provider: PROVIDER_T, task: str) -> TaskProviderHelper:
+ """Get provider helper instance by name and task.
+
+ Args:
+ provider (str): Name of the provider
+ task (str): Name of the task
+
+ Returns:
+ TaskProviderHelper: Helper instance for the specified provider and task
+
+ Raises:
+ ValueError: If provider or task is not supported
+ """
+ if provider not in PROVIDERS:
+ raise ValueError(f"Provider '{provider}' not supported. Available providers: {list(PROVIDERS.keys())}")
+ if task not in PROVIDERS[provider]:
+ raise ValueError(
+ f"Task '{task}' not supported for provider '{provider}'. "
+ f"Available tasks: {list(PROVIDERS[provider].keys())}"
+ )
+ return PROVIDERS[provider][task]
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/_common.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2344b07827ee32be6d3494b390cbf3f76d52749
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/_common.py
@@ -0,0 +1,239 @@
+from functools import lru_cache
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub import constants
+from huggingface_hub.inference._common import RequestParameters
+from huggingface_hub.utils import build_hf_headers, get_token, logging
+
+
+logger = logging.get_logger(__name__)
+
+
+# Dev purposes only.
+# If you want to try to run inference for a new model locally before it's registered on huggingface.co
+# for a given Inference Provider, you can add it to the following dictionary.
+HARDCODED_MODEL_ID_MAPPING: Dict[str, Dict[str, str]] = {
+ # "HF model ID" => "Model ID on Inference Provider's side"
+ #
+ # Example:
+ # "Qwen/Qwen2.5-Coder-32B-Instruct": "Qwen2.5-Coder-32B-Instruct",
+ "fal-ai": {},
+ "fireworks-ai": {},
+ "hf-inference": {},
+ "hyperbolic": {},
+ "nebius": {},
+ "replicate": {},
+ "sambanova": {},
+ "together": {},
+}
+
+
+def filter_none(d: Dict[str, Any]) -> Dict[str, Any]:
+ return {k: v for k, v in d.items() if v is not None}
+
+
+class TaskProviderHelper:
+ """Base class for task-specific provider helpers."""
+
+ def __init__(self, provider: str, base_url: str, task: str) -> None:
+ self.provider = provider
+ self.task = task
+ self.base_url = base_url
+
+ def prepare_request(
+ self,
+ *,
+ inputs: Any,
+ parameters: Dict[str, Any],
+ headers: Dict,
+ model: Optional[str],
+ api_key: Optional[str],
+ extra_payload: Optional[Dict[str, Any]] = None,
+ ) -> RequestParameters:
+ """
+ Prepare the request to be sent to the provider.
+
+ Each step (api_key, model, headers, url, payload) can be customized in subclasses.
+ """
+ # api_key from user, or local token, or raise error
+ api_key = self._prepare_api_key(api_key)
+
+ # mapped model from HF model ID
+ mapped_model = self._prepare_mapped_model(model)
+
+ # default HF headers + user headers (to customize in subclasses)
+ headers = self._prepare_headers(headers, api_key)
+
+ # routed URL if HF token, or direct URL (to customize in '_prepare_route' in subclasses)
+ url = self._prepare_url(api_key, mapped_model)
+
+ # prepare payload (to customize in subclasses)
+ payload = self._prepare_payload_as_dict(inputs, parameters, mapped_model=mapped_model)
+ if payload is not None:
+ payload = recursive_merge(payload, extra_payload or {})
+
+ # body data (to customize in subclasses)
+ data = self._prepare_payload_as_bytes(inputs, parameters, mapped_model, extra_payload)
+
+ # check if both payload and data are set and return
+ if payload is not None and data is not None:
+ raise ValueError("Both payload and data cannot be set in the same request.")
+ if payload is None and data is None:
+ raise ValueError("Either payload or data must be set in the request.")
+ return RequestParameters(url=url, task=self.task, model=mapped_model, json=payload, data=data, headers=headers)
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ """
+ Return the response in the expected format.
+
+ Override this method in subclasses for customized response handling."""
+ return response
+
+ def _prepare_api_key(self, api_key: Optional[str]) -> str:
+ """Return the API key to use for the request.
+
+ Usually not overwritten in subclasses."""
+ if api_key is None:
+ api_key = get_token()
+ if api_key is None:
+ raise ValueError(
+ f"You must provide an api_key to work with {self.provider} API or log in with `huggingface-cli login`."
+ )
+ return api_key
+
+ def _prepare_mapped_model(self, model: Optional[str]) -> str:
+ """Return the mapped model ID to use for the request.
+
+ Usually not overwritten in subclasses."""
+ if model is None:
+ raise ValueError(f"Please provide an HF model ID supported by {self.provider}.")
+
+ # hardcoded mapping for local testing
+ if HARDCODED_MODEL_ID_MAPPING.get(self.provider, {}).get(model):
+ return HARDCODED_MODEL_ID_MAPPING[self.provider][model]
+
+ provider_mapping = _fetch_inference_provider_mapping(model).get(self.provider)
+ if provider_mapping is None:
+ raise ValueError(f"Model {model} is not supported by provider {self.provider}.")
+
+ if provider_mapping.task != self.task:
+ raise ValueError(
+ f"Model {model} is not supported for task {self.task} and provider {self.provider}. "
+ f"Supported task: {provider_mapping.task}."
+ )
+
+ if provider_mapping.status == "staging":
+ logger.warning(
+ f"Model {model} is in staging mode for provider {self.provider}. Meant for test purposes only."
+ )
+ return provider_mapping.provider_id
+
+ def _prepare_headers(self, headers: Dict, api_key: str) -> Dict:
+ """Return the headers to use for the request.
+
+ Override this method in subclasses for customized headers.
+ """
+ return {**build_hf_headers(token=api_key), **headers}
+
+ def _prepare_url(self, api_key: str, mapped_model: str) -> str:
+ """Return the URL to use for the request.
+
+ Usually not overwritten in subclasses."""
+ base_url = self._prepare_base_url(api_key)
+ route = self._prepare_route(mapped_model)
+ return f"{base_url.rstrip('/')}/{route.lstrip('/')}"
+
+ def _prepare_base_url(self, api_key: str) -> str:
+ """Return the base URL to use for the request.
+
+ Usually not overwritten in subclasses."""
+ # Route to the proxy if the api_key is a HF TOKEN
+ if api_key.startswith("hf_"):
+ logger.info(f"Calling '{self.provider}' provider through Hugging Face router.")
+ return constants.INFERENCE_PROXY_TEMPLATE.format(provider=self.provider)
+ else:
+ logger.info(f"Calling '{self.provider}' provider directly.")
+ return self.base_url
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ """Return the route to use for the request.
+
+ Override this method in subclasses for customized routes.
+ """
+ return ""
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ """Return the payload to use for the request, as a dict.
+
+ Override this method in subclasses for customized payloads.
+ Only one of `_prepare_payload_as_dict` and `_prepare_payload_as_bytes` should return a value.
+ """
+ return None
+
+ def _prepare_payload_as_bytes(
+ self, inputs: Any, parameters: Dict, mapped_model: str, extra_payload: Optional[Dict]
+ ) -> Optional[bytes]:
+ """Return the body to use for the request, as bytes.
+
+ Override this method in subclasses for customized body data.
+ Only one of `_prepare_payload_as_dict` and `_prepare_payload_as_bytes` should return a value.
+ """
+ return None
+
+
+class BaseConversationalTask(TaskProviderHelper):
+ """
+ Base class for conversational (chat completion) tasks.
+ The schema follows the OpenAI API format defined here: https://platform.openai.com/docs/api-reference/chat
+ """
+
+ def __init__(self, provider: str, base_url: str):
+ super().__init__(provider=provider, base_url=base_url, task="conversational")
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ return "/v1/chat/completions"
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ return {"messages": inputs, **filter_none(parameters), "model": mapped_model}
+
+
+class BaseTextGenerationTask(TaskProviderHelper):
+ """
+ Base class for text-generation (completion) tasks.
+ The schema follows the OpenAI API format defined here: https://platform.openai.com/docs/api-reference/completions
+ """
+
+ def __init__(self, provider: str, base_url: str):
+ super().__init__(provider=provider, base_url=base_url, task="text-generation")
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ return "/v1/completions"
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ return {"prompt": inputs, **filter_none(parameters), "model": mapped_model}
+
+
+@lru_cache(maxsize=None)
+def _fetch_inference_provider_mapping(model: str) -> Dict:
+ """
+ Fetch provider mappings for a model from the Hub.
+ """
+ from huggingface_hub.hf_api import HfApi
+
+ info = HfApi().model_info(model, expand=["inferenceProviderMapping"])
+ provider_mapping = info.inference_provider_mapping
+ if provider_mapping is None:
+ raise ValueError(f"No provider mapping found for model {model}")
+ return provider_mapping
+
+
+def recursive_merge(dict1: Dict, dict2: Dict) -> Dict:
+ return {
+ **dict1,
+ **{
+ key: recursive_merge(dict1[key], value)
+ if (key in dict1 and isinstance(dict1[key], dict) and isinstance(value, dict))
+ else value
+ for key, value in dict2.items()
+ },
+ }
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/black_forest_labs.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/black_forest_labs.py
new file mode 100644
index 0000000000000000000000000000000000000000..14d8eb3dc1cbc8adab7bb181032710f81de38c8e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/black_forest_labs.py
@@ -0,0 +1,66 @@
+import time
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub.inference._common import _as_dict
+from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
+from huggingface_hub.utils import logging
+from huggingface_hub.utils._http import get_session
+
+
+logger = logging.get_logger(__name__)
+
+MAX_POLLING_ATTEMPTS = 6
+POLLING_INTERVAL = 1.0
+
+
+class BlackForestLabsTextToImageTask(TaskProviderHelper):
+ def __init__(self):
+ super().__init__(provider="black-forest-labs", base_url="https://api.us1.bfl.ai/v1", task="text-to-image")
+
+ def _prepare_headers(self, headers: Dict, api_key: str) -> Dict:
+ headers = super()._prepare_headers(headers, api_key)
+ if not api_key.startswith("hf_"):
+ _ = headers.pop("authorization")
+ headers["X-Key"] = api_key
+ return headers
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ return mapped_model
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ parameters = filter_none(parameters)
+ if "num_inference_steps" in parameters:
+ parameters["steps"] = parameters.pop("num_inference_steps")
+ if "guidance_scale" in parameters:
+ parameters["guidance"] = parameters.pop("guidance_scale")
+
+ return {"prompt": inputs, **parameters}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ """
+ Polling mechanism for Black Forest Labs since the API is asynchronous.
+ """
+ url = _as_dict(response).get("polling_url")
+ session = get_session()
+ for _ in range(MAX_POLLING_ATTEMPTS):
+ time.sleep(POLLING_INTERVAL)
+
+ response = session.get(url, headers={"Content-Type": "application/json"}) # type: ignore
+ response.raise_for_status() # type: ignore
+ response_json: Dict = response.json() # type: ignore
+ status = response_json.get("status")
+ logger.info(
+ f"Polling generation result from {url}. Current status: {status}. "
+ f"Will retry after {POLLING_INTERVAL} seconds if not ready."
+ )
+
+ if (
+ status == "Ready"
+ and isinstance(response_json.get("result"), dict)
+ and (sample_url := response_json["result"].get("sample"))
+ ):
+ image_resp = session.get(sample_url)
+ image_resp.raise_for_status()
+ return image_resp.content
+
+ raise TimeoutError(f"Failed to get the image URL after {MAX_POLLING_ATTEMPTS} attempts.")
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/fal_ai.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/fal_ai.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6d2e4bd8fbb3ea19bfb419ed9f64507997b048c
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/fal_ai.py
@@ -0,0 +1,90 @@
+import base64
+from abc import ABC
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub.inference._common import _as_dict
+from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
+from huggingface_hub.utils import get_session
+
+
+class FalAITask(TaskProviderHelper, ABC):
+ def __init__(self, task: str):
+ super().__init__(provider="fal-ai", base_url="https://fal.run", task=task)
+
+ def _prepare_headers(self, headers: Dict, api_key: str) -> Dict:
+ headers = super()._prepare_headers(headers, api_key)
+ if not api_key.startswith("hf_"):
+ headers["authorization"] = f"Key {api_key}"
+ return headers
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ return f"/{mapped_model}"
+
+
+class FalAIAutomaticSpeechRecognitionTask(FalAITask):
+ def __init__(self):
+ super().__init__("automatic-speech-recognition")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ if isinstance(inputs, str) and inputs.startswith(("http://", "https://")):
+ # If input is a URL, pass it directly
+ audio_url = inputs
+ else:
+ # If input is a file path, read it first
+ if isinstance(inputs, str):
+ with open(inputs, "rb") as f:
+ inputs = f.read()
+
+ audio_b64 = base64.b64encode(inputs).decode()
+ content_type = "audio/mpeg"
+ audio_url = f"data:{content_type};base64,{audio_b64}"
+
+ return {"audio_url": audio_url, **filter_none(parameters)}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ text = _as_dict(response)["text"]
+ if not isinstance(text, str):
+ raise ValueError(f"Unexpected output format from FalAI API. Expected string, got {type(text)}.")
+ return text
+
+
+class FalAITextToImageTask(FalAITask):
+ def __init__(self):
+ super().__init__("text-to-image")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ parameters = filter_none(parameters)
+ if "width" in parameters and "height" in parameters:
+ parameters["image_size"] = {
+ "width": parameters.pop("width"),
+ "height": parameters.pop("height"),
+ }
+ return {"prompt": inputs, **parameters}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ url = _as_dict(response)["images"][0]["url"]
+ return get_session().get(url).content
+
+
+class FalAITextToSpeechTask(FalAITask):
+ def __init__(self):
+ super().__init__("text-to-speech")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ return {"lyrics": inputs, **filter_none(parameters)}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ url = _as_dict(response)["audio"]["url"]
+ return get_session().get(url).content
+
+
+class FalAITextToVideoTask(FalAITask):
+ def __init__(self):
+ super().__init__("text-to-video")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ return {"prompt": inputs, **filter_none(parameters)}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ url = _as_dict(response)["video"]["url"]
+ return get_session().get(url).content
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/fireworks_ai.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/fireworks_ai.py
new file mode 100644
index 0000000000000000000000000000000000000000..bac95c29a7af190dd3bf631cf8cefc9157739334
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/fireworks_ai.py
@@ -0,0 +1,6 @@
+from ._common import BaseConversationalTask
+
+
+class FireworksAIConversationalTask(BaseConversationalTask):
+ def __init__(self):
+ super().__init__(provider="fireworks-ai", base_url="https://api.fireworks.ai/inference")
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/hf_inference.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/hf_inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb1caee04b787479c438bbfc7085ff29671a1d41
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/hf_inference.py
@@ -0,0 +1,118 @@
+import json
+from functools import lru_cache
+from pathlib import Path
+from typing import Any, Dict, Optional
+
+from huggingface_hub import constants
+from huggingface_hub.inference._common import _b64_encode, _open_as_binary
+from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
+from huggingface_hub.utils import build_hf_headers, get_session, get_token, hf_raise_for_status
+
+
+class HFInferenceTask(TaskProviderHelper):
+ """Base class for HF Inference API tasks."""
+
+ def __init__(self, task: str):
+ super().__init__(
+ provider="hf-inference",
+ base_url=constants.INFERENCE_PROXY_TEMPLATE.format(provider="hf-inference"),
+ task=task,
+ )
+
+ def _prepare_api_key(self, api_key: Optional[str]) -> str:
+ # special case: for HF Inference we allow not providing an API key
+ return api_key or get_token() # type: ignore[return-value]
+
+ def _prepare_mapped_model(self, model: Optional[str]) -> str:
+ if model is not None:
+ return model
+ model = _fetch_recommended_models().get(self.task)
+ if model is None:
+ raise ValueError(
+ f"Task {self.task} has no recommended model for HF Inference. Please specify a model"
+ " explicitly. Visit https://huggingface.co/tasks for more info."
+ )
+ return model
+
+ def _prepare_url(self, api_key: str, mapped_model: str) -> str:
+ # hf-inference provider can handle URLs (e.g. Inference Endpoints or TGI deployment)
+ if mapped_model.startswith(("http://", "https://")):
+ return mapped_model
+ return (
+ # Feature-extraction and sentence-similarity are the only cases where we handle models with several tasks.
+ f"{self.base_url}/pipeline/{self.task}/{mapped_model}"
+ if self.task in ("feature-extraction", "sentence-similarity")
+ # Otherwise, we use the default endpoint
+ else f"{self.base_url}/models/{mapped_model}"
+ )
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ if isinstance(inputs, bytes):
+ raise ValueError(f"Unexpected binary input for task {self.task}.")
+ if isinstance(inputs, Path):
+ raise ValueError(f"Unexpected path input for task {self.task} (got {inputs})")
+ return {"inputs": inputs, "parameters": filter_none(parameters)}
+
+
+class HFInferenceBinaryInputTask(HFInferenceTask):
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ return None
+
+ def _prepare_payload_as_bytes(
+ self, inputs: Any, parameters: Dict, mapped_model: str, extra_payload: Optional[Dict]
+ ) -> Optional[bytes]:
+ parameters = filter_none({k: v for k, v in parameters.items() if v is not None})
+ extra_payload = extra_payload or {}
+ has_parameters = len(parameters) > 0 or len(extra_payload) > 0
+
+ # Raise if not a binary object or a local path or a URL.
+ if not isinstance(inputs, (bytes, Path)) and not isinstance(inputs, str):
+ raise ValueError(f"Expected binary inputs or a local path or a URL. Got {inputs}")
+
+ # Send inputs as raw content when no parameters are provided
+ if not has_parameters:
+ with _open_as_binary(inputs) as data:
+ data_as_bytes = data if isinstance(data, bytes) else data.read()
+ return data_as_bytes
+
+ # Otherwise encode as b64
+ return json.dumps({"inputs": _b64_encode(inputs), "parameters": parameters, **extra_payload}).encode("utf-8")
+
+
+class HFInferenceConversational(HFInferenceTask):
+ def __init__(self):
+ super().__init__("text-generation")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ payload_model = "tgi" if mapped_model.startswith(("http://", "https://")) else mapped_model
+ return {**filter_none(parameters), "model": payload_model, "messages": inputs}
+
+ def _prepare_url(self, api_key: str, mapped_model: str) -> str:
+ base_url = (
+ mapped_model
+ if mapped_model.startswith(("http://", "https://"))
+ else f"{constants.INFERENCE_PROXY_TEMPLATE.format(provider='hf-inference')}/models/{mapped_model}"
+ )
+ return _build_chat_completion_url(base_url)
+
+
+def _build_chat_completion_url(model_url: str) -> str:
+ # Strip trailing /
+ model_url = model_url.rstrip("/")
+
+ # Append /chat/completions if not already present
+ if model_url.endswith("/v1"):
+ model_url += "/chat/completions"
+
+ # Append /v1/chat/completions if not already present
+ if not model_url.endswith("/chat/completions"):
+ model_url += "/v1/chat/completions"
+
+ return model_url
+
+
+@lru_cache(maxsize=1)
+def _fetch_recommended_models() -> Dict[str, Optional[str]]:
+ response = get_session().get(f"{constants.ENDPOINT}/api/tasks", headers=build_hf_headers())
+ hf_raise_for_status(response)
+ return {task: next(iter(details["widgetModels"]), None) for task, details in response.json().items()}
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/hyperbolic.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/hyperbolic.py
new file mode 100644
index 0000000000000000000000000000000000000000..919a38182e8aa311f6d287d7e62410a1f9b8dbb5
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/hyperbolic.py
@@ -0,0 +1,43 @@
+import base64
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub.inference._common import _as_dict
+from huggingface_hub.inference._providers._common import BaseConversationalTask, TaskProviderHelper, filter_none
+
+
+class HyperbolicTextToImageTask(TaskProviderHelper):
+ def __init__(self):
+ super().__init__(provider="hyperbolic", base_url="https://api.hyperbolic.xyz", task="text-to-image")
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ return "/v1/images/generations"
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ parameters = filter_none(parameters)
+ if "num_inference_steps" in parameters:
+ parameters["steps"] = parameters.pop("num_inference_steps")
+ if "guidance_scale" in parameters:
+ parameters["cfg_scale"] = parameters.pop("guidance_scale")
+ # For Hyperbolic, the width and height are required parameters
+ if "width" not in parameters:
+ parameters["width"] = 512
+ if "height" not in parameters:
+ parameters["height"] = 512
+ return {"prompt": inputs, "model_name": mapped_model, **parameters}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ response_dict = _as_dict(response)
+ return base64.b64decode(response_dict["images"][0]["image"])
+
+
+class HyperbolicTextGenerationTask(BaseConversationalTask):
+ """
+ Special case for Hyperbolic, where text-generation task is handled as a conversational task.
+ """
+
+ def __init__(self, task: str):
+ super().__init__(
+ provider="hyperbolic",
+ base_url="https://api.hyperbolic.xyz",
+ )
+ self.task = task
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/nebius.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/nebius.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6b37356a361ea2b0c87e1bc2832c1bde7b15a73
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/nebius.py
@@ -0,0 +1,41 @@
+import base64
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub.inference._common import _as_dict
+from huggingface_hub.inference._providers._common import (
+ BaseConversationalTask,
+ BaseTextGenerationTask,
+ TaskProviderHelper,
+ filter_none,
+)
+
+
+class NebiusTextGenerationTask(BaseTextGenerationTask):
+ def __init__(self):
+ super().__init__(provider="nebius", base_url="https://api.studio.nebius.ai")
+
+
+class NebiusConversationalTask(BaseConversationalTask):
+ def __init__(self):
+ super().__init__(provider="nebius", base_url="https://api.studio.nebius.ai")
+
+
+class NebiusTextToImageTask(TaskProviderHelper):
+ def __init__(self):
+ super().__init__(task="text-to-image", provider="nebius", base_url="https://api.studio.nebius.ai")
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ return "/v1/images/generations"
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ parameters = filter_none(parameters)
+ if "guidance_scale" in parameters:
+ parameters.pop("guidance_scale")
+ if parameters.get("response_format") not in ("b64_json", "url"):
+ parameters["response_format"] = "b64_json"
+
+ return {"prompt": inputs, **parameters, "model": mapped_model}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ response_dict = _as_dict(response)
+ return base64.b64decode(response_dict["data"][0]["b64_json"])
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/novita.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/novita.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fc836a3520a0203a6f3a801bad5dfe4570e5431
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/novita.py
@@ -0,0 +1,26 @@
+from huggingface_hub.inference._providers._common import (
+ BaseConversationalTask,
+ BaseTextGenerationTask,
+)
+
+
+_PROVIDER = "novita"
+_BASE_URL = "https://api.novita.ai/v3/openai"
+
+
+class NovitaTextGenerationTask(BaseTextGenerationTask):
+ def __init__(self):
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ # there is no v1/ route for novita
+ return "/completions"
+
+
+class NovitaConversationalTask(BaseConversationalTask):
+ def __init__(self):
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ # there is no v1/ route for novita
+ return "/chat/completions"
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/replicate.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/replicate.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc84f69f37290b3f4c88818d464999860a6a48e8
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/replicate.py
@@ -0,0 +1,53 @@
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub.inference._common import _as_dict
+from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
+from huggingface_hub.utils import get_session
+
+
+_PROVIDER = "replicate"
+_BASE_URL = "https://api.replicate.com"
+
+
+class ReplicateTask(TaskProviderHelper):
+ def __init__(self, task: str):
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task)
+
+ def _prepare_headers(self, headers: Dict, api_key: str) -> Dict:
+ headers = super()._prepare_headers(headers, api_key)
+ headers["Prefer"] = "wait"
+ return headers
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ if ":" in mapped_model:
+ return "/v1/predictions"
+ return f"/v1/models/{mapped_model}/predictions"
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ payload: Dict[str, Any] = {"input": {"prompt": inputs, **filter_none(parameters)}}
+ if ":" in mapped_model:
+ version = mapped_model.split(":", 1)[1]
+ payload["version"] = version
+ return payload
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ response_dict = _as_dict(response)
+ if response_dict.get("output") is None:
+ raise TimeoutError(
+ f"Inference request timed out after 60 seconds. No output generated for model {response_dict.get('model')}"
+ "The model might be in cold state or starting up. Please try again later."
+ )
+ output_url = (
+ response_dict["output"] if isinstance(response_dict["output"], str) else response_dict["output"][0]
+ )
+ return get_session().get(output_url).content
+
+
+class ReplicateTextToSpeechTask(ReplicateTask):
+ def __init__(self):
+ super().__init__("text-to-speech")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ payload: Dict = super()._prepare_payload_as_dict(inputs, parameters, mapped_model) # type: ignore[assignment]
+ payload["input"]["text"] = payload["input"].pop("prompt") # rename "prompt" to "text" for TTS
+ return payload
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/sambanova.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/sambanova.py
new file mode 100644
index 0000000000000000000000000000000000000000..3678e942ab31e96210b4542ff7191ac19122642d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/sambanova.py
@@ -0,0 +1,6 @@
+from huggingface_hub.inference._providers._common import BaseConversationalTask
+
+
+class SambanovaConversationalTask(BaseConversationalTask):
+ def __init__(self):
+ super().__init__(provider="sambanova", base_url="https://api.sambanova.ai")
diff --git a/env/Lib/site-packages/huggingface_hub/inference/_providers/together.py b/env/Lib/site-packages/huggingface_hub/inference/_providers/together.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e2c1eb48970af75b5a6ad6d7df8f2fc68b8ed5e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference/_providers/together.py
@@ -0,0 +1,59 @@
+import base64
+from abc import ABC
+from typing import Any, Dict, Optional, Union
+
+from huggingface_hub.inference._common import _as_dict
+from huggingface_hub.inference._providers._common import (
+ BaseConversationalTask,
+ BaseTextGenerationTask,
+ TaskProviderHelper,
+ filter_none,
+)
+
+
+_PROVIDER = "together"
+_BASE_URL = "https://api.together.xyz"
+
+
+class TogetherTask(TaskProviderHelper, ABC):
+ """Base class for Together API tasks."""
+
+ def __init__(self, task: str):
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task)
+
+ def _prepare_route(self, mapped_model: str) -> str:
+ if self.task == "text-to-image":
+ return "/v1/images/generations"
+ elif self.task == "conversational":
+ return "/v1/chat/completions"
+ elif self.task == "text-generation":
+ return "/v1/completions"
+ raise ValueError(f"Unsupported task '{self.task}' for Together API.")
+
+
+class TogetherTextGenerationTask(BaseTextGenerationTask):
+ def __init__(self):
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
+
+
+class TogetherConversationalTask(BaseConversationalTask):
+ def __init__(self):
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
+
+
+class TogetherTextToImageTask(TogetherTask):
+ def __init__(self):
+ super().__init__("text-to-image")
+
+ def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
+ parameters = filter_none(parameters)
+ if "num_inference_steps" in parameters:
+ parameters["steps"] = parameters.pop("num_inference_steps")
+ if "guidance_scale" in parameters:
+ parameters["guidance"] = parameters.pop("guidance_scale")
+
+ return {"prompt": inputs, "response_format": "base64", **parameters, "model": mapped_model}
+
+ def get_response(self, response: Union[bytes, Dict]) -> Any:
+ response_dict = _as_dict(response)
+ return base64.b64decode(response_dict["data"][0]["b64_json"])
diff --git a/env/Lib/site-packages/huggingface_hub/inference_api.py b/env/Lib/site-packages/huggingface_hub/inference_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..f895fcc61c3867838b013ecd3f6789cbc010b5b3
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/inference_api.py
@@ -0,0 +1,217 @@
+import io
+from typing import Any, Dict, List, Optional, Union
+
+from . import constants
+from .hf_api import HfApi
+from .utils import build_hf_headers, get_session, is_pillow_available, logging, validate_hf_hub_args
+from .utils._deprecation import _deprecate_method
+
+
+logger = logging.get_logger(__name__)
+
+
+ALL_TASKS = [
+ # NLP
+ "text-classification",
+ "token-classification",
+ "table-question-answering",
+ "question-answering",
+ "zero-shot-classification",
+ "translation",
+ "summarization",
+ "conversational",
+ "feature-extraction",
+ "text-generation",
+ "text2text-generation",
+ "fill-mask",
+ "sentence-similarity",
+ # Audio
+ "text-to-speech",
+ "automatic-speech-recognition",
+ "audio-to-audio",
+ "audio-classification",
+ "voice-activity-detection",
+ # Computer vision
+ "image-classification",
+ "object-detection",
+ "image-segmentation",
+ "text-to-image",
+ "image-to-image",
+ # Others
+ "tabular-classification",
+ "tabular-regression",
+]
+
+
+class InferenceApi:
+ """Client to configure requests and make calls to the HuggingFace Inference API.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub.inference_api import InferenceApi
+
+ >>> # Mask-fill example
+ >>> inference = InferenceApi("bert-base-uncased")
+ >>> inference(inputs="The goal of life is [MASK].")
+ [{'sequence': 'the goal of life is life.', 'score': 0.10933292657136917, 'token': 2166, 'token_str': 'life'}]
+
+ >>> # Question Answering example
+ >>> inference = InferenceApi("deepset/roberta-base-squad2")
+ >>> inputs = {
+ ... "question": "What's my name?",
+ ... "context": "My name is Clara and I live in Berkeley.",
+ ... }
+ >>> inference(inputs)
+ {'score': 0.9326569437980652, 'start': 11, 'end': 16, 'answer': 'Clara'}
+
+ >>> # Zero-shot example
+ >>> inference = InferenceApi("typeform/distilbert-base-uncased-mnli")
+ >>> inputs = "Hi, I recently bought a device from your company but it is not working as advertised and I would like to get reimbursed!"
+ >>> params = {"candidate_labels": ["refund", "legal", "faq"]}
+ >>> inference(inputs, params)
+ {'sequence': 'Hi, I recently bought a device from your company but it is not working as advertised and I would like to get reimbursed!', 'labels': ['refund', 'faq', 'legal'], 'scores': [0.9378499388694763, 0.04914155602455139, 0.013008488342165947]}
+
+ >>> # Overriding configured task
+ >>> inference = InferenceApi("bert-base-uncased", task="feature-extraction")
+
+ >>> # Text-to-image
+ >>> inference = InferenceApi("stabilityai/stable-diffusion-2-1")
+ >>> inference("cat")
+
+
+ >>> # Return as raw response to parse the output yourself
+ >>> inference = InferenceApi("mio/amadeus")
+ >>> response = inference("hello world", raw_response=True)
+ >>> response.headers
+ {"Content-Type": "audio/flac", ...}
+ >>> response.content # raw bytes from server
+ b'(...)'
+ ```
+ """
+
+ @validate_hf_hub_args
+ @_deprecate_method(
+ version="1.0",
+ message=(
+ "`InferenceApi` client is deprecated in favor of the more feature-complete `InferenceClient`. Check out"
+ " this guide to learn how to convert your script to use it:"
+ " https://huggingface.co/docs/huggingface_hub/guides/inference#legacy-inferenceapi-client."
+ ),
+ )
+ def __init__(
+ self,
+ repo_id: str,
+ task: Optional[str] = None,
+ token: Optional[str] = None,
+ gpu: bool = False,
+ ):
+ """Inits headers and API call information.
+
+ Args:
+ repo_id (``str``):
+ Id of repository (e.g. `user/bert-base-uncased`).
+ task (``str``, `optional`, defaults ``None``):
+ Whether to force a task instead of using task specified in the
+ repository.
+ token (`str`, `optional`):
+ The API token to use as HTTP bearer authorization. This is not
+ the authentication token. You can find the token in
+ https://huggingface.co/settings/token. Alternatively, you can
+ find both your organizations and personal API tokens using
+ `HfApi().whoami(token)`.
+ gpu (`bool`, `optional`, defaults `False`):
+ Whether to use GPU instead of CPU for inference(requires Startup
+ plan at least).
+ """
+ self.options = {"wait_for_model": True, "use_gpu": gpu}
+ self.headers = build_hf_headers(token=token)
+
+ # Configure task
+ model_info = HfApi(token=token).model_info(repo_id=repo_id)
+ if not model_info.pipeline_tag and not task:
+ raise ValueError(
+ "Task not specified in the repository. Please add it to the model card"
+ " using pipeline_tag"
+ " (https://huggingface.co/docs#how-is-a-models-type-of-inference-api-and-widget-determined)"
+ )
+
+ if task and task != model_info.pipeline_tag:
+ if task not in ALL_TASKS:
+ raise ValueError(f"Invalid task {task}. Make sure it's valid.")
+
+ logger.warning(
+ "You're using a different task than the one specified in the"
+ " repository. Be sure to know what you're doing :)"
+ )
+ self.task = task
+ else:
+ assert model_info.pipeline_tag is not None, "Pipeline tag cannot be None"
+ self.task = model_info.pipeline_tag
+
+ self.api_url = f"{constants.INFERENCE_ENDPOINT}/pipeline/{self.task}/{repo_id}"
+
+ def __repr__(self):
+ # Do not add headers to repr to avoid leaking token.
+ return f"InferenceAPI(api_url='{self.api_url}', task='{self.task}', options={self.options})"
+
+ def __call__(
+ self,
+ inputs: Optional[Union[str, Dict, List[str], List[List[str]]]] = None,
+ params: Optional[Dict] = None,
+ data: Optional[bytes] = None,
+ raw_response: bool = False,
+ ) -> Any:
+ """Make a call to the Inference API.
+
+ Args:
+ inputs (`str` or `Dict` or `List[str]` or `List[List[str]]`, *optional*):
+ Inputs for the prediction.
+ params (`Dict`, *optional*):
+ Additional parameters for the models. Will be sent as `parameters` in the
+ payload.
+ data (`bytes`, *optional*):
+ Bytes content of the request. In this case, leave `inputs` and `params` empty.
+ raw_response (`bool`, defaults to `False`):
+ If `True`, the raw `Response` object is returned. You can parse its content
+ as preferred. By default, the content is parsed into a more practical format
+ (json dictionary or PIL Image for example).
+ """
+ # Build payload
+ payload: Dict[str, Any] = {
+ "options": self.options,
+ }
+ if inputs:
+ payload["inputs"] = inputs
+ if params:
+ payload["parameters"] = params
+
+ # Make API call
+ response = get_session().post(self.api_url, headers=self.headers, json=payload, data=data)
+
+ # Let the user handle the response
+ if raw_response:
+ return response
+
+ # By default, parse the response for the user.
+ content_type = response.headers.get("Content-Type") or ""
+ if content_type.startswith("image"):
+ if not is_pillow_available():
+ raise ImportError(
+ f"Task '{self.task}' returned as image but Pillow is not installed."
+ " Please install it (`pip install Pillow`) or pass"
+ " `raw_response=True` to get the raw `Response` object and parse"
+ " the image by yourself."
+ )
+
+ from PIL import Image
+
+ return Image.open(io.BytesIO(response.content))
+ elif content_type == "application/json":
+ return response.json()
+ else:
+ raise NotImplementedError(
+ f"{content_type} output type is not implemented yet. You can pass"
+ " `raw_response=True` to get the raw `Response` object and parse the"
+ " output by yourself."
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/keras_mixin.py b/env/Lib/site-packages/huggingface_hub/keras_mixin.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1c7ad503e2f663b1f36da4c2ef5c954fde35606
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/keras_mixin.py
@@ -0,0 +1,500 @@
+import collections.abc as collections
+import json
+import os
+import warnings
+from functools import wraps
+from pathlib import Path
+from shutil import copytree
+from typing import Any, Dict, List, Optional, Union
+
+from huggingface_hub import ModelHubMixin, snapshot_download
+from huggingface_hub.utils import (
+ get_tf_version,
+ is_graphviz_available,
+ is_pydot_available,
+ is_tf_available,
+ yaml_dump,
+)
+
+from . import constants
+from .hf_api import HfApi
+from .utils import SoftTemporaryDirectory, logging, validate_hf_hub_args
+from .utils._typing import CallableT
+
+
+logger = logging.get_logger(__name__)
+
+keras = None
+if is_tf_available():
+ # Depending on which version of TensorFlow is installed, we need to import
+ # keras from the correct location.
+ # See https://github.com/tensorflow/tensorflow/releases/tag/v2.16.1.
+ # Note: saving a keras model only works with Keras<3.0.
+ try:
+ import tf_keras as keras # type: ignore
+ except ImportError:
+ import tensorflow as tf # type: ignore
+
+ keras = tf.keras
+
+
+def _requires_keras_2_model(fn: CallableT) -> CallableT:
+ # Wrapper to raise if user tries to save a Keras 3.x model
+ @wraps(fn)
+ def _inner(model, *args, **kwargs):
+ if not hasattr(model, "history"): # hacky way to check if model is Keras 2.x
+ raise NotImplementedError(
+ f"Cannot use '{fn.__name__}': Keras 3.x is not supported."
+ " Please save models manually and upload them using `upload_folder` or `huggingface-cli upload`."
+ )
+ return fn(model, *args, **kwargs)
+
+ return _inner # type: ignore [return-value]
+
+
+def _flatten_dict(dictionary, parent_key=""):
+ """Flatten a nested dictionary.
+ Reference: https://stackoverflow.com/a/6027615/10319735
+
+ Args:
+ dictionary (`dict`):
+ The nested dictionary to be flattened.
+ parent_key (`str`):
+ The parent key to be prefixed to the children keys.
+ Necessary for recursing over the nested dictionary.
+
+ Returns:
+ The flattened dictionary.
+ """
+ items = []
+ for key, value in dictionary.items():
+ new_key = f"{parent_key}.{key}" if parent_key else key
+ if isinstance(value, collections.MutableMapping):
+ items.extend(
+ _flatten_dict(
+ value,
+ new_key,
+ ).items()
+ )
+ else:
+ items.append((new_key, value))
+ return dict(items)
+
+
+def _create_hyperparameter_table(model):
+ """Parse hyperparameter dictionary into a markdown table."""
+ table = None
+ if model.optimizer is not None:
+ optimizer_params = model.optimizer.get_config()
+ # flatten the configuration
+ optimizer_params = _flatten_dict(optimizer_params)
+ optimizer_params["training_precision"] = keras.mixed_precision.global_policy().name
+ table = "| Hyperparameters | Value |\n| :-- | :-- |\n"
+ for key, value in optimizer_params.items():
+ table += f"| {key} | {value} |\n"
+ return table
+
+
+def _plot_network(model, save_directory):
+ keras.utils.plot_model(
+ model,
+ to_file=f"{save_directory}/model.png",
+ show_shapes=False,
+ show_dtype=False,
+ show_layer_names=True,
+ rankdir="TB",
+ expand_nested=False,
+ dpi=96,
+ layer_range=None,
+ )
+
+
+def _create_model_card(
+ model,
+ repo_dir: Path,
+ plot_model: bool = True,
+ metadata: Optional[dict] = None,
+):
+ """
+ Creates a model card for the repository.
+
+ Do not overwrite an existing README.md file.
+ """
+ readme_path = repo_dir / "README.md"
+ if readme_path.exists():
+ return
+
+ hyperparameters = _create_hyperparameter_table(model)
+ if plot_model and is_graphviz_available() and is_pydot_available():
+ _plot_network(model, repo_dir)
+ if metadata is None:
+ metadata = {}
+ metadata["library_name"] = "keras"
+ model_card: str = "---\n"
+ model_card += yaml_dump(metadata, default_flow_style=False)
+ model_card += "---\n"
+ model_card += "\n## Model description\n\nMore information needed\n"
+ model_card += "\n## Intended uses & limitations\n\nMore information needed\n"
+ model_card += "\n## Training and evaluation data\n\nMore information needed\n"
+ if hyperparameters is not None:
+ model_card += "\n## Training procedure\n"
+ model_card += "\n### Training hyperparameters\n"
+ model_card += "\nThe following hyperparameters were used during training:\n\n"
+ model_card += hyperparameters
+ model_card += "\n"
+ if plot_model and os.path.exists(f"{repo_dir}/model.png"):
+ model_card += "\n ## Model Plot\n"
+ model_card += "\n"
+ model_card += "\nView Model Plot\n"
+ path_to_plot = "./model.png"
+ model_card += f"\n\n"
+ model_card += "\n"
+
+ readme_path.write_text(model_card)
+
+
+@_requires_keras_2_model
+def save_pretrained_keras(
+ model,
+ save_directory: Union[str, Path],
+ config: Optional[Dict[str, Any]] = None,
+ include_optimizer: bool = False,
+ plot_model: bool = True,
+ tags: Optional[Union[list, str]] = None,
+ **model_save_kwargs,
+):
+ """
+ Saves a Keras model to save_directory in SavedModel format. Use this if
+ you're using the Functional or Sequential APIs.
+
+ Args:
+ model (`Keras.Model`):
+ The [Keras
+ model](https://www.tensorflow.org/api_docs/python/tf/keras/Model)
+ you'd like to save. The model must be compiled and built.
+ save_directory (`str` or `Path`):
+ Specify directory in which you want to save the Keras model.
+ config (`dict`, *optional*):
+ Configuration object to be saved alongside the model weights.
+ include_optimizer(`bool`, *optional*, defaults to `False`):
+ Whether or not to include optimizer in serialization.
+ plot_model (`bool`, *optional*, defaults to `True`):
+ Setting this to `True` will plot the model and put it in the model
+ card. Requires graphviz and pydot to be installed.
+ tags (Union[`str`,`list`], *optional*):
+ List of tags that are related to model or string of a single tag. See example tags
+ [here](https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1).
+ model_save_kwargs(`dict`, *optional*):
+ model_save_kwargs will be passed to
+ [`tf.keras.models.save_model()`](https://www.tensorflow.org/api_docs/python/tf/keras/models/save_model).
+ """
+ if keras is None:
+ raise ImportError("Called a Tensorflow-specific function but could not import it.")
+
+ if not model.built:
+ raise ValueError("Model should be built before trying to save")
+
+ save_directory = Path(save_directory)
+ save_directory.mkdir(parents=True, exist_ok=True)
+
+ # saving config
+ if config:
+ if not isinstance(config, dict):
+ raise RuntimeError(f"Provided config to save_pretrained_keras should be a dict. Got: '{type(config)}'")
+
+ with (save_directory / constants.CONFIG_NAME).open("w") as f:
+ json.dump(config, f)
+
+ metadata = {}
+ if isinstance(tags, list):
+ metadata["tags"] = tags
+ elif isinstance(tags, str):
+ metadata["tags"] = [tags]
+
+ task_name = model_save_kwargs.pop("task_name", None)
+ if task_name is not None:
+ warnings.warn(
+ "`task_name` input argument is deprecated. Pass `tags` instead.",
+ FutureWarning,
+ )
+ if "tags" in metadata:
+ metadata["tags"].append(task_name)
+ else:
+ metadata["tags"] = [task_name]
+
+ if model.history is not None:
+ if model.history.history != {}:
+ path = save_directory / "history.json"
+ if path.exists():
+ warnings.warn(
+ "`history.json` file already exists, it will be overwritten by the history of this version.",
+ UserWarning,
+ )
+ with path.open("w", encoding="utf-8") as f:
+ json.dump(model.history.history, f, indent=2, sort_keys=True)
+
+ _create_model_card(model, save_directory, plot_model, metadata)
+ keras.models.save_model(model, save_directory, include_optimizer=include_optimizer, **model_save_kwargs)
+
+
+def from_pretrained_keras(*args, **kwargs) -> "KerasModelHubMixin":
+ r"""
+ Instantiate a pretrained Keras model from a pre-trained model from the Hub.
+ The model is expected to be in `SavedModel` format.
+
+ Args:
+ pretrained_model_name_or_path (`str` or `os.PathLike`):
+ Can be either:
+ - A string, the `model id` of a pretrained model hosted inside a
+ model repo on huggingface.co. Valid model ids can be located
+ at the root-level, like `bert-base-uncased`, or namespaced
+ under a user or organization name, like
+ `dbmdz/bert-base-german-cased`.
+ - You can add `revision` by appending `@` at the end of model_id
+ simply like this: `dbmdz/bert-base-german-cased@main` Revision
+ is the specific model version to use. It can be a branch name,
+ a tag name, or a commit id, since we use a git-based system
+ for storing models and other artifacts on huggingface.co, so
+ `revision` can be any identifier allowed by git.
+ - A path to a `directory` containing model weights saved using
+ [`~transformers.PreTrainedModel.save_pretrained`], e.g.,
+ `./my_model_directory/`.
+ - `None` if you are both providing the configuration and state
+ dictionary (resp. with keyword arguments `config` and
+ `state_dict`).
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether to force the (re-)download of the model weights and
+ configuration files, overriding the cached versions if they exist.
+ proxies (`Dict[str, str]`, *optional*):
+ A dictionary of proxy servers to use by protocol or endpoint, e.g.,
+ `{'http': 'foo.bar:3128', 'http://hostname': 'foo.bar:4012'}`. The
+ proxies are used on each request.
+ token (`str` or `bool`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. If
+ `True`, will use the token generated when running `transformers-cli
+ login` (stored in `~/.huggingface`).
+ cache_dir (`Union[str, os.PathLike]`, *optional*):
+ Path to a directory in which a downloaded pretrained model
+ configuration should be cached if the standard cache should not be
+ used.
+ local_files_only(`bool`, *optional*, defaults to `False`):
+ Whether to only look at local files (i.e., do not try to download
+ the model).
+ model_kwargs (`Dict`, *optional*):
+ model_kwargs will be passed to the model during initialization
+
+
+
+ Passing `token=True` is required when you want to use a private
+ model.
+
+
+ """
+ return KerasModelHubMixin.from_pretrained(*args, **kwargs)
+
+
+@validate_hf_hub_args
+@_requires_keras_2_model
+def push_to_hub_keras(
+ model,
+ repo_id: str,
+ *,
+ config: Optional[dict] = None,
+ commit_message: str = "Push Keras model using huggingface_hub.",
+ private: Optional[bool] = None,
+ api_endpoint: Optional[str] = None,
+ token: Optional[str] = None,
+ branch: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ log_dir: Optional[str] = None,
+ include_optimizer: bool = False,
+ tags: Optional[Union[list, str]] = None,
+ plot_model: bool = True,
+ **model_save_kwargs,
+):
+ """
+ Upload model checkpoint to the Hub.
+
+ Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
+ `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
+ details.
+
+ Args:
+ model (`Keras.Model`):
+ The [Keras model](`https://www.tensorflow.org/api_docs/python/tf/keras/Model`) you'd like to push to the
+ Hub. The model must be compiled and built.
+ repo_id (`str`):
+ ID of the repository to push to (example: `"username/my-model"`).
+ commit_message (`str`, *optional*, defaults to "Add Keras model"):
+ Message to commit while pushing.
+ private (`bool`, *optional*):
+ Whether the repository created should be private.
+ If `None` (default), the repo will be public unless the organization's default is private.
+ api_endpoint (`str`, *optional*):
+ The API endpoint to use when pushing the model to the hub.
+ token (`str`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. If
+ not set, will use the token set when logging in with
+ `huggingface-cli login` (stored in `~/.huggingface`).
+ branch (`str`, *optional*):
+ The git branch on which to push the model. This defaults to
+ the default branch as specified in your repository, which
+ defaults to `"main"`.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `branch` with that commit.
+ Defaults to `False`.
+ config (`dict`, *optional*):
+ Configuration object to be saved alongside the model weights.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are pushed.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not pushed.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo.
+ log_dir (`str`, *optional*):
+ TensorBoard logging directory to be pushed. The Hub automatically
+ hosts and displays a TensorBoard instance if log files are included
+ in the repository.
+ include_optimizer (`bool`, *optional*, defaults to `False`):
+ Whether or not to include optimizer during serialization.
+ tags (Union[`list`, `str`], *optional*):
+ List of tags that are related to model or string of a single tag. See example tags
+ [here](https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1).
+ plot_model (`bool`, *optional*, defaults to `True`):
+ Setting this to `True` will plot the model and put it in the model
+ card. Requires graphviz and pydot to be installed.
+ model_save_kwargs(`dict`, *optional*):
+ model_save_kwargs will be passed to
+ [`tf.keras.models.save_model()`](https://www.tensorflow.org/api_docs/python/tf/keras/models/save_model).
+
+ Returns:
+ The url of the commit of your model in the given repository.
+ """
+ api = HfApi(endpoint=api_endpoint)
+ repo_id = api.create_repo(repo_id=repo_id, token=token, private=private, exist_ok=True).repo_id
+
+ # Push the files to the repo in a single commit
+ with SoftTemporaryDirectory() as tmp:
+ saved_path = Path(tmp) / repo_id
+ save_pretrained_keras(
+ model,
+ saved_path,
+ config=config,
+ include_optimizer=include_optimizer,
+ tags=tags,
+ plot_model=plot_model,
+ **model_save_kwargs,
+ )
+
+ # If `log_dir` provided, delete remote logs and upload new ones
+ if log_dir is not None:
+ delete_patterns = (
+ []
+ if delete_patterns is None
+ else (
+ [delete_patterns] # convert `delete_patterns` to a list
+ if isinstance(delete_patterns, str)
+ else delete_patterns
+ )
+ )
+ delete_patterns.append("logs/*")
+ copytree(log_dir, saved_path / "logs")
+
+ return api.upload_folder(
+ repo_type="model",
+ repo_id=repo_id,
+ folder_path=saved_path,
+ commit_message=commit_message,
+ token=token,
+ revision=branch,
+ create_pr=create_pr,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ delete_patterns=delete_patterns,
+ )
+
+
+class KerasModelHubMixin(ModelHubMixin):
+ """
+ Implementation of [`ModelHubMixin`] to provide model Hub upload/download
+ capabilities to Keras models.
+
+
+ ```python
+ >>> import tensorflow as tf
+ >>> from huggingface_hub import KerasModelHubMixin
+
+
+ >>> class MyModel(tf.keras.Model, KerasModelHubMixin):
+ ... def __init__(self, **kwargs):
+ ... super().__init__()
+ ... self.config = kwargs.pop("config", None)
+ ... self.dummy_inputs = ...
+ ... self.layer = ...
+
+ ... def call(self, *args):
+ ... return ...
+
+
+ >>> # Initialize and compile the model as you normally would
+ >>> model = MyModel()
+ >>> model.compile(...)
+ >>> # Build the graph by training it or passing dummy inputs
+ >>> _ = model(model.dummy_inputs)
+ >>> # Save model weights to local directory
+ >>> model.save_pretrained("my-awesome-model")
+ >>> # Push model weights to the Hub
+ >>> model.push_to_hub("my-awesome-model")
+ >>> # Download and initialize weights from the Hub
+ >>> model = MyModel.from_pretrained("username/super-cool-model")
+ ```
+ """
+
+ def _save_pretrained(self, save_directory):
+ save_pretrained_keras(self, save_directory)
+
+ @classmethod
+ def _from_pretrained(
+ cls,
+ model_id,
+ revision,
+ cache_dir,
+ force_download,
+ proxies,
+ resume_download,
+ local_files_only,
+ token,
+ config: Optional[Dict[str, Any]] = None,
+ **model_kwargs,
+ ):
+ """Here we just call [`from_pretrained_keras`] function so both the mixin and
+ functional APIs stay in sync.
+
+ TODO - Some args above aren't used since we are calling
+ snapshot_download instead of hf_hub_download.
+ """
+ if keras is None:
+ raise ImportError("Called a TensorFlow-specific function but could not import it.")
+
+ # Root is either a local filepath matching model_id or a cached snapshot
+ if not os.path.isdir(model_id):
+ storage_folder = snapshot_download(
+ repo_id=model_id,
+ revision=revision,
+ cache_dir=cache_dir,
+ library_name="keras",
+ library_version=get_tf_version(),
+ )
+ else:
+ storage_folder = model_id
+
+ # TODO: change this in a future PR. We are not returning a KerasModelHubMixin instance here...
+ model = keras.models.load_model(storage_folder)
+
+ # For now, we add a new attribute, config, to store the config loaded from the hub/a local dir.
+ model.config = config
+
+ return model
diff --git a/env/Lib/site-packages/huggingface_hub/lfs.py b/env/Lib/site-packages/huggingface_hub/lfs.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2d4f36829dfe941ce60c8b711c1cc912e8c324a
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/lfs.py
@@ -0,0 +1,460 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Git LFS related type definitions and utilities"""
+
+import inspect
+import io
+import re
+import warnings
+from dataclasses import dataclass
+from math import ceil
+from os.path import getsize
+from pathlib import Path
+from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, Optional, Tuple, TypedDict
+from urllib.parse import unquote
+
+from huggingface_hub import constants
+
+from .utils import (
+ build_hf_headers,
+ fix_hf_endpoint_in_url,
+ get_session,
+ hf_raise_for_status,
+ http_backoff,
+ logging,
+ tqdm,
+ validate_hf_hub_args,
+)
+from .utils._lfs import SliceFileObj
+from .utils.sha import sha256, sha_fileobj
+from .utils.tqdm import is_tqdm_disabled
+
+
+if TYPE_CHECKING:
+ from ._commit_api import CommitOperationAdd
+
+logger = logging.get_logger(__name__)
+
+OID_REGEX = re.compile(r"^[0-9a-f]{40}$")
+
+LFS_MULTIPART_UPLOAD_COMMAND = "lfs-multipart-upload"
+
+LFS_HEADERS = {
+ "Accept": "application/vnd.git-lfs+json",
+ "Content-Type": "application/vnd.git-lfs+json",
+}
+
+
+@dataclass
+class UploadInfo:
+ """
+ Dataclass holding required information to determine whether a blob
+ should be uploaded to the hub using the LFS protocol or the regular protocol
+
+ Args:
+ sha256 (`bytes`):
+ SHA256 hash of the blob
+ size (`int`):
+ Size in bytes of the blob
+ sample (`bytes`):
+ First 512 bytes of the blob
+ """
+
+ sha256: bytes
+ size: int
+ sample: bytes
+
+ @classmethod
+ def from_path(cls, path: str):
+ size = getsize(path)
+ with io.open(path, "rb") as file:
+ sample = file.peek(512)[:512]
+ sha = sha_fileobj(file)
+ return cls(size=size, sha256=sha, sample=sample)
+
+ @classmethod
+ def from_bytes(cls, data: bytes):
+ sha = sha256(data).digest()
+ return cls(size=len(data), sample=data[:512], sha256=sha)
+
+ @classmethod
+ def from_fileobj(cls, fileobj: BinaryIO):
+ sample = fileobj.read(512)
+ fileobj.seek(0, io.SEEK_SET)
+ sha = sha_fileobj(fileobj)
+ size = fileobj.tell()
+ fileobj.seek(0, io.SEEK_SET)
+ return cls(size=size, sha256=sha, sample=sample)
+
+
+@validate_hf_hub_args
+def post_lfs_batch_info(
+ upload_infos: Iterable[UploadInfo],
+ token: Optional[str],
+ repo_type: str,
+ repo_id: str,
+ revision: Optional[str] = None,
+ endpoint: Optional[str] = None,
+ headers: Optional[Dict[str, str]] = None,
+) -> Tuple[List[dict], List[dict]]:
+ """
+ Requests the LFS batch endpoint to retrieve upload instructions
+
+ Learn more: https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md
+
+ Args:
+ upload_infos (`Iterable` of `UploadInfo`):
+ `UploadInfo` for the files that are being uploaded, typically obtained
+ from `CommitOperationAdd.upload_info`
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The git revision to upload to.
+ headers (`dict`, *optional*):
+ Additional headers to include in the request
+
+ Returns:
+ `LfsBatchInfo`: 2-tuple:
+ - First element is the list of upload instructions from the server
+ - Second element is an list of errors, if any
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If an argument is invalid or the server response is malformed.
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ If the server returned an error.
+ """
+ endpoint = endpoint if endpoint is not None else constants.ENDPOINT
+ url_prefix = ""
+ if repo_type in constants.REPO_TYPES_URL_PREFIXES:
+ url_prefix = constants.REPO_TYPES_URL_PREFIXES[repo_type]
+ batch_url = f"{endpoint}/{url_prefix}{repo_id}.git/info/lfs/objects/batch"
+ payload: Dict = {
+ "operation": "upload",
+ "transfers": ["basic", "multipart"],
+ "objects": [
+ {
+ "oid": upload.sha256.hex(),
+ "size": upload.size,
+ }
+ for upload in upload_infos
+ ],
+ "hash_algo": "sha256",
+ }
+ if revision is not None:
+ payload["ref"] = {"name": unquote(revision)} # revision has been previously 'quoted'
+
+ headers = {
+ **LFS_HEADERS,
+ **build_hf_headers(token=token),
+ **(headers or {}),
+ }
+ resp = get_session().post(batch_url, headers=headers, json=payload)
+ hf_raise_for_status(resp)
+ batch_info = resp.json()
+
+ objects = batch_info.get("objects", None)
+ if not isinstance(objects, list):
+ raise ValueError("Malformed response from server")
+
+ return (
+ [_validate_batch_actions(obj) for obj in objects if "error" not in obj],
+ [_validate_batch_error(obj) for obj in objects if "error" in obj],
+ )
+
+
+class PayloadPartT(TypedDict):
+ partNumber: int
+ etag: str
+
+
+class CompletionPayloadT(TypedDict):
+ """Payload that will be sent to the Hub when uploading multi-part."""
+
+ oid: str
+ parts: List[PayloadPartT]
+
+
+def lfs_upload(
+ operation: "CommitOperationAdd",
+ lfs_batch_action: Dict,
+ token: Optional[str] = None,
+ headers: Optional[Dict[str, str]] = None,
+ endpoint: Optional[str] = None,
+) -> None:
+ """
+ Handles uploading a given object to the Hub with the LFS protocol.
+
+ Can be a No-op if the content of the file is already present on the hub large file storage.
+
+ Args:
+ operation (`CommitOperationAdd`):
+ The add operation triggering this upload.
+ lfs_batch_action (`dict`):
+ Upload instructions from the LFS batch endpoint for this object. See [`~utils.lfs.post_lfs_batch_info`] for
+ more details.
+ headers (`dict`, *optional*):
+ Headers to include in the request, including authentication and user agent headers.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `lfs_batch_action` is improperly formatted
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ If the upload resulted in an error
+ """
+ # 0. If LFS file is already present, skip upload
+ _validate_batch_actions(lfs_batch_action)
+ actions = lfs_batch_action.get("actions")
+ if actions is None:
+ # The file was already uploaded
+ logger.debug(f"Content of file {operation.path_in_repo} is already present upstream - skipping upload")
+ return
+
+ # 1. Validate server response (check required keys in dict)
+ upload_action = lfs_batch_action["actions"]["upload"]
+ _validate_lfs_action(upload_action)
+ verify_action = lfs_batch_action["actions"].get("verify")
+ if verify_action is not None:
+ _validate_lfs_action(verify_action)
+
+ # 2. Upload file (either single part or multi-part)
+ header = upload_action.get("header", {})
+ chunk_size = header.get("chunk_size")
+ upload_url = fix_hf_endpoint_in_url(upload_action["href"], endpoint=endpoint)
+ if chunk_size is not None:
+ try:
+ chunk_size = int(chunk_size)
+ except (ValueError, TypeError):
+ raise ValueError(
+ f"Malformed response from LFS batch endpoint: `chunk_size` should be an integer. Got '{chunk_size}'."
+ )
+ _upload_multi_part(operation=operation, header=header, chunk_size=chunk_size, upload_url=upload_url)
+ else:
+ _upload_single_part(operation=operation, upload_url=upload_url)
+
+ # 3. Verify upload went well
+ if verify_action is not None:
+ _validate_lfs_action(verify_action)
+ verify_url = fix_hf_endpoint_in_url(verify_action["href"], endpoint)
+ verify_resp = get_session().post(
+ verify_url,
+ headers=build_hf_headers(token=token, headers=headers),
+ json={"oid": operation.upload_info.sha256.hex(), "size": operation.upload_info.size},
+ )
+ hf_raise_for_status(verify_resp)
+ logger.debug(f"{operation.path_in_repo}: Upload successful")
+
+
+def _validate_lfs_action(lfs_action: dict):
+ """validates response from the LFS batch endpoint"""
+ if not (
+ isinstance(lfs_action.get("href"), str)
+ and (lfs_action.get("header") is None or isinstance(lfs_action.get("header"), dict))
+ ):
+ raise ValueError("lfs_action is improperly formatted")
+ return lfs_action
+
+
+def _validate_batch_actions(lfs_batch_actions: dict):
+ """validates response from the LFS batch endpoint"""
+ if not (isinstance(lfs_batch_actions.get("oid"), str) and isinstance(lfs_batch_actions.get("size"), int)):
+ raise ValueError("lfs_batch_actions is improperly formatted")
+
+ upload_action = lfs_batch_actions.get("actions", {}).get("upload")
+ verify_action = lfs_batch_actions.get("actions", {}).get("verify")
+ if upload_action is not None:
+ _validate_lfs_action(upload_action)
+ if verify_action is not None:
+ _validate_lfs_action(verify_action)
+ return lfs_batch_actions
+
+
+def _validate_batch_error(lfs_batch_error: dict):
+ """validates response from the LFS batch endpoint"""
+ if not (isinstance(lfs_batch_error.get("oid"), str) and isinstance(lfs_batch_error.get("size"), int)):
+ raise ValueError("lfs_batch_error is improperly formatted")
+ error_info = lfs_batch_error.get("error")
+ if not (
+ isinstance(error_info, dict)
+ and isinstance(error_info.get("message"), str)
+ and isinstance(error_info.get("code"), int)
+ ):
+ raise ValueError("lfs_batch_error is improperly formatted")
+ return lfs_batch_error
+
+
+def _upload_single_part(operation: "CommitOperationAdd", upload_url: str) -> None:
+ """
+ Uploads `fileobj` as a single PUT HTTP request (basic LFS transfer protocol)
+
+ Args:
+ upload_url (`str`):
+ The URL to PUT the file to.
+ fileobj:
+ The file-like object holding the data to upload.
+
+ Returns: `requests.Response`
+
+ Raises:
+ [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ If the upload resulted in an error.
+ """
+ with operation.as_file(with_tqdm=True) as fileobj:
+ # S3 might raise a transient 500 error -> let's retry if that happens
+ response = http_backoff("PUT", upload_url, data=fileobj, retry_on_status_codes=(500, 502, 503, 504))
+ hf_raise_for_status(response)
+
+
+def _upload_multi_part(operation: "CommitOperationAdd", header: Dict, chunk_size: int, upload_url: str) -> None:
+ """
+ Uploads file using HF multipart LFS transfer protocol.
+ """
+ # 1. Get upload URLs for each part
+ sorted_parts_urls = _get_sorted_parts_urls(header=header, upload_info=operation.upload_info, chunk_size=chunk_size)
+
+ # 2. Upload parts (either with hf_transfer or in pure Python)
+ use_hf_transfer = constants.HF_HUB_ENABLE_HF_TRANSFER
+ if (
+ constants.HF_HUB_ENABLE_HF_TRANSFER
+ and not isinstance(operation.path_or_fileobj, str)
+ and not isinstance(operation.path_or_fileobj, Path)
+ ):
+ warnings.warn(
+ "hf_transfer is enabled but does not support uploading from bytes or BinaryIO, falling back to regular"
+ " upload"
+ )
+ use_hf_transfer = False
+
+ response_headers = (
+ _upload_parts_hf_transfer(operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size)
+ if use_hf_transfer
+ else _upload_parts_iteratively(operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size)
+ )
+
+ # 3. Send completion request
+ completion_res = get_session().post(
+ upload_url,
+ json=_get_completion_payload(response_headers, operation.upload_info.sha256.hex()),
+ headers=LFS_HEADERS,
+ )
+ hf_raise_for_status(completion_res)
+
+
+def _get_sorted_parts_urls(header: Dict, upload_info: UploadInfo, chunk_size: int) -> List[str]:
+ sorted_part_upload_urls = [
+ upload_url
+ for _, upload_url in sorted(
+ [
+ (int(part_num, 10), upload_url)
+ for part_num, upload_url in header.items()
+ if part_num.isdigit() and len(part_num) > 0
+ ],
+ key=lambda t: t[0],
+ )
+ ]
+ num_parts = len(sorted_part_upload_urls)
+ if num_parts != ceil(upload_info.size / chunk_size):
+ raise ValueError("Invalid server response to upload large LFS file")
+ return sorted_part_upload_urls
+
+
+def _get_completion_payload(response_headers: List[Dict], oid: str) -> CompletionPayloadT:
+ parts: List[PayloadPartT] = []
+ for part_number, header in enumerate(response_headers):
+ etag = header.get("etag")
+ if etag is None or etag == "":
+ raise ValueError(f"Invalid etag (`{etag}`) returned for part {part_number + 1}")
+ parts.append(
+ {
+ "partNumber": part_number + 1,
+ "etag": etag,
+ }
+ )
+ return {"oid": oid, "parts": parts}
+
+
+def _upload_parts_iteratively(
+ operation: "CommitOperationAdd", sorted_parts_urls: List[str], chunk_size: int
+) -> List[Dict]:
+ headers = []
+ with operation.as_file(with_tqdm=True) as fileobj:
+ for part_idx, part_upload_url in enumerate(sorted_parts_urls):
+ with SliceFileObj(
+ fileobj,
+ seek_from=chunk_size * part_idx,
+ read_limit=chunk_size,
+ ) as fileobj_slice:
+ # S3 might raise a transient 500 error -> let's retry if that happens
+ part_upload_res = http_backoff(
+ "PUT", part_upload_url, data=fileobj_slice, retry_on_status_codes=(500, 502, 503, 504)
+ )
+ hf_raise_for_status(part_upload_res)
+ headers.append(part_upload_res.headers)
+ return headers # type: ignore
+
+
+def _upload_parts_hf_transfer(
+ operation: "CommitOperationAdd", sorted_parts_urls: List[str], chunk_size: int
+) -> List[Dict]:
+ # Upload file using an external Rust-based package. Upload is faster but support less features (no progress bars).
+ try:
+ from hf_transfer import multipart_upload
+ except ImportError:
+ raise ValueError(
+ "Fast uploading using 'hf_transfer' is enabled (HF_HUB_ENABLE_HF_TRANSFER=1) but 'hf_transfer' package is"
+ " not available in your environment. Try `pip install hf_transfer`."
+ )
+
+ supports_callback = "callback" in inspect.signature(multipart_upload).parameters
+ if not supports_callback:
+ warnings.warn(
+ "You are using an outdated version of `hf_transfer`. Consider upgrading to latest version to enable progress bars using `pip install -U hf_transfer`."
+ )
+
+ total = operation.upload_info.size
+ desc = operation.path_in_repo
+ if len(desc) > 40:
+ desc = f"(…){desc[-40:]}"
+
+ with tqdm(
+ unit="B",
+ unit_scale=True,
+ total=total,
+ initial=0,
+ desc=desc,
+ disable=is_tqdm_disabled(logger.getEffectiveLevel()),
+ name="huggingface_hub.lfs_upload",
+ ) as progress:
+ try:
+ output = multipart_upload(
+ file_path=operation.path_or_fileobj,
+ parts_urls=sorted_parts_urls,
+ chunk_size=chunk_size,
+ max_files=128,
+ parallel_failures=127, # could be removed
+ max_retries=5,
+ **({"callback": progress.update} if supports_callback else {}),
+ )
+ except Exception as e:
+ raise RuntimeError(
+ "An error occurred while uploading using `hf_transfer`. Consider disabling HF_HUB_ENABLE_HF_TRANSFER for"
+ " better error handling."
+ ) from e
+ if not supports_callback:
+ progress.update(total)
+ return output
diff --git a/env/Lib/site-packages/huggingface_hub/py.typed b/env/Lib/site-packages/huggingface_hub/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/huggingface_hub/repocard.py b/env/Lib/site-packages/huggingface_hub/repocard.py
new file mode 100644
index 0000000000000000000000000000000000000000..83b22b2bf60a6aa52ecd1d66545fbf5fa6d45a0f
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/repocard.py
@@ -0,0 +1,830 @@
+import os
+import re
+from pathlib import Path
+from typing import Any, Dict, Literal, Optional, Type, Union
+
+import requests
+import yaml
+
+from huggingface_hub.file_download import hf_hub_download
+from huggingface_hub.hf_api import upload_file
+from huggingface_hub.repocard_data import (
+ CardData,
+ DatasetCardData,
+ EvalResult,
+ ModelCardData,
+ SpaceCardData,
+ eval_results_to_model_index,
+ model_index_to_eval_results,
+)
+from huggingface_hub.utils import get_session, is_jinja_available, yaml_dump
+
+from . import constants
+from .errors import EntryNotFoundError
+from .utils import SoftTemporaryDirectory, logging, validate_hf_hub_args
+
+
+logger = logging.get_logger(__name__)
+
+
+TEMPLATE_MODELCARD_PATH = Path(__file__).parent / "templates" / "modelcard_template.md"
+TEMPLATE_DATASETCARD_PATH = Path(__file__).parent / "templates" / "datasetcard_template.md"
+
+# exact same regex as in the Hub server. Please keep in sync.
+# See https://github.com/huggingface/moon-landing/blob/main/server/lib/ViewMarkdown.ts#L18
+REGEX_YAML_BLOCK = re.compile(r"^(\s*---[\r\n]+)([\S\s]*?)([\r\n]+---(\r\n|\n|$))")
+
+
+class RepoCard:
+ card_data_class = CardData
+ default_template_path = TEMPLATE_MODELCARD_PATH
+ repo_type = "model"
+
+ def __init__(self, content: str, ignore_metadata_errors: bool = False):
+ """Initialize a RepoCard from string content. The content should be a
+ Markdown file with a YAML block at the beginning and a Markdown body.
+
+ Args:
+ content (`str`): The content of the Markdown file.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard import RepoCard
+ >>> text = '''
+ ... ---
+ ... language: en
+ ... license: mit
+ ... ---
+ ...
+ ... # My repo
+ ... '''
+ >>> card = RepoCard(text)
+ >>> card.data.to_dict()
+ {'language': 'en', 'license': 'mit'}
+ >>> card.text
+ '\\n# My repo\\n'
+
+ ```
+
+ Raises the following error:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ when the content of the repo card metadata is not a dictionary.
+
+
+ """
+
+ # Set the content of the RepoCard, as well as underlying .data and .text attributes.
+ # See the `content` property setter for more details.
+ self.ignore_metadata_errors = ignore_metadata_errors
+ self.content = content
+
+ @property
+ def content(self):
+ """The content of the RepoCard, including the YAML block and the Markdown body."""
+ line_break = _detect_line_ending(self._content) or "\n"
+ return f"---{line_break}{self.data.to_yaml(line_break=line_break, original_order=self._original_order)}{line_break}---{line_break}{self.text}"
+
+ @content.setter
+ def content(self, content: str):
+ """Set the content of the RepoCard."""
+ self._content = content
+
+ match = REGEX_YAML_BLOCK.search(content)
+ if match:
+ # Metadata found in the YAML block
+ yaml_block = match.group(2)
+ self.text = content[match.end() :]
+ data_dict = yaml.safe_load(yaml_block)
+
+ if data_dict is None:
+ data_dict = {}
+
+ # The YAML block's data should be a dictionary
+ if not isinstance(data_dict, dict):
+ raise ValueError("repo card metadata block should be a dict")
+ else:
+ # Model card without metadata... create empty metadata
+ logger.warning("Repo card metadata block was not found. Setting CardData to empty.")
+ data_dict = {}
+ self.text = content
+
+ self.data = self.card_data_class(**data_dict, ignore_metadata_errors=self.ignore_metadata_errors)
+ self._original_order = list(data_dict.keys())
+
+ def __str__(self):
+ return self.content
+
+ def save(self, filepath: Union[Path, str]):
+ r"""Save a RepoCard to a file.
+
+ Args:
+ filepath (`Union[Path, str]`): Filepath to the markdown file to save.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard import RepoCard
+ >>> card = RepoCard("---\nlanguage: en\n---\n# This is a test repo card")
+ >>> card.save("/tmp/test.md")
+
+ ```
+ """
+ filepath = Path(filepath)
+ filepath.parent.mkdir(parents=True, exist_ok=True)
+ # Preserve newlines as in the existing file.
+ with open(filepath, mode="w", newline="", encoding="utf-8") as f:
+ f.write(str(self))
+
+ @classmethod
+ def load(
+ cls,
+ repo_id_or_path: Union[str, Path],
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ignore_metadata_errors: bool = False,
+ ):
+ """Initialize a RepoCard from a Hugging Face Hub repo's README.md or a local filepath.
+
+ Args:
+ repo_id_or_path (`Union[str, Path]`):
+ The repo ID associated with a Hugging Face Hub repo or a local filepath.
+ repo_type (`str`, *optional*):
+ The type of Hugging Face repo to push to. Defaults to None, which will use use "model". Other options
+ are "dataset" and "space". Not used when loading from a local filepath. If this is called from a child
+ class, the default value will be the child class's `repo_type`.
+ token (`str`, *optional*):
+ Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to the stored token.
+ ignore_metadata_errors (`str`):
+ If True, errors while parsing the metadata section will be ignored. Some information might be lost during
+ the process. Use it at your own risk.
+
+ Returns:
+ [`huggingface_hub.repocard.RepoCard`]: The RepoCard (or subclass) initialized from the repo's
+ README.md file or filepath.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard import RepoCard
+ >>> card = RepoCard.load("nateraw/food")
+ >>> assert card.data.tags == ["generated_from_trainer", "image-classification", "pytorch"]
+
+ ```
+ """
+
+ if Path(repo_id_or_path).is_file():
+ card_path = Path(repo_id_or_path)
+ elif isinstance(repo_id_or_path, str):
+ card_path = Path(
+ hf_hub_download(
+ repo_id_or_path,
+ constants.REPOCARD_NAME,
+ repo_type=repo_type or cls.repo_type,
+ token=token,
+ )
+ )
+ else:
+ raise ValueError(f"Cannot load RepoCard: path not found on disk ({repo_id_or_path}).")
+
+ # Preserve newlines in the existing file.
+ with card_path.open(mode="r", newline="", encoding="utf-8") as f:
+ return cls(f.read(), ignore_metadata_errors=ignore_metadata_errors)
+
+ def validate(self, repo_type: Optional[str] = None):
+ """Validates card against Hugging Face Hub's card validation logic.
+ Using this function requires access to the internet, so it is only called
+ internally by [`huggingface_hub.repocard.RepoCard.push_to_hub`].
+
+ Args:
+ repo_type (`str`, *optional*, defaults to "model"):
+ The type of Hugging Face repo to push to. Options are "model", "dataset", and "space".
+ If this function is called from a child class, the default will be the child class's `repo_type`.
+
+
+ Raises the following errors:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if the card fails validation checks.
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the request to the Hub API fails for any other reason.
+
+
+ """
+
+ # If repo type is provided, otherwise, use the repo type of the card.
+ repo_type = repo_type or self.repo_type
+
+ body = {
+ "repoType": repo_type,
+ "content": str(self),
+ }
+ headers = {"Accept": "text/plain"}
+
+ try:
+ r = get_session().post("https://huggingface.co/api/validate-yaml", body, headers=headers)
+ r.raise_for_status()
+ except requests.exceptions.HTTPError as exc:
+ if r.status_code == 400:
+ raise ValueError(r.text)
+ else:
+ raise exc
+
+ def push_to_hub(
+ self,
+ repo_id: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ):
+ """Push a RepoCard to a Hugging Face Hub repo.
+
+ Args:
+ repo_id (`str`):
+ The repo ID of the Hugging Face Hub repo to push to. Example: "nateraw/food".
+ token (`str`, *optional*):
+ Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to
+ the stored token.
+ repo_type (`str`, *optional*, defaults to "model"):
+ The type of Hugging Face repo to push to. Options are "model", "dataset", and "space". If this
+ function is called by a child class, it will default to the child class's `repo_type`.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit.
+ commit_description (`str`, *optional*)
+ The description of the generated commit.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ create_pr (`bool`, *optional*):
+ Whether or not to create a Pull Request with this commit. Defaults to `False`.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ Returns:
+ `str`: URL of the commit which updated the card metadata.
+ """
+
+ # If repo type is provided, otherwise, use the repo type of the card.
+ repo_type = repo_type or self.repo_type
+
+ # Validate card before pushing to hub
+ self.validate(repo_type=repo_type)
+
+ with SoftTemporaryDirectory() as tmpdir:
+ tmp_path = Path(tmpdir) / constants.REPOCARD_NAME
+ tmp_path.write_text(str(self))
+ url = upload_file(
+ path_or_fileobj=str(tmp_path),
+ path_in_repo=constants.REPOCARD_NAME,
+ repo_id=repo_id,
+ token=token,
+ repo_type=repo_type,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ revision=revision,
+ parent_commit=parent_commit,
+ )
+ return url
+
+ @classmethod
+ def from_template(
+ cls,
+ card_data: CardData,
+ template_path: Optional[str] = None,
+ template_str: Optional[str] = None,
+ **template_kwargs,
+ ):
+ """Initialize a RepoCard from a template. By default, it uses the default template.
+
+ Templates are Jinja2 templates that can be customized by passing keyword arguments.
+
+ Args:
+ card_data (`huggingface_hub.CardData`):
+ A huggingface_hub.CardData instance containing the metadata you want to include in the YAML
+ header of the repo card on the Hugging Face Hub.
+ template_path (`str`, *optional*):
+ A path to a markdown file with optional Jinja template variables that can be filled
+ in with `template_kwargs`. Defaults to the default template.
+
+ Returns:
+ [`huggingface_hub.repocard.RepoCard`]: A RepoCard instance with the specified card data and content from the
+ template.
+ """
+ if is_jinja_available():
+ import jinja2
+ else:
+ raise ImportError(
+ "Using RepoCard.from_template requires Jinja2 to be installed. Please"
+ " install it with `pip install Jinja2`."
+ )
+
+ kwargs = card_data.to_dict().copy()
+ kwargs.update(template_kwargs) # Template_kwargs have priority
+
+ if template_path is not None:
+ template_str = Path(template_path).read_text()
+ if template_str is None:
+ template_str = Path(cls.default_template_path).read_text()
+ template = jinja2.Template(template_str)
+ content = template.render(card_data=card_data.to_yaml(), **kwargs)
+ return cls(content)
+
+
+class ModelCard(RepoCard):
+ card_data_class = ModelCardData
+ default_template_path = TEMPLATE_MODELCARD_PATH
+ repo_type = "model"
+
+ @classmethod
+ def from_template( # type: ignore # violates Liskov property but easier to use
+ cls,
+ card_data: ModelCardData,
+ template_path: Optional[str] = None,
+ template_str: Optional[str] = None,
+ **template_kwargs,
+ ):
+ """Initialize a ModelCard from a template. By default, it uses the default template, which can be found here:
+ https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/modelcard_template.md
+
+ Templates are Jinja2 templates that can be customized by passing keyword arguments.
+
+ Args:
+ card_data (`huggingface_hub.ModelCardData`):
+ A huggingface_hub.ModelCardData instance containing the metadata you want to include in the YAML
+ header of the model card on the Hugging Face Hub.
+ template_path (`str`, *optional*):
+ A path to a markdown file with optional Jinja template variables that can be filled
+ in with `template_kwargs`. Defaults to the default template.
+
+ Returns:
+ [`huggingface_hub.ModelCard`]: A ModelCard instance with the specified card data and content from the
+ template.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import ModelCard, ModelCardData, EvalResult
+
+ >>> # Using the Default Template
+ >>> card_data = ModelCardData(
+ ... language='en',
+ ... license='mit',
+ ... library_name='timm',
+ ... tags=['image-classification', 'resnet'],
+ ... datasets=['beans'],
+ ... metrics=['accuracy'],
+ ... )
+ >>> card = ModelCard.from_template(
+ ... card_data,
+ ... model_description='This model does x + y...'
+ ... )
+
+ >>> # Including Evaluation Results
+ >>> card_data = ModelCardData(
+ ... language='en',
+ ... tags=['image-classification', 'resnet'],
+ ... eval_results=[
+ ... EvalResult(
+ ... task_type='image-classification',
+ ... dataset_type='beans',
+ ... dataset_name='Beans',
+ ... metric_type='accuracy',
+ ... metric_value=0.9,
+ ... ),
+ ... ],
+ ... model_name='my-cool-model',
+ ... )
+ >>> card = ModelCard.from_template(card_data)
+
+ >>> # Using a Custom Template
+ >>> card_data = ModelCardData(
+ ... language='en',
+ ... tags=['image-classification', 'resnet']
+ ... )
+ >>> card = ModelCard.from_template(
+ ... card_data=card_data,
+ ... template_path='./src/huggingface_hub/templates/modelcard_template.md',
+ ... custom_template_var='custom value', # will be replaced in template if it exists
+ ... )
+
+ ```
+ """
+ return super().from_template(card_data, template_path, template_str, **template_kwargs)
+
+
+class DatasetCard(RepoCard):
+ card_data_class = DatasetCardData
+ default_template_path = TEMPLATE_DATASETCARD_PATH
+ repo_type = "dataset"
+
+ @classmethod
+ def from_template( # type: ignore # violates Liskov property but easier to use
+ cls,
+ card_data: DatasetCardData,
+ template_path: Optional[str] = None,
+ template_str: Optional[str] = None,
+ **template_kwargs,
+ ):
+ """Initialize a DatasetCard from a template. By default, it uses the default template, which can be found here:
+ https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/datasetcard_template.md
+
+ Templates are Jinja2 templates that can be customized by passing keyword arguments.
+
+ Args:
+ card_data (`huggingface_hub.DatasetCardData`):
+ A huggingface_hub.DatasetCardData instance containing the metadata you want to include in the YAML
+ header of the dataset card on the Hugging Face Hub.
+ template_path (`str`, *optional*):
+ A path to a markdown file with optional Jinja template variables that can be filled
+ in with `template_kwargs`. Defaults to the default template.
+
+ Returns:
+ [`huggingface_hub.DatasetCard`]: A DatasetCard instance with the specified card data and content from the
+ template.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import DatasetCard, DatasetCardData
+
+ >>> # Using the Default Template
+ >>> card_data = DatasetCardData(
+ ... language='en',
+ ... license='mit',
+ ... annotations_creators='crowdsourced',
+ ... task_categories=['text-classification'],
+ ... task_ids=['sentiment-classification', 'text-scoring'],
+ ... multilinguality='monolingual',
+ ... pretty_name='My Text Classification Dataset',
+ ... )
+ >>> card = DatasetCard.from_template(
+ ... card_data,
+ ... pretty_name=card_data.pretty_name,
+ ... )
+
+ >>> # Using a Custom Template
+ >>> card_data = DatasetCardData(
+ ... language='en',
+ ... license='mit',
+ ... )
+ >>> card = DatasetCard.from_template(
+ ... card_data=card_data,
+ ... template_path='./src/huggingface_hub/templates/datasetcard_template.md',
+ ... custom_template_var='custom value', # will be replaced in template if it exists
+ ... )
+
+ ```
+ """
+ return super().from_template(card_data, template_path, template_str, **template_kwargs)
+
+
+class SpaceCard(RepoCard):
+ card_data_class = SpaceCardData
+ default_template_path = TEMPLATE_MODELCARD_PATH
+ repo_type = "space"
+
+
+def _detect_line_ending(content: str) -> Literal["\r", "\n", "\r\n", None]: # noqa: F722
+ """Detect the line ending of a string. Used by RepoCard to avoid making huge diff on newlines.
+
+ Uses same implementation as in Hub server, keep it in sync.
+
+ Returns:
+ str: The detected line ending of the string.
+ """
+ cr = content.count("\r")
+ lf = content.count("\n")
+ crlf = content.count("\r\n")
+ if cr + lf == 0:
+ return None
+ if crlf == cr and crlf == lf:
+ return "\r\n"
+ if cr > lf:
+ return "\r"
+ else:
+ return "\n"
+
+
+def metadata_load(local_path: Union[str, Path]) -> Optional[Dict]:
+ content = Path(local_path).read_text()
+ match = REGEX_YAML_BLOCK.search(content)
+ if match:
+ yaml_block = match.group(2)
+ data = yaml.safe_load(yaml_block)
+ if data is None or isinstance(data, dict):
+ return data
+ raise ValueError("repo card metadata block should be a dict")
+ else:
+ return None
+
+
+def metadata_save(local_path: Union[str, Path], data: Dict) -> None:
+ """
+ Save the metadata dict in the upper YAML part Trying to preserve newlines as
+ in the existing file. Docs about open() with newline="" parameter:
+ https://docs.python.org/3/library/functions.html?highlight=open#open Does
+ not work with "^M" linebreaks, which are replaced by \n
+ """
+ line_break = "\n"
+ content = ""
+ # try to detect existing newline character
+ if os.path.exists(local_path):
+ with open(local_path, "r", newline="", encoding="utf8") as readme:
+ content = readme.read()
+ if isinstance(readme.newlines, tuple):
+ line_break = readme.newlines[0]
+ elif isinstance(readme.newlines, str):
+ line_break = readme.newlines
+
+ # creates a new file if it not
+ with open(local_path, "w", newline="", encoding="utf8") as readme:
+ data_yaml = yaml_dump(data, sort_keys=False, line_break=line_break)
+ # sort_keys: keep dict order
+ match = REGEX_YAML_BLOCK.search(content)
+ if match:
+ output = content[: match.start()] + f"---{line_break}{data_yaml}---{line_break}" + content[match.end() :]
+ else:
+ output = f"---{line_break}{data_yaml}---{line_break}{content}"
+
+ readme.write(output)
+ readme.close()
+
+
+def metadata_eval_result(
+ *,
+ model_pretty_name: str,
+ task_pretty_name: str,
+ task_id: str,
+ metrics_pretty_name: str,
+ metrics_id: str,
+ metrics_value: Any,
+ dataset_pretty_name: str,
+ dataset_id: str,
+ metrics_config: Optional[str] = None,
+ metrics_verified: bool = False,
+ dataset_config: Optional[str] = None,
+ dataset_split: Optional[str] = None,
+ dataset_revision: Optional[str] = None,
+ metrics_verification_token: Optional[str] = None,
+) -> Dict:
+ """
+ Creates a metadata dict with the result from a model evaluated on a dataset.
+
+ Args:
+ model_pretty_name (`str`):
+ The name of the model in natural language.
+ task_pretty_name (`str`):
+ The name of a task in natural language.
+ task_id (`str`):
+ Example: automatic-speech-recognition. A task id.
+ metrics_pretty_name (`str`):
+ A name for the metric in natural language. Example: Test WER.
+ metrics_id (`str`):
+ Example: wer. A metric id from https://hf.co/metrics.
+ metrics_value (`Any`):
+ The value from the metric. Example: 20.0 or "20.0 ± 1.2".
+ dataset_pretty_name (`str`):
+ The name of the dataset in natural language.
+ dataset_id (`str`):
+ Example: common_voice. A dataset id from https://hf.co/datasets.
+ metrics_config (`str`, *optional*):
+ The name of the metric configuration used in `load_metric()`.
+ Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
+ metrics_verified (`bool`, *optional*, defaults to `False`):
+ Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
+ dataset_config (`str`, *optional*):
+ Example: fr. The name of the dataset configuration used in `load_dataset()`.
+ dataset_split (`str`, *optional*):
+ Example: test. The name of the dataset split used in `load_dataset()`.
+ dataset_revision (`str`, *optional*):
+ Example: 5503434ddd753f426f4b38109466949a1217c2bb. The name of the dataset dataset revision
+ used in `load_dataset()`.
+ metrics_verification_token (`bool`, *optional*):
+ A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
+
+ Returns:
+ `dict`: a metadata dict with the result from a model evaluated on a dataset.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import metadata_eval_result
+ >>> results = metadata_eval_result(
+ ... model_pretty_name="RoBERTa fine-tuned on ReactionGIF",
+ ... task_pretty_name="Text Classification",
+ ... task_id="text-classification",
+ ... metrics_pretty_name="Accuracy",
+ ... metrics_id="accuracy",
+ ... metrics_value=0.2662102282047272,
+ ... dataset_pretty_name="ReactionJPEG",
+ ... dataset_id="julien-c/reactionjpeg",
+ ... dataset_config="default",
+ ... dataset_split="test",
+ ... )
+ >>> results == {
+ ... 'model-index': [
+ ... {
+ ... 'name': 'RoBERTa fine-tuned on ReactionGIF',
+ ... 'results': [
+ ... {
+ ... 'task': {
+ ... 'type': 'text-classification',
+ ... 'name': 'Text Classification'
+ ... },
+ ... 'dataset': {
+ ... 'name': 'ReactionJPEG',
+ ... 'type': 'julien-c/reactionjpeg',
+ ... 'config': 'default',
+ ... 'split': 'test'
+ ... },
+ ... 'metrics': [
+ ... {
+ ... 'type': 'accuracy',
+ ... 'value': 0.2662102282047272,
+ ... 'name': 'Accuracy',
+ ... 'verified': False
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... }
+ True
+
+ ```
+ """
+
+ return {
+ "model-index": eval_results_to_model_index(
+ model_name=model_pretty_name,
+ eval_results=[
+ EvalResult(
+ task_name=task_pretty_name,
+ task_type=task_id,
+ metric_name=metrics_pretty_name,
+ metric_type=metrics_id,
+ metric_value=metrics_value,
+ dataset_name=dataset_pretty_name,
+ dataset_type=dataset_id,
+ metric_config=metrics_config,
+ verified=metrics_verified,
+ verify_token=metrics_verification_token,
+ dataset_config=dataset_config,
+ dataset_split=dataset_split,
+ dataset_revision=dataset_revision,
+ )
+ ],
+ )
+ }
+
+
+@validate_hf_hub_args
+def metadata_update(
+ repo_id: str,
+ metadata: Dict,
+ *,
+ repo_type: Optional[str] = None,
+ overwrite: bool = False,
+ token: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: bool = False,
+ parent_commit: Optional[str] = None,
+) -> str:
+ """
+ Updates the metadata in the README.md of a repository on the Hugging Face Hub.
+ If the README.md file doesn't exist yet, a new one is created with metadata and an
+ the default ModelCard or DatasetCard template. For `space` repo, an error is thrown
+ as a Space cannot exist without a `README.md` file.
+
+ Args:
+ repo_id (`str`):
+ The name of the repository.
+ metadata (`dict`):
+ A dictionary containing the metadata to be updated.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if updating to a dataset or space,
+ `None` or `"model"` if updating to a model. Default is `None`.
+ overwrite (`bool`, *optional*, defaults to `False`):
+ If set to `True` an existing field can be overwritten, otherwise
+ attempting to overwrite an existing field will cause an error.
+ token (`str`, *optional*):
+ The Hugging Face authentication token.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to
+ `f"Update metadata with huggingface_hub"`
+ commit_description (`str` *optional*)
+ The description of the generated commit
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the
+ `"main"` branch.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `revision` with that commit.
+ Defaults to `False`.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ Returns:
+ `str`: URL of the commit which updated the card metadata.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import metadata_update
+ >>> metadata = {'model-index': [{'name': 'RoBERTa fine-tuned on ReactionGIF',
+ ... 'results': [{'dataset': {'name': 'ReactionGIF',
+ ... 'type': 'julien-c/reactiongif'},
+ ... 'metrics': [{'name': 'Recall',
+ ... 'type': 'recall',
+ ... 'value': 0.7762102282047272}],
+ ... 'task': {'name': 'Text Classification',
+ ... 'type': 'text-classification'}}]}]}
+ >>> url = metadata_update("hf-internal-testing/reactiongif-roberta-card", metadata)
+
+ ```
+ """
+ commit_message = commit_message if commit_message is not None else "Update metadata with huggingface_hub"
+
+ # Card class given repo_type
+ card_class: Type[RepoCard]
+ if repo_type is None or repo_type == "model":
+ card_class = ModelCard
+ elif repo_type == "dataset":
+ card_class = DatasetCard
+ elif repo_type == "space":
+ card_class = RepoCard
+ else:
+ raise ValueError(f"Unknown repo_type: {repo_type}")
+
+ # Either load repo_card from the Hub or create an empty one.
+ # NOTE: Will not create the repo if it doesn't exist.
+ try:
+ card = card_class.load(repo_id, token=token, repo_type=repo_type)
+ except EntryNotFoundError:
+ if repo_type == "space":
+ raise ValueError("Cannot update metadata on a Space that doesn't contain a `README.md` file.")
+
+ # Initialize a ModelCard or DatasetCard from default template and no data.
+ card = card_class.from_template(CardData())
+
+ for key, value in metadata.items():
+ if key == "model-index":
+ # if the new metadata doesn't include a name, either use existing one or repo name
+ if "name" not in value[0]:
+ value[0]["name"] = getattr(card, "model_name", repo_id)
+ model_name, new_results = model_index_to_eval_results(value)
+ if card.data.eval_results is None:
+ card.data.eval_results = new_results
+ card.data.model_name = model_name
+ else:
+ existing_results = card.data.eval_results
+
+ # Iterate over new results
+ # Iterate over existing results
+ # If both results describe the same metric but value is different:
+ # If overwrite=True: overwrite the metric value
+ # Else: raise ValueError
+ # Else: append new result to existing ones.
+ for new_result in new_results:
+ result_found = False
+ for existing_result in existing_results:
+ if new_result.is_equal_except_value(existing_result):
+ if new_result != existing_result and not overwrite:
+ raise ValueError(
+ "You passed a new value for the existing metric"
+ f" 'name: {new_result.metric_name}, type: "
+ f"{new_result.metric_type}'. Set `overwrite=True`"
+ " to overwrite existing metrics."
+ )
+ result_found = True
+ existing_result.metric_value = new_result.metric_value
+ if existing_result.verified is True:
+ existing_result.verify_token = new_result.verify_token
+ if not result_found:
+ card.data.eval_results.append(new_result)
+ else:
+ # Any metadata that is not a result metric
+ if card.data.get(key) is not None and not overwrite and card.data.get(key) != value:
+ raise ValueError(
+ f"You passed a new value for the existing meta data field '{key}'."
+ " Set `overwrite=True` to overwrite existing metadata."
+ )
+ else:
+ card.data[key] = value
+
+ return card.push_to_hub(
+ repo_id,
+ token=token,
+ repo_type=repo_type,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ revision=revision,
+ parent_commit=parent_commit,
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/repocard_data.py b/env/Lib/site-packages/huggingface_hub/repocard_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..18366ca123260089dede1acce4510464c3d7162b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/repocard_data.py
@@ -0,0 +1,750 @@
+import copy
+from collections import defaultdict
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional, Tuple, Union
+
+from huggingface_hub.utils import logging, yaml_dump
+
+
+logger = logging.get_logger(__name__)
+
+
+@dataclass
+class EvalResult:
+ """
+ Flattened representation of individual evaluation results found in model-index of Model Cards.
+
+ For more information on the model-index spec, see https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1.
+
+ Args:
+ task_type (`str`):
+ The task identifier. Example: "image-classification".
+ dataset_type (`str`):
+ The dataset identifier. Example: "common_voice". Use dataset id from https://hf.co/datasets.
+ dataset_name (`str`):
+ A pretty name for the dataset. Example: "Common Voice (French)".
+ metric_type (`str`):
+ The metric identifier. Example: "wer". Use metric id from https://hf.co/metrics.
+ metric_value (`Any`):
+ The metric value. Example: 0.9 or "20.0 ± 1.2".
+ task_name (`str`, *optional*):
+ A pretty name for the task. Example: "Speech Recognition".
+ dataset_config (`str`, *optional*):
+ The name of the dataset configuration used in `load_dataset()`.
+ Example: fr in `load_dataset("common_voice", "fr")`. See the `datasets` docs for more info:
+ https://hf.co/docs/datasets/package_reference/loading_methods#datasets.load_dataset.name
+ dataset_split (`str`, *optional*):
+ The split used in `load_dataset()`. Example: "test".
+ dataset_revision (`str`, *optional*):
+ The revision (AKA Git Sha) of the dataset used in `load_dataset()`.
+ Example: 5503434ddd753f426f4b38109466949a1217c2bb
+ dataset_args (`Dict[str, Any]`, *optional*):
+ The arguments passed during `Metric.compute()`. Example for `bleu`: `{"max_order": 4}`
+ metric_name (`str`, *optional*):
+ A pretty name for the metric. Example: "Test WER".
+ metric_config (`str`, *optional*):
+ The name of the metric configuration used in `load_metric()`.
+ Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
+ See the `datasets` docs for more info: https://huggingface.co/docs/datasets/v2.1.0/en/loading#load-configurations
+ metric_args (`Dict[str, Any]`, *optional*):
+ The arguments passed during `Metric.compute()`. Example for `bleu`: max_order: 4
+ verified (`bool`, *optional*):
+ Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
+ verify_token (`str`, *optional*):
+ A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
+ source_name (`str`, *optional*):
+ The name of the source of the evaluation result. Example: "Open LLM Leaderboard".
+ source_url (`str`, *optional*):
+ The URL of the source of the evaluation result. Example: "https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard".
+ """
+
+ # Required
+
+ # The task identifier
+ # Example: automatic-speech-recognition
+ task_type: str
+
+ # The dataset identifier
+ # Example: common_voice. Use dataset id from https://hf.co/datasets
+ dataset_type: str
+
+ # A pretty name for the dataset.
+ # Example: Common Voice (French)
+ dataset_name: str
+
+ # The metric identifier
+ # Example: wer. Use metric id from https://hf.co/metrics
+ metric_type: str
+
+ # Value of the metric.
+ # Example: 20.0 or "20.0 ± 1.2"
+ metric_value: Any
+
+ # Optional
+
+ # A pretty name for the task.
+ # Example: Speech Recognition
+ task_name: Optional[str] = None
+
+ # The name of the dataset configuration used in `load_dataset()`.
+ # Example: fr in `load_dataset("common_voice", "fr")`.
+ # See the `datasets` docs for more info:
+ # https://huggingface.co/docs/datasets/package_reference/loading_methods#datasets.load_dataset.name
+ dataset_config: Optional[str] = None
+
+ # The split used in `load_dataset()`.
+ # Example: test
+ dataset_split: Optional[str] = None
+
+ # The revision (AKA Git Sha) of the dataset used in `load_dataset()`.
+ # Example: 5503434ddd753f426f4b38109466949a1217c2bb
+ dataset_revision: Optional[str] = None
+
+ # The arguments passed during `Metric.compute()`.
+ # Example for `bleu`: max_order: 4
+ dataset_args: Optional[Dict[str, Any]] = None
+
+ # A pretty name for the metric.
+ # Example: Test WER
+ metric_name: Optional[str] = None
+
+ # The name of the metric configuration used in `load_metric()`.
+ # Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
+ # See the `datasets` docs for more info: https://huggingface.co/docs/datasets/v2.1.0/en/loading#load-configurations
+ metric_config: Optional[str] = None
+
+ # The arguments passed during `Metric.compute()`.
+ # Example for `bleu`: max_order: 4
+ metric_args: Optional[Dict[str, Any]] = None
+
+ # Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
+ verified: Optional[bool] = None
+
+ # A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
+ verify_token: Optional[str] = None
+
+ # The name of the source of the evaluation result.
+ # Example: Open LLM Leaderboard
+ source_name: Optional[str] = None
+
+ # The URL of the source of the evaluation result.
+ # Example: https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard
+ source_url: Optional[str] = None
+
+ @property
+ def unique_identifier(self) -> tuple:
+ """Returns a tuple that uniquely identifies this evaluation."""
+ return (
+ self.task_type,
+ self.dataset_type,
+ self.dataset_config,
+ self.dataset_split,
+ self.dataset_revision,
+ )
+
+ def is_equal_except_value(self, other: "EvalResult") -> bool:
+ """
+ Return True if `self` and `other` describe exactly the same metric but with a
+ different value.
+ """
+ for key, _ in self.__dict__.items():
+ if key == "metric_value":
+ continue
+ # For metrics computed by Hugging Face's evaluation service, `verify_token` is derived from `metric_value`,
+ # so we exclude it here in the comparison.
+ if key != "verify_token" and getattr(self, key) != getattr(other, key):
+ return False
+ return True
+
+ def __post_init__(self) -> None:
+ if self.source_name is not None and self.source_url is None:
+ raise ValueError("If `source_name` is provided, `source_url` must also be provided.")
+
+
+@dataclass
+class CardData:
+ """Structure containing metadata from a RepoCard.
+
+ [`CardData`] is the parent class of [`ModelCardData`] and [`DatasetCardData`].
+
+ Metadata can be exported as a dictionary or YAML. Export can be customized to alter the representation of the data
+ (example: flatten evaluation results). `CardData` behaves as a dictionary (can get, pop, set values) but do not
+ inherit from `dict` to allow this export step.
+ """
+
+ def __init__(self, ignore_metadata_errors: bool = False, **kwargs):
+ self.__dict__.update(kwargs)
+
+ def to_dict(self):
+ """Converts CardData to a dict.
+
+ Returns:
+ `dict`: CardData represented as a dictionary ready to be dumped to a YAML
+ block for inclusion in a README.md file.
+ """
+
+ data_dict = copy.deepcopy(self.__dict__)
+ self._to_dict(data_dict)
+ return {key: value for key, value in data_dict.items() if value is not None}
+
+ def _to_dict(self, data_dict):
+ """Use this method in child classes to alter the dict representation of the data. Alter the dict in-place.
+
+ Args:
+ data_dict (`dict`): The raw dict representation of the card data.
+ """
+ pass
+
+ def to_yaml(self, line_break=None, original_order: Optional[List[str]] = None) -> str:
+ """Dumps CardData to a YAML block for inclusion in a README.md file.
+
+ Args:
+ line_break (str, *optional*):
+ The line break to use when dumping to yaml.
+
+ Returns:
+ `str`: CardData represented as a YAML block.
+ """
+ if original_order:
+ self.__dict__ = {
+ k: self.__dict__[k]
+ for k in original_order + list(set(self.__dict__.keys()) - set(original_order))
+ if k in self.__dict__
+ }
+ return yaml_dump(self.to_dict(), sort_keys=False, line_break=line_break).strip()
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+ def __str__(self):
+ return self.to_yaml()
+
+ def get(self, key: str, default: Any = None) -> Any:
+ """Get value for a given metadata key."""
+ value = self.__dict__.get(key)
+ return default if value is None else value
+
+ def pop(self, key: str, default: Any = None) -> Any:
+ """Pop value for a given metadata key."""
+ return self.__dict__.pop(key, default)
+
+ def __getitem__(self, key: str) -> Any:
+ """Get value for a given metadata key."""
+ return self.__dict__[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ """Set value for a given metadata key."""
+ self.__dict__[key] = value
+
+ def __contains__(self, key: str) -> bool:
+ """Check if a given metadata key is set."""
+ return key in self.__dict__
+
+ def __len__(self) -> int:
+ """Return the number of metadata keys set."""
+ return len(self.__dict__)
+
+
+class ModelCardData(CardData):
+ """Model Card Metadata that is used by Hugging Face Hub when included at the top of your README.md
+
+ Args:
+ base_model (`str` or `List[str]`, *optional*):
+ The identifier of the base model from which the model derives. This is applicable for example if your model is a
+ fine-tune or adapter of an existing model. The value must be the ID of a model on the Hub (or a list of IDs
+ if your model derives from multiple models). Defaults to None.
+ datasets (`Union[str, List[str]]`, *optional*):
+ Dataset or list of datasets that were used to train this model. Should be a dataset ID
+ found on https://hf.co/datasets. Defaults to None.
+ eval_results (`Union[List[EvalResult], EvalResult]`, *optional*):
+ List of `huggingface_hub.EvalResult` that define evaluation results of the model. If provided,
+ `model_name` is used to as a name on PapersWithCode's leaderboards. Defaults to `None`.
+ language (`Union[str, List[str]]`, *optional*):
+ Language of model's training data or metadata. It must be an ISO 639-1, 639-2 or
+ 639-3 code (two/three letters), or a special value like "code", "multilingual". Defaults to `None`.
+ library_name (`str`, *optional*):
+ Name of library used by this model. Example: keras or any library from
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/src/model-libraries.ts.
+ Defaults to None.
+ license (`str`, *optional*):
+ License of this model. Example: apache-2.0 or any license from
+ https://huggingface.co/docs/hub/repositories-licenses. Defaults to None.
+ license_name (`str`, *optional*):
+ Name of the license of this model. Defaults to None. To be used in conjunction with `license_link`.
+ Common licenses (Apache-2.0, MIT, CC-BY-SA-4.0) do not need a name. In that case, use `license` instead.
+ license_link (`str`, *optional*):
+ Link to the license of this model. Defaults to None. To be used in conjunction with `license_name`.
+ Common licenses (Apache-2.0, MIT, CC-BY-SA-4.0) do not need a link. In that case, use `license` instead.
+ metrics (`List[str]`, *optional*):
+ List of metrics used to evaluate this model. Should be a metric name that can be found
+ at https://hf.co/metrics. Example: 'accuracy'. Defaults to None.
+ model_name (`str`, *optional*):
+ A name for this model. It is used along with
+ `eval_results` to construct the `model-index` within the card's metadata. The name
+ you supply here is what will be used on PapersWithCode's leaderboards. If None is provided
+ then the repo name is used as a default. Defaults to None.
+ pipeline_tag (`str`, *optional*):
+ The pipeline tag associated with the model. Example: "text-classification".
+ tags (`List[str]`, *optional*):
+ List of tags to add to your model that can be used when filtering on the Hugging
+ Face Hub. Defaults to None.
+ ignore_metadata_errors (`str`):
+ If True, errors while parsing the metadata section will be ignored. Some information might be lost during
+ the process. Use it at your own risk.
+ kwargs (`dict`, *optional*):
+ Additional metadata that will be added to the model card. Defaults to None.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import ModelCardData
+ >>> card_data = ModelCardData(
+ ... language="en",
+ ... license="mit",
+ ... library_name="timm",
+ ... tags=['image-classification', 'resnet'],
+ ... )
+ >>> card_data.to_dict()
+ {'language': 'en', 'license': 'mit', 'library_name': 'timm', 'tags': ['image-classification', 'resnet']}
+
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ base_model: Optional[Union[str, List[str]]] = None,
+ datasets: Optional[Union[str, List[str]]] = None,
+ eval_results: Optional[List[EvalResult]] = None,
+ language: Optional[Union[str, List[str]]] = None,
+ library_name: Optional[str] = None,
+ license: Optional[str] = None,
+ license_name: Optional[str] = None,
+ license_link: Optional[str] = None,
+ metrics: Optional[List[str]] = None,
+ model_name: Optional[str] = None,
+ pipeline_tag: Optional[str] = None,
+ tags: Optional[List[str]] = None,
+ ignore_metadata_errors: bool = False,
+ **kwargs,
+ ):
+ self.base_model = base_model
+ self.datasets = datasets
+ self.eval_results = eval_results
+ self.language = language
+ self.library_name = library_name
+ self.license = license
+ self.license_name = license_name
+ self.license_link = license_link
+ self.metrics = metrics
+ self.model_name = model_name
+ self.pipeline_tag = pipeline_tag
+ self.tags = _to_unique_list(tags)
+
+ model_index = kwargs.pop("model-index", None)
+ if model_index:
+ try:
+ model_name, eval_results = model_index_to_eval_results(model_index)
+ self.model_name = model_name
+ self.eval_results = eval_results
+ except (KeyError, TypeError) as error:
+ if ignore_metadata_errors:
+ logger.warning("Invalid model-index. Not loading eval results into CardData.")
+ else:
+ raise ValueError(
+ f"Invalid `model_index` in metadata cannot be parsed: {error.__class__} {error}. Pass"
+ " `ignore_metadata_errors=True` to ignore this error while loading a Model Card. Warning:"
+ " some information will be lost. Use it at your own risk."
+ )
+
+ super().__init__(**kwargs)
+
+ if self.eval_results:
+ if isinstance(self.eval_results, EvalResult):
+ self.eval_results = [self.eval_results]
+ if self.model_name is None:
+ raise ValueError("Passing `eval_results` requires `model_name` to be set.")
+
+ def _to_dict(self, data_dict):
+ """Format the internal data dict. In this case, we convert eval results to a valid model index"""
+ if self.eval_results is not None:
+ data_dict["model-index"] = eval_results_to_model_index(self.model_name, self.eval_results)
+ del data_dict["eval_results"], data_dict["model_name"]
+
+
+class DatasetCardData(CardData):
+ """Dataset Card Metadata that is used by Hugging Face Hub when included at the top of your README.md
+
+ Args:
+ language (`List[str]`, *optional*):
+ Language of dataset's data or metadata. It must be an ISO 639-1, 639-2 or
+ 639-3 code (two/three letters), or a special value like "code", "multilingual".
+ license (`Union[str, List[str]]`, *optional*):
+ License(s) of this dataset. Example: apache-2.0 or any license from
+ https://huggingface.co/docs/hub/repositories-licenses.
+ annotations_creators (`Union[str, List[str]]`, *optional*):
+ How the annotations for the dataset were created.
+ Options are: 'found', 'crowdsourced', 'expert-generated', 'machine-generated', 'no-annotation', 'other'.
+ language_creators (`Union[str, List[str]]`, *optional*):
+ How the text-based data in the dataset was created.
+ Options are: 'found', 'crowdsourced', 'expert-generated', 'machine-generated', 'other'
+ multilinguality (`Union[str, List[str]]`, *optional*):
+ Whether the dataset is multilingual.
+ Options are: 'monolingual', 'multilingual', 'translation', 'other'.
+ size_categories (`Union[str, List[str]]`, *optional*):
+ The number of examples in the dataset. Options are: 'n<1K', '1K1T', and 'other'.
+ source_datasets (`List[str]]`, *optional*):
+ Indicates whether the dataset is an original dataset or extended from another existing dataset.
+ Options are: 'original' and 'extended'.
+ task_categories (`Union[str, List[str]]`, *optional*):
+ What categories of task does the dataset support?
+ task_ids (`Union[str, List[str]]`, *optional*):
+ What specific tasks does the dataset support?
+ paperswithcode_id (`str`, *optional*):
+ ID of the dataset on PapersWithCode.
+ pretty_name (`str`, *optional*):
+ A more human-readable name for the dataset. (ex. "Cats vs. Dogs")
+ train_eval_index (`Dict`, *optional*):
+ A dictionary that describes the necessary spec for doing evaluation on the Hub.
+ If not provided, it will be gathered from the 'train-eval-index' key of the kwargs.
+ config_names (`Union[str, List[str]]`, *optional*):
+ A list of the available dataset configs for the dataset.
+ """
+
+ def __init__(
+ self,
+ *,
+ language: Optional[Union[str, List[str]]] = None,
+ license: Optional[Union[str, List[str]]] = None,
+ annotations_creators: Optional[Union[str, List[str]]] = None,
+ language_creators: Optional[Union[str, List[str]]] = None,
+ multilinguality: Optional[Union[str, List[str]]] = None,
+ size_categories: Optional[Union[str, List[str]]] = None,
+ source_datasets: Optional[List[str]] = None,
+ task_categories: Optional[Union[str, List[str]]] = None,
+ task_ids: Optional[Union[str, List[str]]] = None,
+ paperswithcode_id: Optional[str] = None,
+ pretty_name: Optional[str] = None,
+ train_eval_index: Optional[Dict] = None,
+ config_names: Optional[Union[str, List[str]]] = None,
+ ignore_metadata_errors: bool = False,
+ **kwargs,
+ ):
+ self.annotations_creators = annotations_creators
+ self.language_creators = language_creators
+ self.language = language
+ self.license = license
+ self.multilinguality = multilinguality
+ self.size_categories = size_categories
+ self.source_datasets = source_datasets
+ self.task_categories = task_categories
+ self.task_ids = task_ids
+ self.paperswithcode_id = paperswithcode_id
+ self.pretty_name = pretty_name
+ self.config_names = config_names
+
+ # TODO - maybe handle this similarly to EvalResult?
+ self.train_eval_index = train_eval_index or kwargs.pop("train-eval-index", None)
+ super().__init__(**kwargs)
+
+ def _to_dict(self, data_dict):
+ data_dict["train-eval-index"] = data_dict.pop("train_eval_index")
+
+
+class SpaceCardData(CardData):
+ """Space Card Metadata that is used by Hugging Face Hub when included at the top of your README.md
+
+ To get an exhaustive reference of Spaces configuration, please visit https://huggingface.co/docs/hub/spaces-config-reference#spaces-configuration-reference.
+
+ Args:
+ title (`str`, *optional*)
+ Title of the Space.
+ sdk (`str`, *optional*)
+ SDK of the Space (one of `gradio`, `streamlit`, `docker`, or `static`).
+ sdk_version (`str`, *optional*)
+ Version of the used SDK (if Gradio/Streamlit sdk).
+ python_version (`str`, *optional*)
+ Python version used in the Space (if Gradio/Streamlit sdk).
+ app_file (`str`, *optional*)
+ Path to your main application file (which contains either gradio or streamlit Python code, or static html code).
+ Path is relative to the root of the repository.
+ app_port (`str`, *optional*)
+ Port on which your application is running. Used only if sdk is `docker`.
+ license (`str`, *optional*)
+ License of this model. Example: apache-2.0 or any license from
+ https://huggingface.co/docs/hub/repositories-licenses.
+ duplicated_from (`str`, *optional*)
+ ID of the original Space if this is a duplicated Space.
+ models (List[`str`], *optional*)
+ List of models related to this Space. Should be a dataset ID found on https://hf.co/models.
+ datasets (`List[str]`, *optional*)
+ List of datasets related to this Space. Should be a dataset ID found on https://hf.co/datasets.
+ tags (`List[str]`, *optional*)
+ List of tags to add to your Space that can be used when filtering on the Hub.
+ ignore_metadata_errors (`str`):
+ If True, errors while parsing the metadata section will be ignored. Some information might be lost during
+ the process. Use it at your own risk.
+ kwargs (`dict`, *optional*):
+ Additional metadata that will be added to the space card.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import SpaceCardData
+ >>> card_data = SpaceCardData(
+ ... title="Dreambooth Training",
+ ... license="mit",
+ ... sdk="gradio",
+ ... duplicated_from="multimodalart/dreambooth-training"
+ ... )
+ >>> card_data.to_dict()
+ {'title': 'Dreambooth Training', 'sdk': 'gradio', 'license': 'mit', 'duplicated_from': 'multimodalart/dreambooth-training'}
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ title: Optional[str] = None,
+ sdk: Optional[str] = None,
+ sdk_version: Optional[str] = None,
+ python_version: Optional[str] = None,
+ app_file: Optional[str] = None,
+ app_port: Optional[int] = None,
+ license: Optional[str] = None,
+ duplicated_from: Optional[str] = None,
+ models: Optional[List[str]] = None,
+ datasets: Optional[List[str]] = None,
+ tags: Optional[List[str]] = None,
+ ignore_metadata_errors: bool = False,
+ **kwargs,
+ ):
+ self.title = title
+ self.sdk = sdk
+ self.sdk_version = sdk_version
+ self.python_version = python_version
+ self.app_file = app_file
+ self.app_port = app_port
+ self.license = license
+ self.duplicated_from = duplicated_from
+ self.models = models
+ self.datasets = datasets
+ self.tags = _to_unique_list(tags)
+ super().__init__(**kwargs)
+
+
+def model_index_to_eval_results(model_index: List[Dict[str, Any]]) -> Tuple[str, List[EvalResult]]:
+ """Takes in a model index and returns the model name and a list of `huggingface_hub.EvalResult` objects.
+
+ A detailed spec of the model index can be found here:
+ https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
+
+ Args:
+ model_index (`List[Dict[str, Any]]`):
+ A model index data structure, likely coming from a README.md file on the
+ Hugging Face Hub.
+
+ Returns:
+ model_name (`str`):
+ The name of the model as found in the model index. This is used as the
+ identifier for the model on leaderboards like PapersWithCode.
+ eval_results (`List[EvalResult]`):
+ A list of `huggingface_hub.EvalResult` objects containing the metrics
+ reported in the provided model_index.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard_data import model_index_to_eval_results
+ >>> # Define a minimal model index
+ >>> model_index = [
+ ... {
+ ... "name": "my-cool-model",
+ ... "results": [
+ ... {
+ ... "task": {
+ ... "type": "image-classification"
+ ... },
+ ... "dataset": {
+ ... "type": "beans",
+ ... "name": "Beans"
+ ... },
+ ... "metrics": [
+ ... {
+ ... "type": "accuracy",
+ ... "value": 0.9
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... }
+ ... ]
+ >>> model_name, eval_results = model_index_to_eval_results(model_index)
+ >>> model_name
+ 'my-cool-model'
+ >>> eval_results[0].task_type
+ 'image-classification'
+ >>> eval_results[0].metric_type
+ 'accuracy'
+
+ ```
+ """
+
+ eval_results = []
+ for elem in model_index:
+ name = elem["name"]
+ results = elem["results"]
+ for result in results:
+ task_type = result["task"]["type"]
+ task_name = result["task"].get("name")
+ dataset_type = result["dataset"]["type"]
+ dataset_name = result["dataset"]["name"]
+ dataset_config = result["dataset"].get("config")
+ dataset_split = result["dataset"].get("split")
+ dataset_revision = result["dataset"].get("revision")
+ dataset_args = result["dataset"].get("args")
+ source_name = result.get("source", {}).get("name")
+ source_url = result.get("source", {}).get("url")
+
+ for metric in result["metrics"]:
+ metric_type = metric["type"]
+ metric_value = metric["value"]
+ metric_name = metric.get("name")
+ metric_args = metric.get("args")
+ metric_config = metric.get("config")
+ verified = metric.get("verified")
+ verify_token = metric.get("verifyToken")
+
+ eval_result = EvalResult(
+ task_type=task_type, # Required
+ dataset_type=dataset_type, # Required
+ dataset_name=dataset_name, # Required
+ metric_type=metric_type, # Required
+ metric_value=metric_value, # Required
+ task_name=task_name,
+ dataset_config=dataset_config,
+ dataset_split=dataset_split,
+ dataset_revision=dataset_revision,
+ dataset_args=dataset_args,
+ metric_name=metric_name,
+ metric_args=metric_args,
+ metric_config=metric_config,
+ verified=verified,
+ verify_token=verify_token,
+ source_name=source_name,
+ source_url=source_url,
+ )
+ eval_results.append(eval_result)
+ return name, eval_results
+
+
+def _remove_none(obj):
+ """
+ Recursively remove `None` values from a dict. Borrowed from: https://stackoverflow.com/a/20558778
+ """
+ if isinstance(obj, (list, tuple, set)):
+ return type(obj)(_remove_none(x) for x in obj if x is not None)
+ elif isinstance(obj, dict):
+ return type(obj)((_remove_none(k), _remove_none(v)) for k, v in obj.items() if k is not None and v is not None)
+ else:
+ return obj
+
+
+def eval_results_to_model_index(model_name: str, eval_results: List[EvalResult]) -> List[Dict[str, Any]]:
+ """Takes in given model name and list of `huggingface_hub.EvalResult` and returns a
+ valid model-index that will be compatible with the format expected by the
+ Hugging Face Hub.
+
+ Args:
+ model_name (`str`):
+ Name of the model (ex. "my-cool-model"). This is used as the identifier
+ for the model on leaderboards like PapersWithCode.
+ eval_results (`List[EvalResult]`):
+ List of `huggingface_hub.EvalResult` objects containing the metrics to be
+ reported in the model-index.
+
+ Returns:
+ model_index (`List[Dict[str, Any]]`): The eval_results converted to a model-index.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard_data import eval_results_to_model_index, EvalResult
+ >>> # Define minimal eval_results
+ >>> eval_results = [
+ ... EvalResult(
+ ... task_type="image-classification", # Required
+ ... dataset_type="beans", # Required
+ ... dataset_name="Beans", # Required
+ ... metric_type="accuracy", # Required
+ ... metric_value=0.9, # Required
+ ... )
+ ... ]
+ >>> eval_results_to_model_index("my-cool-model", eval_results)
+ [{'name': 'my-cool-model', 'results': [{'task': {'type': 'image-classification'}, 'dataset': {'name': 'Beans', 'type': 'beans'}, 'metrics': [{'type': 'accuracy', 'value': 0.9}]}]}]
+
+ ```
+ """
+
+ # Metrics are reported on a unique task-and-dataset basis.
+ # Here, we make a map of those pairs and the associated EvalResults.
+ task_and_ds_types_map: Dict[Any, List[EvalResult]] = defaultdict(list)
+ for eval_result in eval_results:
+ task_and_ds_types_map[eval_result.unique_identifier].append(eval_result)
+
+ # Use the map from above to generate the model index data.
+ model_index_data = []
+ for results in task_and_ds_types_map.values():
+ # All items from `results` share same metadata
+ sample_result = results[0]
+ data = {
+ "task": {
+ "type": sample_result.task_type,
+ "name": sample_result.task_name,
+ },
+ "dataset": {
+ "name": sample_result.dataset_name,
+ "type": sample_result.dataset_type,
+ "config": sample_result.dataset_config,
+ "split": sample_result.dataset_split,
+ "revision": sample_result.dataset_revision,
+ "args": sample_result.dataset_args,
+ },
+ "metrics": [
+ {
+ "type": result.metric_type,
+ "value": result.metric_value,
+ "name": result.metric_name,
+ "config": result.metric_config,
+ "args": result.metric_args,
+ "verified": result.verified,
+ "verifyToken": result.verify_token,
+ }
+ for result in results
+ ],
+ }
+ if sample_result.source_url is not None:
+ source = {
+ "url": sample_result.source_url,
+ }
+ if sample_result.source_name is not None:
+ source["name"] = sample_result.source_name
+ data["source"] = source
+ model_index_data.append(data)
+
+ # TODO - Check if there cases where this list is longer than one?
+ # Finally, the model index itself is list of dicts.
+ model_index = [
+ {
+ "name": model_name,
+ "results": model_index_data,
+ }
+ ]
+ return _remove_none(model_index)
+
+
+def _to_unique_list(tags: Optional[List[str]]) -> Optional[List[str]]:
+ if tags is None:
+ return tags
+ unique_tags = [] # make tags unique + keep order explicitly
+ for tag in tags:
+ if tag not in unique_tags:
+ unique_tags.append(tag)
+ return unique_tags
diff --git a/env/Lib/site-packages/huggingface_hub/repository.py b/env/Lib/site-packages/huggingface_hub/repository.py
new file mode 100644
index 0000000000000000000000000000000000000000..af1ab72fb458340f3fc211f0c5ef577b6471fda1
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/repository.py
@@ -0,0 +1,1477 @@
+import atexit
+import os
+import re
+import subprocess
+import threading
+import time
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Callable, Dict, Iterator, List, Optional, Tuple, TypedDict, Union
+from urllib.parse import urlparse
+
+from huggingface_hub import constants
+from huggingface_hub.repocard import metadata_load, metadata_save
+
+from .hf_api import HfApi, repo_type_and_id_from_hf_id
+from .lfs import LFS_MULTIPART_UPLOAD_COMMAND
+from .utils import (
+ SoftTemporaryDirectory,
+ get_token,
+ logging,
+ run_subprocess,
+ tqdm,
+ validate_hf_hub_args,
+)
+from .utils._deprecation import _deprecate_method
+
+
+logger = logging.get_logger(__name__)
+
+
+class CommandInProgress:
+ """
+ Utility to follow commands launched asynchronously.
+ """
+
+ def __init__(
+ self,
+ title: str,
+ is_done_method: Callable,
+ status_method: Callable,
+ process: subprocess.Popen,
+ post_method: Optional[Callable] = None,
+ ):
+ self.title = title
+ self._is_done = is_done_method
+ self._status = status_method
+ self._process = process
+ self._stderr = ""
+ self._stdout = ""
+ self._post_method = post_method
+
+ @property
+ def is_done(self) -> bool:
+ """
+ Whether the process is done.
+ """
+ result = self._is_done()
+
+ if result and self._post_method is not None:
+ self._post_method()
+ self._post_method = None
+
+ return result
+
+ @property
+ def status(self) -> int:
+ """
+ The exit code/status of the current action. Will return `0` if the
+ command has completed successfully, and a number between 1 and 255 if
+ the process errored-out.
+
+ Will return -1 if the command is still ongoing.
+ """
+ return self._status()
+
+ @property
+ def failed(self) -> bool:
+ """
+ Whether the process errored-out.
+ """
+ return self.status > 0
+
+ @property
+ def stderr(self) -> str:
+ """
+ The current output message on the standard error.
+ """
+ if self._process.stderr is not None:
+ self._stderr += self._process.stderr.read()
+ return self._stderr
+
+ @property
+ def stdout(self) -> str:
+ """
+ The current output message on the standard output.
+ """
+ if self._process.stdout is not None:
+ self._stdout += self._process.stdout.read()
+ return self._stdout
+
+ def __repr__(self):
+ status = self.status
+
+ if status == -1:
+ status = "running"
+
+ return (
+ f"[{self.title} command, status code: {status},"
+ f" {'in progress.' if not self.is_done else 'finished.'} PID:"
+ f" {self._process.pid}]"
+ )
+
+
+def is_git_repo(folder: Union[str, Path]) -> bool:
+ """
+ Check if the folder is the root or part of a git repository
+
+ Args:
+ folder (`str`):
+ The folder in which to run the command.
+
+ Returns:
+ `bool`: `True` if the repository is part of a repository, `False`
+ otherwise.
+ """
+ folder_exists = os.path.exists(os.path.join(folder, ".git"))
+ git_branch = subprocess.run("git branch".split(), cwd=folder, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return folder_exists and git_branch.returncode == 0
+
+
+def is_local_clone(folder: Union[str, Path], remote_url: str) -> bool:
+ """
+ Check if the folder is a local clone of the remote_url
+
+ Args:
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+ remote_url (`str`):
+ The url of a git repository.
+
+ Returns:
+ `bool`: `True` if the repository is a local clone of the remote
+ repository specified, `False` otherwise.
+ """
+ if not is_git_repo(folder):
+ return False
+
+ remotes = run_subprocess("git remote -v", folder).stdout
+
+ # Remove token for the test with remotes.
+ remote_url = re.sub(r"https://.*@", "https://", remote_url)
+ remotes = [re.sub(r"https://.*@", "https://", remote) for remote in remotes.split()]
+ return remote_url in remotes
+
+
+def is_tracked_with_lfs(filename: Union[str, Path]) -> bool:
+ """
+ Check if the file passed is tracked with git-lfs.
+
+ Args:
+ filename (`str` or `Path`):
+ The filename to check.
+
+ Returns:
+ `bool`: `True` if the file passed is tracked with git-lfs, `False`
+ otherwise.
+ """
+ folder = Path(filename).parent
+ filename = Path(filename).name
+
+ try:
+ p = run_subprocess("git check-attr -a".split() + [filename], folder)
+ attributes = p.stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ if not is_git_repo(folder):
+ return False
+ else:
+ raise OSError(exc.stderr)
+
+ if len(attributes) == 0:
+ return False
+
+ found_lfs_tag = {"diff": False, "merge": False, "filter": False}
+
+ for attribute in attributes.split("\n"):
+ for tag in found_lfs_tag.keys():
+ if tag in attribute and "lfs" in attribute:
+ found_lfs_tag[tag] = True
+
+ return all(found_lfs_tag.values())
+
+
+def is_git_ignored(filename: Union[str, Path]) -> bool:
+ """
+ Check if file is git-ignored. Supports nested .gitignore files.
+
+ Args:
+ filename (`str` or `Path`):
+ The filename to check.
+
+ Returns:
+ `bool`: `True` if the file passed is ignored by `git`, `False`
+ otherwise.
+ """
+ folder = Path(filename).parent
+ filename = Path(filename).name
+
+ try:
+ p = run_subprocess("git check-ignore".split() + [filename], folder, check=False)
+ # Will return exit code 1 if not gitignored
+ is_ignored = not bool(p.returncode)
+ except subprocess.CalledProcessError as exc:
+ raise OSError(exc.stderr)
+
+ return is_ignored
+
+
+def is_binary_file(filename: Union[str, Path]) -> bool:
+ """
+ Check if file is a binary file.
+
+ Args:
+ filename (`str` or `Path`):
+ The filename to check.
+
+ Returns:
+ `bool`: `True` if the file passed is a binary file, `False` otherwise.
+ """
+ try:
+ with open(filename, "rb") as f:
+ content = f.read(10 * (1024**2)) # Read a maximum of 10MB
+
+ # Code sample taken from the following stack overflow thread
+ # https://stackoverflow.com/questions/898669/how-can-i-detect-if-a-file-is-binary-non-text-in-python/7392391#7392391
+ text_chars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})
+ return bool(content.translate(None, text_chars))
+ except UnicodeDecodeError:
+ return True
+
+
+def files_to_be_staged(pattern: str = ".", folder: Union[str, Path, None] = None) -> List[str]:
+ """
+ Returns a list of filenames that are to be staged.
+
+ Args:
+ pattern (`str` or `Path`):
+ The pattern of filenames to check. Put `.` to get all files.
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+
+ Returns:
+ `List[str]`: List of files that are to be staged.
+ """
+ try:
+ p = run_subprocess("git ls-files --exclude-standard -mo".split() + [pattern], folder)
+ if len(p.stdout.strip()):
+ files = p.stdout.strip().split("\n")
+ else:
+ files = []
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return files
+
+
+def is_tracked_upstream(folder: Union[str, Path]) -> bool:
+ """
+ Check if the current checked-out branch is tracked upstream.
+
+ Args:
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+
+ Returns:
+ `bool`: `True` if the current checked-out branch is tracked upstream,
+ `False` otherwise.
+ """
+ try:
+ run_subprocess("git rev-parse --symbolic-full-name --abbrev-ref @{u}", folder)
+ return True
+ except subprocess.CalledProcessError as exc:
+ if "HEAD" in exc.stderr:
+ raise OSError("No branch checked out")
+
+ return False
+
+
+def commits_to_push(folder: Union[str, Path], upstream: Optional[str] = None) -> int:
+ """
+ Check the number of commits that would be pushed upstream
+
+ Args:
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+ upstream (`str`, *optional*):
+ The name of the upstream repository with which the comparison should be
+ made.
+
+ Returns:
+ `int`: Number of commits that would be pushed upstream were a `git
+ push` to proceed.
+ """
+ try:
+ result = run_subprocess(f"git cherry -v {upstream or ''}", folder)
+ return len(result.stdout.split("\n")) - 1
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+
+class PbarT(TypedDict):
+ # Used to store an opened progress bar in `_lfs_log_progress`
+ bar: tqdm
+ past_bytes: int
+
+
+@contextmanager
+def _lfs_log_progress():
+ """
+ This is a context manager that will log the Git LFS progress of cleaning,
+ smudging, pulling and pushing.
+ """
+
+ if logger.getEffectiveLevel() >= logging.ERROR:
+ try:
+ yield
+ except Exception:
+ pass
+ return
+
+ def output_progress(stopping_event: threading.Event):
+ """
+ To be launched as a separate thread with an event meaning it should stop
+ the tail.
+ """
+ # Key is tuple(state, filename), value is a dict(tqdm bar and a previous value)
+ pbars: Dict[Tuple[str, str], PbarT] = {}
+
+ def close_pbars():
+ for pbar in pbars.values():
+ pbar["bar"].update(pbar["bar"].total - pbar["past_bytes"])
+ pbar["bar"].refresh()
+ pbar["bar"].close()
+
+ def tail_file(filename) -> Iterator[str]:
+ """
+ Creates a generator to be iterated through, which will return each
+ line one by one. Will stop tailing the file if the stopping_event is
+ set.
+ """
+ with open(filename, "r") as file:
+ current_line = ""
+ while True:
+ if stopping_event.is_set():
+ close_pbars()
+ break
+
+ line_bit = file.readline()
+ if line_bit is not None and not len(line_bit.strip()) == 0:
+ current_line += line_bit
+ if current_line.endswith("\n"):
+ yield current_line
+ current_line = ""
+ else:
+ time.sleep(1)
+
+ # If the file isn't created yet, wait for a few seconds before trying again.
+ # Can be interrupted with the stopping_event.
+ while not os.path.exists(os.environ["GIT_LFS_PROGRESS"]):
+ if stopping_event.is_set():
+ close_pbars()
+ return
+
+ time.sleep(2)
+
+ for line in tail_file(os.environ["GIT_LFS_PROGRESS"]):
+ try:
+ state, file_progress, byte_progress, filename = line.split()
+ except ValueError as error:
+ # Try/except to ease debugging. See https://github.com/huggingface/huggingface_hub/issues/1373.
+ raise ValueError(f"Cannot unpack LFS progress line:\n{line}") from error
+ description = f"{state.capitalize()} file {filename}"
+
+ current_bytes, total_bytes = byte_progress.split("/")
+ current_bytes_int = int(current_bytes)
+ total_bytes_int = int(total_bytes)
+
+ pbar = pbars.get((state, filename))
+ if pbar is None:
+ # Initialize progress bar
+ pbars[(state, filename)] = {
+ "bar": tqdm(
+ desc=description,
+ initial=current_bytes_int,
+ total=total_bytes_int,
+ unit="B",
+ unit_scale=True,
+ unit_divisor=1024,
+ name="huggingface_hub.lfs_upload",
+ ),
+ "past_bytes": int(current_bytes),
+ }
+ else:
+ # Update progress bar
+ pbar["bar"].update(current_bytes_int - pbar["past_bytes"])
+ pbar["past_bytes"] = current_bytes_int
+
+ current_lfs_progress_value = os.environ.get("GIT_LFS_PROGRESS", "")
+
+ with SoftTemporaryDirectory() as tmpdir:
+ os.environ["GIT_LFS_PROGRESS"] = os.path.join(tmpdir, "lfs_progress")
+ logger.debug(f"Following progress in {os.environ['GIT_LFS_PROGRESS']}")
+
+ exit_event = threading.Event()
+ x = threading.Thread(target=output_progress, args=(exit_event,), daemon=True)
+ x.start()
+
+ try:
+ yield
+ finally:
+ exit_event.set()
+ x.join()
+
+ os.environ["GIT_LFS_PROGRESS"] = current_lfs_progress_value
+
+
+class Repository:
+ """
+ Helper class to wrap the git and git-lfs commands.
+
+ The aim is to facilitate interacting with huggingface.co hosted model or
+ dataset repos, though not a lot here (if any) is actually specific to
+ huggingface.co.
+
+
+
+ [`Repository`] is deprecated in favor of the http-based alternatives implemented in
+ [`HfApi`]. Given its large adoption in legacy code, the complete removal of
+ [`Repository`] will only happen in release `v1.0`. For more details, please read
+ https://huggingface.co/docs/huggingface_hub/concepts/git_vs_http.
+
+
+ """
+
+ command_queue: List[CommandInProgress]
+
+ @validate_hf_hub_args
+ @_deprecate_method(
+ version="1.0",
+ message=(
+ "Please prefer the http-based alternatives instead. Given its large adoption in legacy code, the complete"
+ " removal is only planned on next major release.\nFor more details, please read"
+ " https://huggingface.co/docs/huggingface_hub/concepts/git_vs_http."
+ ),
+ )
+ def __init__(
+ self,
+ local_dir: Union[str, Path],
+ clone_from: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str] = True,
+ git_user: Optional[str] = None,
+ git_email: Optional[str] = None,
+ revision: Optional[str] = None,
+ skip_lfs_files: bool = False,
+ client: Optional[HfApi] = None,
+ ):
+ """
+ Instantiate a local clone of a git repo.
+
+ If `clone_from` is set, the repo will be cloned from an existing remote repository.
+ If the remote repo does not exist, a `EnvironmentError` exception will be thrown.
+ Please create the remote repo first using [`create_repo`].
+
+ `Repository` uses the local git credentials by default. If explicitly set, the `token`
+ or the `git_user`/`git_email` pair will be used instead.
+
+ Args:
+ local_dir (`str` or `Path`):
+ path (e.g. `'my_trained_model/'`) to the local directory, where
+ the `Repository` will be initialized.
+ clone_from (`str`, *optional*):
+ Either a repository url or `repo_id`.
+ Example:
+ - `"https://huggingface.co/philschmid/playground-tests"`
+ - `"philschmid/playground-tests"`
+ repo_type (`str`, *optional*):
+ To set when cloning a repo from a repo_id. Default is model.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+ git_user (`str`, *optional*):
+ will override the `git config user.name` for committing and
+ pushing files to the hub.
+ git_email (`str`, *optional*):
+ will override the `git config user.email` for committing and
+ pushing files to the hub.
+ revision (`str`, *optional*):
+ Revision to checkout after initializing the repository. If the
+ revision doesn't exist, a branch will be created with that
+ revision name from the default branch's current HEAD.
+ skip_lfs_files (`bool`, *optional*, defaults to `False`):
+ whether to skip git-LFS files or not.
+ client (`HfApi`, *optional*):
+ Instance of [`HfApi`] to use when calling the HF Hub API. A new
+ instance will be created if this is left to `None`.
+
+ Raises:
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If the remote repository set in `clone_from` does not exist.
+ """
+ if isinstance(local_dir, Path):
+ local_dir = str(local_dir)
+ os.makedirs(local_dir, exist_ok=True)
+ self.local_dir = os.path.join(os.getcwd(), local_dir)
+ self._repo_type = repo_type
+ self.command_queue = []
+ self.skip_lfs_files = skip_lfs_files
+ self.client = client if client is not None else HfApi()
+
+ self.check_git_versions()
+
+ if isinstance(token, str):
+ self.huggingface_token: Optional[str] = token
+ elif token is False:
+ self.huggingface_token = None
+ else:
+ # if `True` -> explicit use of the cached token
+ # if `None` -> implicit use of the cached token
+ self.huggingface_token = get_token()
+
+ if clone_from is not None:
+ self.clone_from(repo_url=clone_from)
+ else:
+ if is_git_repo(self.local_dir):
+ logger.debug("[Repository] is a valid git repo")
+ else:
+ raise ValueError("If not specifying `clone_from`, you need to pass Repository a valid git clone.")
+
+ if self.huggingface_token is not None and (git_email is None or git_user is None):
+ user = self.client.whoami(self.huggingface_token)
+
+ if git_email is None:
+ git_email = user.get("email")
+
+ if git_user is None:
+ git_user = user.get("fullname")
+
+ if git_user is not None or git_email is not None:
+ self.git_config_username_and_email(git_user, git_email)
+
+ self.lfs_enable_largefiles()
+ self.git_credential_helper_store()
+
+ if revision is not None:
+ self.git_checkout(revision, create_branch_ok=True)
+
+ # This ensures that all commands exit before exiting the Python runtime.
+ # This will ensure all pushes register on the hub, even if other errors happen in subsequent operations.
+ atexit.register(self.wait_for_commands)
+
+ @property
+ def current_branch(self) -> str:
+ """
+ Returns the current checked out branch.
+
+ Returns:
+ `str`: Current checked out branch.
+ """
+ try:
+ result = run_subprocess("git rev-parse --abbrev-ref HEAD", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return result
+
+ def check_git_versions(self):
+ """
+ Checks that `git` and `git-lfs` can be run.
+
+ Raises:
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `git` or `git-lfs` are not installed.
+ """
+ try:
+ git_version = run_subprocess("git --version", self.local_dir).stdout.strip()
+ except FileNotFoundError:
+ raise EnvironmentError("Looks like you do not have git installed, please install.")
+
+ try:
+ lfs_version = run_subprocess("git-lfs --version", self.local_dir).stdout.strip()
+ except FileNotFoundError:
+ raise EnvironmentError(
+ "Looks like you do not have git-lfs installed, please install."
+ " You can install from https://git-lfs.github.com/."
+ " Then run `git lfs install` (you only have to do this once)."
+ )
+ logger.info(git_version + "\n" + lfs_version)
+
+ @validate_hf_hub_args
+ def clone_from(self, repo_url: str, token: Union[bool, str, None] = None):
+ """
+ Clone from a remote. If the folder already exists, will try to clone the
+ repository within it.
+
+ If this folder is a git repository with linked history, will try to
+ update the repository.
+
+ Args:
+ repo_url (`str`):
+ The URL from which to clone the repository
+ token (`Union[str, bool]`, *optional*):
+ Whether to use the authentication token. It can be:
+ - a string which is the token itself
+ - `False`, which would not use the authentication token
+ - `True`, which would fetch the authentication token from the
+ local folder and use it (you should be logged in for this to
+ work).
+ - `None`, which would retrieve the value of
+ `self.huggingface_token`.
+
+
+
+ Raises the following error:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if an organization token (starts with "api_org") is passed. Use must use
+ your own personal access token (see https://hf.co/settings/tokens).
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if you are trying to clone the repository in a non-empty folder, or if the
+ `git` operations raise errors.
+
+
+ """
+ token = (
+ token # str -> use it
+ if isinstance(token, str)
+ else (
+ None # `False` -> explicit no token
+ if token is False
+ else self.huggingface_token # `None` or `True` -> use default
+ )
+ )
+ if token is not None and token.startswith("api_org"):
+ raise ValueError(
+ "You must use your personal access token, not an Organization token"
+ " (see https://hf.co/settings/tokens)."
+ )
+
+ hub_url = self.client.endpoint
+ if hub_url in repo_url or ("http" not in repo_url and len(repo_url.split("/")) <= 2):
+ repo_type, namespace, repo_name = repo_type_and_id_from_hf_id(repo_url, hub_url=hub_url)
+ repo_id = f"{namespace}/{repo_name}" if namespace is not None else repo_name
+
+ if repo_type is not None:
+ self._repo_type = repo_type
+
+ repo_url = hub_url + "/"
+
+ if self._repo_type in constants.REPO_TYPES_URL_PREFIXES:
+ repo_url += constants.REPO_TYPES_URL_PREFIXES[self._repo_type]
+
+ if token is not None:
+ # Add token in git url when provided
+ scheme = urlparse(repo_url).scheme
+ repo_url = repo_url.replace(f"{scheme}://", f"{scheme}://user:{token}@")
+
+ repo_url += repo_id
+
+ # For error messages, it's cleaner to show the repo url without the token.
+ clean_repo_url = re.sub(r"(https?)://.*@", r"\1://", repo_url)
+ try:
+ run_subprocess("git lfs install", self.local_dir)
+
+ # checks if repository is initialized in a empty repository or in one with files
+ if len(os.listdir(self.local_dir)) == 0:
+ logger.warning(f"Cloning {clean_repo_url} into local empty directory.")
+
+ with _lfs_log_progress():
+ env = os.environ.copy()
+
+ if self.skip_lfs_files:
+ env.update({"GIT_LFS_SKIP_SMUDGE": "1"})
+
+ run_subprocess(
+ # 'git lfs clone' is deprecated (will display a warning in the terminal)
+ # but we still use it as it provides a nicer UX when downloading large
+ # files (shows progress).
+ f"{'git clone' if self.skip_lfs_files else 'git lfs clone'} {repo_url} .",
+ self.local_dir,
+ env=env,
+ )
+ else:
+ # Check if the folder is the root of a git repository
+ if not is_git_repo(self.local_dir):
+ raise EnvironmentError(
+ "Tried to clone a repository in a non-empty folder that isn't"
+ f" a git repository ('{self.local_dir}'). If you really want to"
+ f" do this, do it manually:\n cd {self.local_dir} && git init"
+ " && git remote add origin && git pull origin main\n or clone"
+ " repo to a new folder and move your existing files there"
+ " afterwards."
+ )
+
+ if is_local_clone(self.local_dir, repo_url):
+ logger.warning(
+ f"{self.local_dir} is already a clone of {clean_repo_url}."
+ " Make sure you pull the latest changes with"
+ " `repo.git_pull()`."
+ )
+ else:
+ output = run_subprocess("git remote get-url origin", self.local_dir, check=False)
+
+ error_msg = (
+ f"Tried to clone {clean_repo_url} in an unrelated git"
+ " repository.\nIf you believe this is an error, please add"
+ f" a remote with the following URL: {clean_repo_url}."
+ )
+ if output.returncode == 0:
+ clean_local_remote_url = re.sub(r"https://.*@", "https://", output.stdout)
+ error_msg += f"\nLocal path has its origin defined as: {clean_local_remote_url}"
+ raise EnvironmentError(error_msg)
+
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_config_username_and_email(self, git_user: Optional[str] = None, git_email: Optional[str] = None):
+ """
+ Sets git username and email (only in the current repo).
+
+ Args:
+ git_user (`str`, *optional*):
+ The username to register through `git`.
+ git_email (`str`, *optional*):
+ The email to register through `git`.
+ """
+ try:
+ if git_user is not None:
+ run_subprocess("git config user.name".split() + [git_user], self.local_dir)
+
+ if git_email is not None:
+ run_subprocess(f"git config user.email {git_email}".split(), self.local_dir)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_credential_helper_store(self):
+ """
+ Sets the git credential helper to `store`
+ """
+ try:
+ run_subprocess("git config credential.helper store", self.local_dir)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_head_hash(self) -> str:
+ """
+ Get commit sha on top of HEAD.
+
+ Returns:
+ `str`: The current checked out commit SHA.
+ """
+ try:
+ p = run_subprocess("git rev-parse HEAD", self.local_dir)
+ return p.stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_remote_url(self) -> str:
+ """
+ Get URL to origin remote.
+
+ Returns:
+ `str`: The URL of the `origin` remote.
+ """
+ try:
+ p = run_subprocess("git config --get remote.origin.url", self.local_dir)
+ url = p.stdout.strip()
+ # Strip basic auth info.
+ return re.sub(r"https://.*@", "https://", url)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_head_commit_url(self) -> str:
+ """
+ Get URL to last commit on HEAD. We assume it's been pushed, and the url
+ scheme is the same one as for GitHub or HuggingFace.
+
+ Returns:
+ `str`: The URL to the current checked-out commit.
+ """
+ sha = self.git_head_hash()
+ url = self.git_remote_url()
+ if url.endswith("/"):
+ url = url[:-1]
+ return f"{url}/commit/{sha}"
+
+ def list_deleted_files(self) -> List[str]:
+ """
+ Returns a list of the files that are deleted in the working directory or
+ index.
+
+ Returns:
+ `List[str]`: A list of files that have been deleted in the working
+ directory or index.
+ """
+ try:
+ git_status = run_subprocess("git status -s", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if len(git_status) == 0:
+ return []
+
+ # Receives a status like the following
+ # D .gitignore
+ # D new_file.json
+ # AD new_file1.json
+ # ?? new_file2.json
+ # ?? new_file4.json
+
+ # Strip each line of whitespaces
+ modified_files_statuses = [status.strip() for status in git_status.split("\n")]
+
+ # Only keep files that are deleted using the D prefix
+ deleted_files_statuses = [status for status in modified_files_statuses if "D" in status.split()[0]]
+
+ # Remove the D prefix and strip to keep only the relevant filename
+ deleted_files = [status.split()[-1].strip() for status in deleted_files_statuses]
+
+ return deleted_files
+
+ def lfs_track(self, patterns: Union[str, List[str]], filename: bool = False):
+ """
+ Tell git-lfs to track files according to a pattern.
+
+ Setting the `filename` argument to `True` will treat the arguments as
+ literal filenames, not as patterns. Any special glob characters in the
+ filename will be escaped when writing to the `.gitattributes` file.
+
+ Args:
+ patterns (`Union[str, List[str]]`):
+ The pattern, or list of patterns, to track with git-lfs.
+ filename (`bool`, *optional*, defaults to `False`):
+ Whether to use the patterns as literal filenames.
+ """
+ if isinstance(patterns, str):
+ patterns = [patterns]
+ try:
+ for pattern in patterns:
+ run_subprocess(
+ f"git lfs track {'--filename' if filename else ''} {pattern}",
+ self.local_dir,
+ )
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def lfs_untrack(self, patterns: Union[str, List[str]]):
+ """
+ Tell git-lfs to untrack those files.
+
+ Args:
+ patterns (`Union[str, List[str]]`):
+ The pattern, or list of patterns, to untrack with git-lfs.
+ """
+ if isinstance(patterns, str):
+ patterns = [patterns]
+ try:
+ for pattern in patterns:
+ run_subprocess("git lfs untrack".split() + [pattern], self.local_dir)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def lfs_enable_largefiles(self):
+ """
+ HF-specific. This enables upload support of files >5GB.
+ """
+ try:
+ lfs_config = "git config lfs.customtransfer.multipart"
+ run_subprocess(f"{lfs_config}.path huggingface-cli", self.local_dir)
+ run_subprocess(
+ f"{lfs_config}.args {LFS_MULTIPART_UPLOAD_COMMAND}",
+ self.local_dir,
+ )
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def auto_track_binary_files(self, pattern: str = ".") -> List[str]:
+ """
+ Automatically track binary files with git-lfs.
+
+ Args:
+ pattern (`str`, *optional*, defaults to "."):
+ The pattern with which to track files that are binary.
+
+ Returns:
+ `List[str]`: List of filenames that are now tracked due to being
+ binary files
+ """
+ files_to_be_tracked_with_lfs = []
+
+ deleted_files = self.list_deleted_files()
+
+ for filename in files_to_be_staged(pattern, folder=self.local_dir):
+ if filename in deleted_files:
+ continue
+
+ path_to_file = os.path.join(os.getcwd(), self.local_dir, filename)
+
+ if not (is_tracked_with_lfs(path_to_file) or is_git_ignored(path_to_file)):
+ size_in_mb = os.path.getsize(path_to_file) / (1024 * 1024)
+
+ if size_in_mb >= 10:
+ logger.warning(
+ "Parsing a large file to check if binary or not. Tracking large"
+ " files using `repository.auto_track_large_files` is"
+ " recommended so as to not load the full file in memory."
+ )
+
+ is_binary = is_binary_file(path_to_file)
+
+ if is_binary:
+ self.lfs_track(filename)
+ files_to_be_tracked_with_lfs.append(filename)
+
+ # Cleanup the .gitattributes if files were deleted
+ self.lfs_untrack(deleted_files)
+
+ return files_to_be_tracked_with_lfs
+
+ def auto_track_large_files(self, pattern: str = ".") -> List[str]:
+ """
+ Automatically track large files (files that weigh more than 10MBs) with
+ git-lfs.
+
+ Args:
+ pattern (`str`, *optional*, defaults to "."):
+ The pattern with which to track files that are above 10MBs.
+
+ Returns:
+ `List[str]`: List of filenames that are now tracked due to their
+ size.
+ """
+ files_to_be_tracked_with_lfs = []
+
+ deleted_files = self.list_deleted_files()
+
+ for filename in files_to_be_staged(pattern, folder=self.local_dir):
+ if filename in deleted_files:
+ continue
+
+ path_to_file = os.path.join(os.getcwd(), self.local_dir, filename)
+ size_in_mb = os.path.getsize(path_to_file) / (1024 * 1024)
+
+ if size_in_mb >= 10 and not is_tracked_with_lfs(path_to_file) and not is_git_ignored(path_to_file):
+ self.lfs_track(filename)
+ files_to_be_tracked_with_lfs.append(filename)
+
+ # Cleanup the .gitattributes if files were deleted
+ self.lfs_untrack(deleted_files)
+
+ return files_to_be_tracked_with_lfs
+
+ def lfs_prune(self, recent=False):
+ """
+ git lfs prune
+
+ Args:
+ recent (`bool`, *optional*, defaults to `False`):
+ Whether to prune files even if they were referenced by recent
+ commits. See the following
+ [link](https://github.com/git-lfs/git-lfs/blob/f3d43f0428a84fc4f1e5405b76b5a73ec2437e65/docs/man/git-lfs-prune.1.ronn#recent-files)
+ for more information.
+ """
+ try:
+ with _lfs_log_progress():
+ result = run_subprocess(f"git lfs prune {'--recent' if recent else ''}", self.local_dir)
+ logger.info(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_pull(self, rebase: bool = False, lfs: bool = False):
+ """
+ git pull
+
+ Args:
+ rebase (`bool`, *optional*, defaults to `False`):
+ Whether to rebase the current branch on top of the upstream
+ branch after fetching.
+ lfs (`bool`, *optional*, defaults to `False`):
+ Whether to fetch the LFS files too. This option only changes the
+ behavior when a repository was cloned without fetching the LFS
+ files; calling `repo.git_pull(lfs=True)` will then fetch the LFS
+ file from the remote repository.
+ """
+ command = "git pull" if not lfs else "git lfs pull"
+ if rebase:
+ command += " --rebase"
+ try:
+ with _lfs_log_progress():
+ result = run_subprocess(command, self.local_dir)
+ logger.info(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_add(self, pattern: str = ".", auto_lfs_track: bool = False):
+ """
+ git add
+
+ Setting the `auto_lfs_track` parameter to `True` will automatically
+ track files that are larger than 10MB with `git-lfs`.
+
+ Args:
+ pattern (`str`, *optional*, defaults to "."):
+ The pattern with which to add files to staging.
+ auto_lfs_track (`bool`, *optional*, defaults to `False`):
+ Whether to automatically track large and binary files with
+ git-lfs. Any file over 10MB in size, or in binary format, will
+ be automatically tracked.
+ """
+ if auto_lfs_track:
+ # Track files according to their size (>=10MB)
+ tracked_files = self.auto_track_large_files(pattern)
+
+ # Read the remaining files and track them if they're binary
+ tracked_files.extend(self.auto_track_binary_files(pattern))
+
+ if tracked_files:
+ logger.warning(
+ f"Adding files tracked by Git LFS: {tracked_files}. This may take a"
+ " bit of time if the files are large."
+ )
+
+ try:
+ result = run_subprocess("git add -v".split() + [pattern], self.local_dir)
+ logger.info(f"Adding to index:\n{result.stdout}\n")
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_commit(self, commit_message: str = "commit files to HF hub"):
+ """
+ git commit
+
+ Args:
+ commit_message (`str`, *optional*, defaults to "commit files to HF hub"):
+ The message attributed to the commit.
+ """
+ try:
+ result = run_subprocess("git commit -v -m".split() + [commit_message], self.local_dir)
+ logger.info(f"Committed:\n{result.stdout}\n")
+ except subprocess.CalledProcessError as exc:
+ if len(exc.stderr) > 0:
+ raise EnvironmentError(exc.stderr)
+ else:
+ raise EnvironmentError(exc.stdout)
+
+ def git_push(
+ self,
+ upstream: Optional[str] = None,
+ blocking: bool = True,
+ auto_lfs_prune: bool = False,
+ ) -> Union[str, Tuple[str, CommandInProgress]]:
+ """
+ git push
+
+ If used without setting `blocking`, will return url to commit on remote
+ repo. If used with `blocking=True`, will return a tuple containing the
+ url to commit and the command object to follow for information about the
+ process.
+
+ Args:
+ upstream (`str`, *optional*):
+ Upstream to which this should push. If not specified, will push
+ to the lastly defined upstream or to the default one (`origin
+ main`).
+ blocking (`bool`, *optional*, defaults to `True`):
+ Whether the function should return only when the push has
+ finished. Setting this to `False` will return an
+ `CommandInProgress` object which has an `is_done` property. This
+ property will be set to `True` when the push is finished.
+ auto_lfs_prune (`bool`, *optional*, defaults to `False`):
+ Whether to automatically prune files once they have been pushed
+ to the remote.
+ """
+ command = "git push"
+
+ if upstream:
+ command += f" --set-upstream {upstream}"
+
+ number_of_commits = commits_to_push(self.local_dir, upstream)
+
+ if number_of_commits > 1:
+ logger.warning(f"Several commits ({number_of_commits}) will be pushed upstream.")
+ if blocking:
+ logger.warning("The progress bars may be unreliable.")
+
+ try:
+ with _lfs_log_progress():
+ process = subprocess.Popen(
+ command.split(),
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ cwd=self.local_dir,
+ )
+
+ if blocking:
+ stdout, stderr = process.communicate()
+ return_code = process.poll()
+ process.kill()
+
+ if len(stderr):
+ logger.warning(stderr)
+
+ if return_code:
+ raise subprocess.CalledProcessError(return_code, process.args, output=stdout, stderr=stderr)
+
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if not blocking:
+
+ def status_method():
+ status = process.poll()
+ if status is None:
+ return -1
+ else:
+ return status
+
+ command_in_progress = CommandInProgress(
+ "push",
+ is_done_method=lambda: process.poll() is not None,
+ status_method=status_method,
+ process=process,
+ post_method=self.lfs_prune if auto_lfs_prune else None,
+ )
+
+ self.command_queue.append(command_in_progress)
+
+ return self.git_head_commit_url(), command_in_progress
+
+ if auto_lfs_prune:
+ self.lfs_prune()
+
+ return self.git_head_commit_url()
+
+ def git_checkout(self, revision: str, create_branch_ok: bool = False):
+ """
+ git checkout a given revision
+
+ Specifying `create_branch_ok` to `True` will create the branch to the
+ given revision if that revision doesn't exist.
+
+ Args:
+ revision (`str`):
+ The revision to checkout.
+ create_branch_ok (`str`, *optional*, defaults to `False`):
+ Whether creating a branch named with the `revision` passed at
+ the current checked-out reference if `revision` isn't an
+ existing revision is allowed.
+ """
+ try:
+ result = run_subprocess(f"git checkout {revision}", self.local_dir)
+ logger.warning(f"Checked out {revision} from {self.current_branch}.")
+ logger.warning(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ if not create_branch_ok:
+ raise EnvironmentError(exc.stderr)
+ else:
+ try:
+ result = run_subprocess(f"git checkout -b {revision}", self.local_dir)
+ logger.warning(
+ f"Revision `{revision}` does not exist. Created and checked out branch `{revision}`."
+ )
+ logger.warning(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def tag_exists(self, tag_name: str, remote: Optional[str] = None) -> bool:
+ """
+ Check if a tag exists or not.
+
+ Args:
+ tag_name (`str`):
+ The name of the tag to check.
+ remote (`str`, *optional*):
+ Whether to check if the tag exists on a remote. This parameter
+ should be the identifier of the remote.
+
+ Returns:
+ `bool`: Whether the tag exists.
+ """
+ if remote:
+ try:
+ result = run_subprocess(f"git ls-remote origin refs/tags/{tag_name}", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return len(result) != 0
+ else:
+ try:
+ git_tags = run_subprocess("git tag", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ git_tags = git_tags.split("\n")
+ return tag_name in git_tags
+
+ def delete_tag(self, tag_name: str, remote: Optional[str] = None) -> bool:
+ """
+ Delete a tag, both local and remote, if it exists
+
+ Args:
+ tag_name (`str`):
+ The tag name to delete.
+ remote (`str`, *optional*):
+ The remote on which to delete the tag.
+
+ Returns:
+ `bool`: `True` if deleted, `False` if the tag didn't exist.
+ If remote is not passed, will just be updated locally
+ """
+ delete_locally = True
+ delete_remotely = True
+
+ if not self.tag_exists(tag_name):
+ delete_locally = False
+
+ if not self.tag_exists(tag_name, remote=remote):
+ delete_remotely = False
+
+ if delete_locally:
+ try:
+ run_subprocess(["git", "tag", "-d", tag_name], self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if remote and delete_remotely:
+ try:
+ run_subprocess(f"git push {remote} --delete {tag_name}", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return True
+
+ def add_tag(self, tag_name: str, message: Optional[str] = None, remote: Optional[str] = None):
+ """
+ Add a tag at the current head and push it
+
+ If remote is None, will just be updated locally
+
+ If no message is provided, the tag will be lightweight. if a message is
+ provided, the tag will be annotated.
+
+ Args:
+ tag_name (`str`):
+ The name of the tag to be added.
+ message (`str`, *optional*):
+ The message that accompanies the tag. The tag will turn into an
+ annotated tag if a message is passed.
+ remote (`str`, *optional*):
+ The remote on which to add the tag.
+ """
+ if message:
+ tag_args = ["git", "tag", "-a", tag_name, "-m", message]
+ else:
+ tag_args = ["git", "tag", tag_name]
+
+ try:
+ run_subprocess(tag_args, self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if remote:
+ try:
+ run_subprocess(f"git push {remote} {tag_name}", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def is_repo_clean(self) -> bool:
+ """
+ Return whether or not the git status is clean or not
+
+ Returns:
+ `bool`: `True` if the git status is clean, `False` otherwise.
+ """
+ try:
+ git_status = run_subprocess("git status --porcelain", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return len(git_status) == 0
+
+ def push_to_hub(
+ self,
+ commit_message: str = "commit files to HF hub",
+ blocking: bool = True,
+ clean_ok: bool = True,
+ auto_lfs_prune: bool = False,
+ ) -> Union[None, str, Tuple[str, CommandInProgress]]:
+ """
+ Helper to add, commit, and push files to remote repository on the
+ HuggingFace Hub. Will automatically track large files (>10MB).
+
+ Args:
+ commit_message (`str`):
+ Message to use for the commit.
+ blocking (`bool`, *optional*, defaults to `True`):
+ Whether the function should return only when the `git push` has
+ finished.
+ clean_ok (`bool`, *optional*, defaults to `True`):
+ If True, this function will return None if the repo is
+ untouched. Default behavior is to fail because the git command
+ fails.
+ auto_lfs_prune (`bool`, *optional*, defaults to `False`):
+ Whether to automatically prune files once they have been pushed
+ to the remote.
+ """
+ if clean_ok and self.is_repo_clean():
+ logger.info("Repo currently clean. Ignoring push_to_hub")
+ return None
+ self.git_add(auto_lfs_track=True)
+ self.git_commit(commit_message)
+ return self.git_push(
+ upstream=f"origin {self.current_branch}",
+ blocking=blocking,
+ auto_lfs_prune=auto_lfs_prune,
+ )
+
+ @contextmanager
+ def commit(
+ self,
+ commit_message: str,
+ branch: Optional[str] = None,
+ track_large_files: bool = True,
+ blocking: bool = True,
+ auto_lfs_prune: bool = False,
+ ):
+ """
+ Context manager utility to handle committing to a repository. This
+ automatically tracks large files (>10Mb) with git-lfs. Set the
+ `track_large_files` argument to `False` if you wish to ignore that
+ behavior.
+
+ Args:
+ commit_message (`str`):
+ Message to use for the commit.
+ branch (`str`, *optional*):
+ The branch on which the commit will appear. This branch will be
+ checked-out before any operation.
+ track_large_files (`bool`, *optional*, defaults to `True`):
+ Whether to automatically track large files or not. Will do so by
+ default.
+ blocking (`bool`, *optional*, defaults to `True`):
+ Whether the function should return only when the `git push` has
+ finished.
+ auto_lfs_prune (`bool`, defaults to `True`):
+ Whether to automatically prune files once they have been pushed
+ to the remote.
+
+ Examples:
+
+ ```python
+ >>> with Repository(
+ ... "text-files",
+ ... clone_from="/text-files",
+ ... token=True,
+ >>> ).commit("My first file :)"):
+ ... with open("file.txt", "w+") as f:
+ ... f.write(json.dumps({"hey": 8}))
+
+ >>> import torch
+
+ >>> model = torch.nn.Transformer()
+ >>> with Repository(
+ ... "torch-model",
+ ... clone_from="/torch-model",
+ ... token=True,
+ >>> ).commit("My cool model :)"):
+ ... torch.save(model.state_dict(), "model.pt")
+ ```
+
+ """
+
+ files_to_stage = files_to_be_staged(".", folder=self.local_dir)
+
+ if len(files_to_stage):
+ files_in_msg = str(files_to_stage[:5])[:-1] + ", ...]" if len(files_to_stage) > 5 else str(files_to_stage)
+ logger.error(
+ "There exists some updated files in the local repository that are not"
+ f" committed: {files_in_msg}. This may lead to errors if checking out"
+ " a branch. These files and their modifications will be added to the"
+ " current commit."
+ )
+
+ if branch is not None:
+ self.git_checkout(branch, create_branch_ok=True)
+
+ if is_tracked_upstream(self.local_dir):
+ logger.warning("Pulling changes ...")
+ self.git_pull(rebase=True)
+ else:
+ logger.warning(f"The current branch has no upstream branch. Will push to 'origin {self.current_branch}'")
+
+ current_working_directory = os.getcwd()
+ os.chdir(os.path.join(current_working_directory, self.local_dir))
+
+ try:
+ yield self
+ finally:
+ self.git_add(auto_lfs_track=track_large_files)
+
+ try:
+ self.git_commit(commit_message)
+ except OSError as e:
+ # If no changes are detected, there is nothing to commit.
+ if "nothing to commit" not in str(e):
+ raise e
+
+ try:
+ self.git_push(
+ upstream=f"origin {self.current_branch}",
+ blocking=blocking,
+ auto_lfs_prune=auto_lfs_prune,
+ )
+ except OSError as e:
+ # If no changes are detected, there is nothing to commit.
+ if "could not read Username" in str(e):
+ raise OSError("Couldn't authenticate user for push. Did you set `token` to `True`?") from e
+ else:
+ raise e
+
+ os.chdir(current_working_directory)
+
+ def repocard_metadata_load(self) -> Optional[Dict]:
+ filepath = os.path.join(self.local_dir, constants.REPOCARD_NAME)
+ if os.path.isfile(filepath):
+ return metadata_load(filepath)
+ return None
+
+ def repocard_metadata_save(self, data: Dict) -> None:
+ return metadata_save(os.path.join(self.local_dir, constants.REPOCARD_NAME), data)
+
+ @property
+ def commands_failed(self):
+ """
+ Returns the asynchronous commands that failed.
+ """
+ return [c for c in self.command_queue if c.status > 0]
+
+ @property
+ def commands_in_progress(self):
+ """
+ Returns the asynchronous commands that are currently in progress.
+ """
+ return [c for c in self.command_queue if not c.is_done]
+
+ def wait_for_commands(self):
+ """
+ Blocking method: blocks all subsequent execution until all commands have
+ been processed.
+ """
+ index = 0
+ for command_failed in self.commands_failed:
+ logger.error(f"The {command_failed.title} command with PID {command_failed._process.pid} failed.")
+ logger.error(command_failed.stderr)
+
+ while self.commands_in_progress:
+ if index % 10 == 0:
+ logger.warning(
+ f"Waiting for the following commands to finish before shutting down: {self.commands_in_progress}."
+ )
+
+ index += 1
+
+ time.sleep(1)
diff --git a/env/Lib/site-packages/huggingface_hub/serialization/__init__.py b/env/Lib/site-packages/huggingface_hub/serialization/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8949a22a5f65ab29b7df65aa6a9df9bce0544b7e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/serialization/__init__.py
@@ -0,0 +1,27 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ruff: noqa: F401
+"""Contains helpers to serialize tensors."""
+
+from ._base import StateDictSplit, split_state_dict_into_shards_factory
+from ._tensorflow import get_tf_storage_size, split_tf_state_dict_into_shards
+from ._torch import (
+ get_torch_storage_id,
+ get_torch_storage_size,
+ load_state_dict_from_file,
+ load_torch_model,
+ save_torch_model,
+ save_torch_state_dict,
+ split_torch_state_dict_into_shards,
+)
diff --git a/env/Lib/site-packages/huggingface_hub/serialization/_base.py b/env/Lib/site-packages/huggingface_hub/serialization/_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b6454a90e1942854dd0a095a59c92794323279
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/serialization/_base.py
@@ -0,0 +1,210 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains helpers to split tensors into shards."""
+
+from dataclasses import dataclass, field
+from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
+
+from .. import logging
+
+
+TensorT = TypeVar("TensorT")
+TensorSizeFn_T = Callable[[TensorT], int]
+StorageIDFn_T = Callable[[TensorT], Optional[Any]]
+
+MAX_SHARD_SIZE = "5GB"
+SIZE_UNITS = {
+ "TB": 10**12,
+ "GB": 10**9,
+ "MB": 10**6,
+ "KB": 10**3,
+}
+
+
+logger = logging.get_logger(__file__)
+
+
+@dataclass
+class StateDictSplit:
+ is_sharded: bool = field(init=False)
+ metadata: Dict[str, Any]
+ filename_to_tensors: Dict[str, List[str]]
+ tensor_to_filename: Dict[str, str]
+
+ def __post_init__(self):
+ self.is_sharded = len(self.filename_to_tensors) > 1
+
+
+def split_state_dict_into_shards_factory(
+ state_dict: Dict[str, TensorT],
+ *,
+ get_storage_size: TensorSizeFn_T,
+ filename_pattern: str,
+ get_storage_id: StorageIDFn_T = lambda tensor: None,
+ max_shard_size: Union[int, str] = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, Tensor]`):
+ The state dictionary to save.
+ get_storage_size (`Callable[[Tensor], int]`):
+ A function that returns the size of a tensor when saved on disk in bytes.
+ get_storage_id (`Callable[[Tensor], Optional[Any]]`, *optional*):
+ A function that returns a unique identifier to a tensor storage. Multiple different tensors can share the
+ same underlying storage. This identifier is guaranteed to be unique and constant for this tensor's storage
+ during its lifetime. Two tensor storages with non-overlapping lifetimes may have the same id.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+ """
+ storage_id_to_tensors: Dict[Any, List[str]] = {}
+
+ shard_list: List[Dict[str, TensorT]] = []
+ current_shard: Dict[str, TensorT] = {}
+ current_shard_size = 0
+ total_size = 0
+
+ if isinstance(max_shard_size, str):
+ max_shard_size = parse_size_to_int(max_shard_size)
+
+ for key, tensor in state_dict.items():
+ # when bnb serialization is used the weights in the state dict can be strings
+ # check: https://github.com/huggingface/transformers/pull/24416 for more details
+ if isinstance(tensor, str):
+ logger.info("Skipping tensor %s as it is a string (bnb serialization)", key)
+ continue
+
+ # If a `tensor` shares the same underlying storage as another tensor, we put `tensor` in the same `block`
+ storage_id = get_storage_id(tensor)
+ if storage_id is not None:
+ if storage_id in storage_id_to_tensors:
+ # We skip this tensor for now and will reassign to correct shard later
+ storage_id_to_tensors[storage_id].append(key)
+ continue
+ else:
+ # This is the first tensor with this storage_id, we create a new entry
+ # in the storage_id_to_tensors dict => we will assign the shard id later
+ storage_id_to_tensors[storage_id] = [key]
+
+ # Compute tensor size
+ tensor_size = get_storage_size(tensor)
+
+ # If this tensor is bigger than the maximal size, we put it in its own shard
+ if tensor_size > max_shard_size:
+ total_size += tensor_size
+ shard_list.append({key: tensor})
+ continue
+
+ # If this tensor is going to tip up over the maximal size, we split.
+ # Current shard already has some tensors, we add it to the list of shards and create a new one.
+ if current_shard_size + tensor_size > max_shard_size:
+ shard_list.append(current_shard)
+ current_shard = {}
+ current_shard_size = 0
+
+ # Add the tensor to the current shard
+ current_shard[key] = tensor
+ current_shard_size += tensor_size
+ total_size += tensor_size
+
+ # Add the last shard
+ if len(current_shard) > 0:
+ shard_list.append(current_shard)
+ nb_shards = len(shard_list)
+
+ # Loop over the tensors that share the same storage and assign them together
+ for storage_id, keys in storage_id_to_tensors.items():
+ # Let's try to find the shard where the first tensor of this storage is and put all tensors in the same shard
+ for shard in shard_list:
+ if keys[0] in shard:
+ for key in keys:
+ shard[key] = state_dict[key]
+ break
+
+ # If we only have one shard, we return it => no need to build the index
+ if nb_shards == 1:
+ filename = filename_pattern.format(suffix="")
+ return StateDictSplit(
+ metadata={"total_size": total_size},
+ filename_to_tensors={filename: list(state_dict.keys())},
+ tensor_to_filename={key: filename for key in state_dict.keys()},
+ )
+
+ # Now that each tensor is assigned to a shard, let's assign a filename to each shard
+ tensor_name_to_filename = {}
+ filename_to_tensors = {}
+ for idx, shard in enumerate(shard_list):
+ filename = filename_pattern.format(suffix=f"-{idx + 1:05d}-of-{nb_shards:05d}")
+ for key in shard:
+ tensor_name_to_filename[key] = filename
+ filename_to_tensors[filename] = list(shard.keys())
+
+ # Build the index and return
+ return StateDictSplit(
+ metadata={"total_size": total_size},
+ filename_to_tensors=filename_to_tensors,
+ tensor_to_filename=tensor_name_to_filename,
+ )
+
+
+def parse_size_to_int(size_as_str: str) -> int:
+ """
+ Parse a size expressed as a string with digits and unit (like `"5MB"`) to an integer (in bytes).
+
+ Supported units are "TB", "GB", "MB", "KB".
+
+ Args:
+ size_as_str (`str`): The size to convert. Will be directly returned if an `int`.
+
+ Example:
+
+ ```py
+ >>> parse_size_to_int("5MB")
+ 5000000
+ ```
+ """
+ size_as_str = size_as_str.strip()
+
+ # Parse unit
+ unit = size_as_str[-2:].upper()
+ if unit not in SIZE_UNITS:
+ raise ValueError(f"Unit '{unit}' not supported. Supported units are TB, GB, MB, KB. Got '{size_as_str}'.")
+ multiplier = SIZE_UNITS[unit]
+
+ # Parse value
+ try:
+ value = float(size_as_str[:-2].strip())
+ except ValueError as e:
+ raise ValueError(f"Could not parse the size value from '{size_as_str}': {e}") from e
+
+ return int(value * multiplier)
diff --git a/env/Lib/site-packages/huggingface_hub/serialization/_dduf.py b/env/Lib/site-packages/huggingface_hub/serialization/_dduf.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1debadb3ac8a45716f0359b932dc065f09edb84
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/serialization/_dduf.py
@@ -0,0 +1,387 @@
+import json
+import logging
+import mmap
+import os
+import shutil
+import zipfile
+from contextlib import contextmanager
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Any, Dict, Generator, Iterable, Tuple, Union
+
+from ..errors import DDUFCorruptedFileError, DDUFExportError, DDUFInvalidEntryNameError
+
+
+logger = logging.getLogger(__name__)
+
+DDUF_ALLOWED_ENTRIES = {
+ # Allowed file extensions in a DDUF file
+ ".json",
+ ".model",
+ ".safetensors",
+ ".txt",
+}
+
+DDUF_FOLDER_REQUIRED_ENTRIES = {
+ # Each folder must contain at least one of these entries
+ "config.json",
+ "tokenizer_config.json",
+ "preprocessor_config.json",
+ "scheduler_config.json",
+}
+
+
+@dataclass
+class DDUFEntry:
+ """Object representing a file entry in a DDUF file.
+
+ See [`read_dduf_file`] for how to read a DDUF file.
+
+ Attributes:
+ filename (str):
+ The name of the file in the DDUF archive.
+ offset (int):
+ The offset of the file in the DDUF archive.
+ length (int):
+ The length of the file in the DDUF archive.
+ dduf_path (str):
+ The path to the DDUF archive (for internal use).
+ """
+
+ filename: str
+ length: int
+ offset: int
+
+ dduf_path: Path = field(repr=False)
+
+ @contextmanager
+ def as_mmap(self) -> Generator[bytes, None, None]:
+ """Open the file as a memory-mapped file.
+
+ Useful to load safetensors directly from the file.
+
+ Example:
+ ```py
+ >>> import safetensors.torch
+ >>> with entry.as_mmap() as mm:
+ ... tensors = safetensors.torch.load(mm)
+ ```
+ """
+ with self.dduf_path.open("rb") as f:
+ with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as mm:
+ yield mm[self.offset : self.offset + self.length]
+
+ def read_text(self, encoding: str = "utf-8") -> str:
+ """Read the file as text.
+
+ Useful for '.txt' and '.json' entries.
+
+ Example:
+ ```py
+ >>> import json
+ >>> index = json.loads(entry.read_text())
+ ```
+ """
+ with self.dduf_path.open("rb") as f:
+ f.seek(self.offset)
+ return f.read(self.length).decode(encoding=encoding)
+
+
+def read_dduf_file(dduf_path: Union[os.PathLike, str]) -> Dict[str, DDUFEntry]:
+ """
+ Read a DDUF file and return a dictionary of entries.
+
+ Only the metadata is read, the data is not loaded in memory.
+
+ Args:
+ dduf_path (`str` or `os.PathLike`):
+ The path to the DDUF file to read.
+
+ Returns:
+ `Dict[str, DDUFEntry]`:
+ A dictionary of [`DDUFEntry`] indexed by filename.
+
+ Raises:
+ - [`DDUFCorruptedFileError`]: If the DDUF file is corrupted (i.e. doesn't follow the DDUF format).
+
+ Example:
+ ```python
+ >>> import json
+ >>> import safetensors.torch
+ >>> from huggingface_hub import read_dduf_file
+
+ # Read DDUF metadata
+ >>> dduf_entries = read_dduf_file("FLUX.1-dev.dduf")
+
+ # Returns a mapping filename <> DDUFEntry
+ >>> dduf_entries["model_index.json"]
+ DDUFEntry(filename='model_index.json', offset=66, length=587)
+
+ # Load model index as JSON
+ >>> json.loads(dduf_entries["model_index.json"].read_text())
+ {'_class_name': 'FluxPipeline', '_diffusers_version': '0.32.0.dev0', '_name_or_path': 'black-forest-labs/FLUX.1-dev', ...
+
+ # Load VAE weights using safetensors
+ >>> with dduf_entries["vae/diffusion_pytorch_model.safetensors"].as_mmap() as mm:
+ ... state_dict = safetensors.torch.load(mm)
+ ```
+ """
+ entries = {}
+ dduf_path = Path(dduf_path)
+ logger.info(f"Reading DDUF file {dduf_path}")
+ with zipfile.ZipFile(str(dduf_path), "r") as zf:
+ for info in zf.infolist():
+ logger.debug(f"Reading entry {info.filename}")
+ if info.compress_type != zipfile.ZIP_STORED:
+ raise DDUFCorruptedFileError("Data must not be compressed in DDUF file.")
+
+ try:
+ _validate_dduf_entry_name(info.filename)
+ except DDUFInvalidEntryNameError as e:
+ raise DDUFCorruptedFileError(f"Invalid entry name in DDUF file: {info.filename}") from e
+
+ offset = _get_data_offset(zf, info)
+
+ entries[info.filename] = DDUFEntry(
+ filename=info.filename, offset=offset, length=info.file_size, dduf_path=dduf_path
+ )
+
+ # Consistency checks on the DDUF file
+ if "model_index.json" not in entries:
+ raise DDUFCorruptedFileError("Missing required 'model_index.json' entry in DDUF file.")
+ index = json.loads(entries["model_index.json"].read_text())
+ _validate_dduf_structure(index, entries.keys())
+
+ logger.info(f"Done reading DDUF file {dduf_path}. Found {len(entries)} entries")
+ return entries
+
+
+def export_entries_as_dduf(
+ dduf_path: Union[str, os.PathLike], entries: Iterable[Tuple[str, Union[str, Path, bytes]]]
+) -> None:
+ """Write a DDUF file from an iterable of entries.
+
+ This is a lower-level helper than [`export_folder_as_dduf`] that allows more flexibility when serializing data.
+ In particular, you don't need to save the data on disk before exporting it in the DDUF file.
+
+ Args:
+ dduf_path (`str` or `os.PathLike`):
+ The path to the DDUF file to write.
+ entries (`Iterable[Tuple[str, Union[str, Path, bytes]]]`):
+ An iterable of entries to write in the DDUF file. Each entry is a tuple with the filename and the content.
+ The filename should be the path to the file in the DDUF archive.
+ The content can be a string or a pathlib.Path representing a path to a file on the local disk or directly the content as bytes.
+
+ Raises:
+ - [`DDUFExportError`]: If anything goes wrong during the export (e.g. invalid entry name, missing 'model_index.json', etc.).
+
+ Example:
+ ```python
+ # Export specific files from the local disk.
+ >>> from huggingface_hub import export_entries_as_dduf
+ >>> export_entries_as_dduf(
+ ... dduf_path="stable-diffusion-v1-4-FP16.dduf",
+ ... entries=[ # List entries to add to the DDUF file (here, only FP16 weights)
+ ... ("model_index.json", "path/to/model_index.json"),
+ ... ("vae/config.json", "path/to/vae/config.json"),
+ ... ("vae/diffusion_pytorch_model.fp16.safetensors", "path/to/vae/diffusion_pytorch_model.fp16.safetensors"),
+ ... ("text_encoder/config.json", "path/to/text_encoder/config.json"),
+ ... ("text_encoder/model.fp16.safetensors", "path/to/text_encoder/model.fp16.safetensors"),
+ ... # ... add more entries here
+ ... ]
+ ... )
+ ```
+
+ ```python
+ # Export state_dicts one by one from a loaded pipeline
+ >>> from diffusers import DiffusionPipeline
+ >>> from typing import Generator, Tuple
+ >>> import safetensors.torch
+ >>> from huggingface_hub import export_entries_as_dduf
+ >>> pipe = DiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4")
+ ... # ... do some work with the pipeline
+
+ >>> def as_entries(pipe: DiffusionPipeline) -> Generator[Tuple[str, bytes], None, None]:
+ ... # Build an generator that yields the entries to add to the DDUF file.
+ ... # The first element of the tuple is the filename in the DDUF archive (must use UNIX separator!). The second element is the content of the file.
+ ... # Entries will be evaluated lazily when the DDUF file is created (only 1 entry is loaded in memory at a time)
+ ... yield "vae/config.json", pipe.vae.to_json_string().encode()
+ ... yield "vae/diffusion_pytorch_model.safetensors", safetensors.torch.save(pipe.vae.state_dict())
+ ... yield "text_encoder/config.json", pipe.text_encoder.config.to_json_string().encode()
+ ... yield "text_encoder/model.safetensors", safetensors.torch.save(pipe.text_encoder.state_dict())
+ ... # ... add more entries here
+
+ >>> export_entries_as_dduf(dduf_path="stable-diffusion-v1-4.dduf", entries=as_entries(pipe))
+ ```
+ """
+ logger.info(f"Exporting DDUF file '{dduf_path}'")
+ filenames = set()
+ index = None
+ with zipfile.ZipFile(str(dduf_path), "w", zipfile.ZIP_STORED) as archive:
+ for filename, content in entries:
+ if filename in filenames:
+ raise DDUFExportError(f"Can't add duplicate entry: {filename}")
+ filenames.add(filename)
+
+ if filename == "model_index.json":
+ try:
+ index = json.loads(_load_content(content).decode())
+ except json.JSONDecodeError as e:
+ raise DDUFExportError("Failed to parse 'model_index.json'.") from e
+
+ try:
+ filename = _validate_dduf_entry_name(filename)
+ except DDUFInvalidEntryNameError as e:
+ raise DDUFExportError(f"Invalid entry name: {filename}") from e
+ logger.debug(f"Adding entry '{filename}' to DDUF file")
+ _dump_content_in_archive(archive, filename, content)
+
+ # Consistency checks on the DDUF file
+ if index is None:
+ raise DDUFExportError("Missing required 'model_index.json' entry in DDUF file.")
+ try:
+ _validate_dduf_structure(index, filenames)
+ except DDUFCorruptedFileError as e:
+ raise DDUFExportError("Invalid DDUF file structure.") from e
+
+ logger.info(f"Done writing DDUF file {dduf_path}")
+
+
+def export_folder_as_dduf(dduf_path: Union[str, os.PathLike], folder_path: Union[str, os.PathLike]) -> None:
+ """
+ Export a folder as a DDUF file.
+
+ AUses [`export_entries_as_dduf`] under the hood.
+
+ Args:
+ dduf_path (`str` or `os.PathLike`):
+ The path to the DDUF file to write.
+ folder_path (`str` or `os.PathLike`):
+ The path to the folder containing the diffusion model.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import export_folder_as_dduf
+ >>> export_folder_as_dduf(dduf_path="FLUX.1-dev.dduf", folder_path="path/to/FLUX.1-dev")
+ ```
+ """
+ folder_path = Path(folder_path)
+
+ def _iterate_over_folder() -> Iterable[Tuple[str, Path]]:
+ for path in Path(folder_path).glob("**/*"):
+ if not path.is_file():
+ continue
+ if path.suffix not in DDUF_ALLOWED_ENTRIES:
+ logger.debug(f"Skipping file '{path}' (file type not allowed)")
+ continue
+ path_in_archive = path.relative_to(folder_path)
+ if len(path_in_archive.parts) >= 3:
+ logger.debug(f"Skipping file '{path}' (nested directories not allowed)")
+ continue
+ yield path_in_archive.as_posix(), path
+
+ export_entries_as_dduf(dduf_path, _iterate_over_folder())
+
+
+def _dump_content_in_archive(archive: zipfile.ZipFile, filename: str, content: Union[str, os.PathLike, bytes]) -> None:
+ with archive.open(filename, "w", force_zip64=True) as archive_fh:
+ if isinstance(content, (str, Path)):
+ content_path = Path(content)
+ with content_path.open("rb") as content_fh:
+ shutil.copyfileobj(content_fh, archive_fh, 1024 * 1024 * 8) # type: ignore[misc]
+ elif isinstance(content, bytes):
+ archive_fh.write(content)
+ else:
+ raise DDUFExportError(f"Invalid content type for {filename}. Must be str, Path or bytes.")
+
+
+def _load_content(content: Union[str, Path, bytes]) -> bytes:
+ """Load the content of an entry as bytes.
+
+ Used only for small checks (not to dump content into archive).
+ """
+ if isinstance(content, (str, Path)):
+ return Path(content).read_bytes()
+ elif isinstance(content, bytes):
+ return content
+ else:
+ raise DDUFExportError(f"Invalid content type. Must be str, Path or bytes. Got {type(content)}.")
+
+
+def _validate_dduf_entry_name(entry_name: str) -> str:
+ if "." + entry_name.split(".")[-1] not in DDUF_ALLOWED_ENTRIES:
+ raise DDUFInvalidEntryNameError(f"File type not allowed: {entry_name}")
+ if "\\" in entry_name:
+ raise DDUFInvalidEntryNameError(f"Entry names must use UNIX separators ('/'). Got {entry_name}.")
+ entry_name = entry_name.strip("/")
+ if entry_name.count("/") > 1:
+ raise DDUFInvalidEntryNameError(f"DDUF only supports 1 level of directory. Got {entry_name}.")
+ return entry_name
+
+
+def _validate_dduf_structure(index: Any, entry_names: Iterable[str]) -> None:
+ """
+ Consistency checks on the DDUF file structure.
+
+ Rules:
+ - The 'model_index.json' entry is required and must contain a dictionary.
+ - Each folder name must correspond to an entry in 'model_index.json'.
+ - Each folder must contain at least a config file ('config.json', 'tokenizer_config.json', 'preprocessor_config.json', 'scheduler_config.json').
+
+ Args:
+ index (Any):
+ The content of the 'model_index.json' entry.
+ entry_names (Iterable[str]):
+ The list of entry names in the DDUF file.
+
+ Raises:
+ - [`DDUFCorruptedFileError`]: If the DDUF file is corrupted (i.e. doesn't follow the DDUF format).
+ """
+ if not isinstance(index, dict):
+ raise DDUFCorruptedFileError(f"Invalid 'model_index.json' content. Must be a dictionary. Got {type(index)}.")
+
+ dduf_folders = {entry.split("/")[0] for entry in entry_names if "/" in entry}
+ for folder in dduf_folders:
+ if folder not in index:
+ raise DDUFCorruptedFileError(f"Missing required entry '{folder}' in 'model_index.json'.")
+ if not any(f"{folder}/{required_entry}" in entry_names for required_entry in DDUF_FOLDER_REQUIRED_ENTRIES):
+ raise DDUFCorruptedFileError(
+ f"Missing required file in folder '{folder}'. Must contains at least one of {DDUF_FOLDER_REQUIRED_ENTRIES}."
+ )
+
+
+def _get_data_offset(zf: zipfile.ZipFile, info: zipfile.ZipInfo) -> int:
+ """
+ Calculate the data offset for a file in a ZIP archive.
+
+ Args:
+ zf (`zipfile.ZipFile`):
+ The opened ZIP file. Must be opened in read mode.
+ info (`zipfile.ZipInfo`):
+ The file info.
+
+ Returns:
+ int: The offset of the file data in the ZIP archive.
+ """
+ if zf.fp is None:
+ raise DDUFCorruptedFileError("ZipFile object must be opened in read mode.")
+
+ # Step 1: Get the local file header offset
+ header_offset = info.header_offset
+
+ # Step 2: Read the local file header
+ zf.fp.seek(header_offset)
+ local_file_header = zf.fp.read(30) # Fixed-size part of the local header
+
+ if len(local_file_header) < 30:
+ raise DDUFCorruptedFileError("Incomplete local file header.")
+
+ # Step 3: Parse the header fields to calculate the start of file data
+ # Local file header: https://en.wikipedia.org/wiki/ZIP_(file_format)#File_headers
+ filename_len = int.from_bytes(local_file_header[26:28], "little")
+ extra_field_len = int.from_bytes(local_file_header[28:30], "little")
+
+ # Data offset is after the fixed header, filename, and extra fields
+ data_offset = header_offset + 30 + filename_len + extra_field_len
+
+ return data_offset
diff --git a/env/Lib/site-packages/huggingface_hub/serialization/_tensorflow.py b/env/Lib/site-packages/huggingface_hub/serialization/_tensorflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..59ed8110b28f4891d67e754fdfbfa47a26f85be1
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/serialization/_tensorflow.py
@@ -0,0 +1,95 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains tensorflow-specific helpers."""
+
+import math
+import re
+from typing import TYPE_CHECKING, Dict, Union
+
+from .. import constants
+from ._base import MAX_SHARD_SIZE, StateDictSplit, split_state_dict_into_shards_factory
+
+
+if TYPE_CHECKING:
+ import tensorflow as tf
+
+
+def split_tf_state_dict_into_shards(
+ state_dict: Dict[str, "tf.Tensor"],
+ *,
+ filename_pattern: str = constants.TF2_WEIGHTS_FILE_PATTERN,
+ max_shard_size: Union[int, str] = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, Tensor]`):
+ The state dictionary to save.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"tf_model{suffix}.h5"`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+ """
+ return split_state_dict_into_shards_factory(
+ state_dict,
+ max_shard_size=max_shard_size,
+ filename_pattern=filename_pattern,
+ get_storage_size=get_tf_storage_size,
+ )
+
+
+def get_tf_storage_size(tensor: "tf.Tensor") -> int:
+ # Return `math.ceil` since dtype byte size can be a float (e.g., 0.125 for tf.bool).
+ # Better to overestimate than underestimate.
+ return math.ceil(tensor.numpy().size * _dtype_byte_size_tf(tensor.dtype))
+
+
+def _dtype_byte_size_tf(dtype) -> float:
+ """
+ Returns the size (in bytes) occupied by one parameter of type `dtype`.
+ Taken from https://github.com/huggingface/transformers/blob/74d9d0cebb0263a3f8ab9c280569170cc74651d0/src/transformers/modeling_tf_utils.py#L608.
+ NOTE: why not `tensor.numpy().nbytes`?
+ Example:
+ ```py
+ >>> _dtype_byte_size(tf.float32)
+ 4
+ ```
+ """
+ import tensorflow as tf
+
+ if dtype == tf.bool:
+ return 1 / 8
+ bit_search = re.search(r"[^\d](\d+)$", dtype.name)
+ if bit_search is None:
+ raise ValueError(f"`dtype` is not a valid dtype: {dtype}.")
+ bit_size = int(bit_search.groups()[0])
+ return bit_size // 8
diff --git a/env/Lib/site-packages/huggingface_hub/serialization/_torch.py b/env/Lib/site-packages/huggingface_hub/serialization/_torch.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccb9c42b925165ced016fc5b00e6c978e7cc6aca
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/serialization/_torch.py
@@ -0,0 +1,1015 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains pytorch-specific helpers."""
+
+import importlib
+import json
+import os
+import re
+from collections import defaultdict, namedtuple
+from functools import lru_cache
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List, NamedTuple, Optional, Set, Tuple, Union
+
+from packaging import version
+
+from .. import constants, logging
+from ._base import MAX_SHARD_SIZE, StateDictSplit, split_state_dict_into_shards_factory
+
+
+logger = logging.get_logger(__file__)
+
+if TYPE_CHECKING:
+ import torch
+
+# SAVING
+
+
+def save_torch_model(
+ model: "torch.nn.Module",
+ save_directory: Union[str, Path],
+ *,
+ filename_pattern: Optional[str] = None,
+ force_contiguous: bool = True,
+ max_shard_size: Union[int, str] = MAX_SHARD_SIZE,
+ metadata: Optional[Dict[str, str]] = None,
+ safe_serialization: bool = True,
+ is_main_process: bool = True,
+ shared_tensors_to_discard: Optional[List[str]] = None,
+):
+ """
+ Saves a given torch model to disk, handling sharding and shared tensors issues.
+
+ See also [`save_torch_state_dict`] to save a state dict with more flexibility.
+
+ For more information about tensor sharing, check out [this guide](https://huggingface.co/docs/safetensors/torch_shared_tensors).
+
+ The model state dictionary is split into shards so that each shard is smaller than a given size. The shards are
+ saved in the `save_directory` with the given `filename_pattern`. If the model is too big to fit in a single shard,
+ an index file is saved in the `save_directory` to indicate where each tensor is saved. This helper uses
+ [`split_torch_state_dict_into_shards`] under the hood. If `safe_serialization` is `True`, the shards are saved as
+ safetensors (the default). Otherwise, the shards are saved as pickle.
+
+ Before saving the model, the `save_directory` is cleaned from any previous shard files.
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+
+
+ If your model is a `transformers.PreTrainedModel`, you should pass `model._tied_weights_keys` as `shared_tensors_to_discard` to properly handle shared tensors saving. This ensures the correct duplicate tensors are discarded during saving.
+
+
+
+ Args:
+ model (`torch.nn.Module`):
+ The model to save on disk.
+ save_directory (`str` or `Path`):
+ The directory in which the model will be saved.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"` or `pytorch_model{suffix}.bin` depending on `safe_serialization`
+ parameter.
+ force_contiguous (`boolean`, *optional*):
+ Forcing the state_dict to be saved as contiguous tensors. This has no effect on the correctness of the
+ model, but it could potentially change performance if the layout of the tensor was chosen specifically for
+ that reason. Defaults to `True`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+ metadata (`Dict[str, str]`, *optional*):
+ Extra information to save along with the model. Some metadata will be added for each dropped tensors.
+ This information will not be enough to recover the entire shared structure but might help understanding
+ things.
+ safe_serialization (`bool`, *optional*):
+ Whether to save as safetensors, which is the default behavior. If `False`, the shards are saved as pickle.
+ Safe serialization is recommended for security reasons. Saving as pickle is deprecated and will be removed
+ in a future version.
+ is_main_process (`bool`, *optional*):
+ Whether the process calling this is the main process or not. Useful when in distributed training like
+ TPUs and need to call this function from all processes. In this case, set `is_main_process=True` only on
+ the main process to avoid race conditions. Defaults to True.
+ shared_tensors_to_discard (`List[str]`, *optional*):
+ List of tensor names to drop when saving shared tensors. If not provided and shared tensors are
+ detected, it will drop the first name alphabetically.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import save_torch_model
+ >>> model = ... # A PyTorch model
+
+ # Save state dict to "path/to/folder". The model will be split into shards of 5GB each and saved as safetensors.
+ >>> save_torch_model(model, "path/to/folder")
+
+ # Load model back
+ >>> from huggingface_hub import load_torch_model # TODO
+ >>> load_torch_model(model, "path/to/folder")
+ >>>
+ ```
+ """
+ save_torch_state_dict(
+ state_dict=model.state_dict(),
+ filename_pattern=filename_pattern,
+ force_contiguous=force_contiguous,
+ max_shard_size=max_shard_size,
+ metadata=metadata,
+ safe_serialization=safe_serialization,
+ save_directory=save_directory,
+ is_main_process=is_main_process,
+ shared_tensors_to_discard=shared_tensors_to_discard,
+ )
+
+
+def save_torch_state_dict(
+ state_dict: Dict[str, "torch.Tensor"],
+ save_directory: Union[str, Path],
+ *,
+ filename_pattern: Optional[str] = None,
+ force_contiguous: bool = True,
+ max_shard_size: Union[int, str] = MAX_SHARD_SIZE,
+ metadata: Optional[Dict[str, str]] = None,
+ safe_serialization: bool = True,
+ is_main_process: bool = True,
+ shared_tensors_to_discard: Optional[List[str]] = None,
+) -> None:
+ """
+ Save a model state dictionary to the disk, handling sharding and shared tensors issues.
+
+ See also [`save_torch_model`] to directly save a PyTorch model.
+
+ For more information about tensor sharing, check out [this guide](https://huggingface.co/docs/safetensors/torch_shared_tensors).
+
+ The model state dictionary is split into shards so that each shard is smaller than a given size. The shards are
+ saved in the `save_directory` with the given `filename_pattern`. If the model is too big to fit in a single shard,
+ an index file is saved in the `save_directory` to indicate where each tensor is saved. This helper uses
+ [`split_torch_state_dict_into_shards`] under the hood. If `safe_serialization` is `True`, the shards are saved as
+ safetensors (the default). Otherwise, the shards are saved as pickle.
+
+ Before saving the model, the `save_directory` is cleaned from any previous shard files.
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+
+
+ If your model is a `transformers.PreTrainedModel`, you should pass `model._tied_weights_keys` as `shared_tensors_to_discard` to properly handle shared tensors saving. This ensures the correct duplicate tensors are discarded during saving.
+
+
+
+ Args:
+ state_dict (`Dict[str, torch.Tensor]`):
+ The state dictionary to save.
+ save_directory (`str` or `Path`):
+ The directory in which the model will be saved.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"` or `pytorch_model{suffix}.bin` depending on `safe_serialization`
+ parameter.
+ force_contiguous (`boolean`, *optional*):
+ Forcing the state_dict to be saved as contiguous tensors. This has no effect on the correctness of the
+ model, but it could potentially change performance if the layout of the tensor was chosen specifically for
+ that reason. Defaults to `True`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+ metadata (`Dict[str, str]`, *optional*):
+ Extra information to save along with the model. Some metadata will be added for each dropped tensors.
+ This information will not be enough to recover the entire shared structure but might help understanding
+ things.
+ safe_serialization (`bool`, *optional*):
+ Whether to save as safetensors, which is the default behavior. If `False`, the shards are saved as pickle.
+ Safe serialization is recommended for security reasons. Saving as pickle is deprecated and will be removed
+ in a future version.
+ is_main_process (`bool`, *optional*):
+ Whether the process calling this is the main process or not. Useful when in distributed training like
+ TPUs and need to call this function from all processes. In this case, set `is_main_process=True` only on
+ the main process to avoid race conditions. Defaults to True.
+ shared_tensors_to_discard (`List[str]`, *optional*):
+ List of tensor names to drop when saving shared tensors. If not provided and shared tensors are
+ detected, it will drop the first name alphabetically.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import save_torch_state_dict
+ >>> model = ... # A PyTorch model
+
+ # Save state dict to "path/to/folder". The model will be split into shards of 5GB each and saved as safetensors.
+ >>> state_dict = model_to_save.state_dict()
+ >>> save_torch_state_dict(state_dict, "path/to/folder")
+ ```
+ """
+ save_directory = str(save_directory)
+
+ if filename_pattern is None:
+ filename_pattern = (
+ constants.SAFETENSORS_WEIGHTS_FILE_PATTERN
+ if safe_serialization
+ else constants.PYTORCH_WEIGHTS_FILE_PATTERN
+ )
+
+ if metadata is None:
+ metadata = {}
+ if safe_serialization:
+ try:
+ from safetensors.torch import save_file as save_file_fn
+ except ImportError as e:
+ raise ImportError(
+ "Please install `safetensors` to use safe serialization. "
+ "You can install it with `pip install safetensors`."
+ ) from e
+ # Clean state dict for safetensors
+ state_dict = _clean_state_dict_for_safetensors(
+ state_dict,
+ metadata,
+ force_contiguous=force_contiguous,
+ shared_tensors_to_discard=shared_tensors_to_discard,
+ )
+ else:
+ from torch import save as save_file_fn # type: ignore[assignment]
+
+ logger.warning(
+ "You are using unsafe serialization. Due to security reasons, it is recommended not to load "
+ "pickled models from untrusted sources. If you intend to share your model, we strongly recommend "
+ "using safe serialization by installing `safetensors` with `pip install safetensors`."
+ )
+ # Split dict
+ state_dict_split = split_torch_state_dict_into_shards(
+ state_dict, filename_pattern=filename_pattern, max_shard_size=max_shard_size
+ )
+
+ # Only main process should clean up existing files to avoid race conditions in distributed environment
+ if is_main_process:
+ existing_files_regex = re.compile(filename_pattern.format(suffix=r"(-\d{5}-of-\d{5})?") + r"(\.index\.json)?")
+ for filename in os.listdir(save_directory):
+ if existing_files_regex.match(filename):
+ try:
+ logger.debug(f"Removing existing file '{filename}' from folder.")
+ os.remove(os.path.join(save_directory, filename))
+ except Exception as e:
+ logger.warning(
+ f"Error when trying to remove existing '{filename}' from folder: {e}. Continuing..."
+ )
+
+ # Save each shard
+ per_file_metadata = {"format": "pt"}
+ if not state_dict_split.is_sharded:
+ per_file_metadata.update(metadata)
+ safe_file_kwargs = {"metadata": per_file_metadata} if safe_serialization else {}
+ for filename, tensors in state_dict_split.filename_to_tensors.items():
+ shard = {tensor: state_dict[tensor] for tensor in tensors}
+ save_file_fn(shard, os.path.join(save_directory, filename), **safe_file_kwargs)
+ logger.debug(f"Shard saved to {filename}")
+
+ # Save the index (if any)
+ if state_dict_split.is_sharded:
+ index_path = filename_pattern.format(suffix="") + ".index.json"
+ index = {
+ "metadata": {**state_dict_split.metadata, **metadata},
+ "weight_map": state_dict_split.tensor_to_filename,
+ }
+ with open(os.path.join(save_directory, index_path), "w") as f:
+ json.dump(index, f, indent=2)
+ logger.info(
+ f"The model is bigger than the maximum size per checkpoint ({max_shard_size}). "
+ f"Model weighs have been saved in {len(state_dict_split.filename_to_tensors)} checkpoint shards. "
+ f"You can find where each parameters has been saved in the index located at {index_path}."
+ )
+
+ logger.info(f"Model weights successfully saved to {save_directory}!")
+
+
+def split_torch_state_dict_into_shards(
+ state_dict: Dict[str, "torch.Tensor"],
+ *,
+ filename_pattern: str = constants.SAFETENSORS_WEIGHTS_FILE_PATTERN,
+ max_shard_size: Union[int, str] = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+
+ To save a model state dictionary to the disk, see [`save_torch_state_dict`]. This helper uses
+ `split_torch_state_dict_into_shards` under the hood.
+
+
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, torch.Tensor]`):
+ The state dictionary to save.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+
+ Example:
+ ```py
+ >>> import json
+ >>> import os
+ >>> from safetensors.torch import save_file as safe_save_file
+ >>> from huggingface_hub import split_torch_state_dict_into_shards
+
+ >>> def save_state_dict(state_dict: Dict[str, torch.Tensor], save_directory: str):
+ ... state_dict_split = split_torch_state_dict_into_shards(state_dict)
+ ... for filename, tensors in state_dict_split.filename_to_tensors.items():
+ ... shard = {tensor: state_dict[tensor] for tensor in tensors}
+ ... safe_save_file(
+ ... shard,
+ ... os.path.join(save_directory, filename),
+ ... metadata={"format": "pt"},
+ ... )
+ ... if state_dict_split.is_sharded:
+ ... index = {
+ ... "metadata": state_dict_split.metadata,
+ ... "weight_map": state_dict_split.tensor_to_filename,
+ ... }
+ ... with open(os.path.join(save_directory, "model.safetensors.index.json"), "w") as f:
+ ... f.write(json.dumps(index, indent=2))
+ ```
+ """
+ return split_state_dict_into_shards_factory(
+ state_dict,
+ max_shard_size=max_shard_size,
+ filename_pattern=filename_pattern,
+ get_storage_size=get_torch_storage_size,
+ get_storage_id=get_torch_storage_id,
+ )
+
+
+# LOADING
+
+
+def load_torch_model(
+ model: "torch.nn.Module",
+ checkpoint_path: Union[str, os.PathLike],
+ *,
+ strict: bool = False,
+ safe: bool = True,
+ weights_only: bool = False,
+ map_location: Optional[Union[str, "torch.device"]] = None,
+ mmap: bool = False,
+ filename_pattern: Optional[str] = None,
+) -> NamedTuple:
+ """
+ Load a checkpoint into a model, handling both sharded and non-sharded checkpoints.
+
+ Args:
+ model (`torch.nn.Module`):
+ The model in which to load the checkpoint.
+ checkpoint_path (`str` or `os.PathLike`):
+ Path to either the checkpoint file or directory containing the checkpoint(s).
+ strict (`bool`, *optional*, defaults to `False`):
+ Whether to strictly enforce that the keys in the model state dict match the keys in the checkpoint.
+ safe (`bool`, *optional*, defaults to `True`):
+ If `safe` is True, the safetensors files will be loaded. If `safe` is False, the function
+ will first attempt to load safetensors files if they are available, otherwise it will fall back to loading
+ pickle files. `filename_pattern` parameter takes precedence over `safe` parameter.
+ weights_only (`bool`, *optional*, defaults to `False`):
+ If True, only loads the model weights without optimizer states and other metadata.
+ Only supported in PyTorch >= 1.13.
+ map_location (`str` or `torch.device`, *optional*):
+ A `torch.device` object, string or a dict specifying how to remap storage locations. It
+ indicates the location where all tensors should be loaded.
+ mmap (`bool`, *optional*, defaults to `False`):
+ Whether to use memory-mapped file loading. Memory mapping can improve loading performance
+ for large models in PyTorch >= 2.1.0 with zipfile-based checkpoints.
+ filename_pattern (`str`, *optional*):
+ The pattern to look for the index file. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"`.
+ Returns:
+ `NamedTuple`: A named tuple with `missing_keys` and `unexpected_keys` fields.
+ - `missing_keys` is a list of str containing the missing keys, i.e. keys that are in the model but not in the checkpoint.
+ - `unexpected_keys` is a list of str containing the unexpected keys, i.e. keys that are in the checkpoint but not in the model.
+
+ Raises:
+ [`FileNotFoundError`](https://docs.python.org/3/library/exceptions.html#FileNotFoundError)
+ If the checkpoint file or directory does not exist.
+ [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ If safetensors or torch is not installed when trying to load a .safetensors file or a PyTorch checkpoint respectively.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the checkpoint path is invalid or if the checkpoint format cannot be determined.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import load_torch_model
+ >>> model = ... # A PyTorch model
+ >>> load_torch_model(model, "path/to/checkpoint")
+ ```
+ """
+ checkpoint_path = Path(checkpoint_path)
+
+ if not checkpoint_path.exists():
+ raise ValueError(f"Checkpoint path {checkpoint_path} does not exist")
+ # 1. Check if checkpoint is a single file
+ if checkpoint_path.is_file():
+ state_dict = load_state_dict_from_file(
+ checkpoint_file=checkpoint_path,
+ map_location=map_location,
+ weights_only=weights_only,
+ )
+ return model.load_state_dict(state_dict, strict=strict)
+
+ # 2. If not, checkpoint_path is a directory
+ if filename_pattern is None:
+ filename_pattern = constants.SAFETENSORS_WEIGHTS_FILE_PATTERN
+ index_path = checkpoint_path / (filename_pattern.format(suffix="") + ".index.json")
+ # Only fallback to pickle format if safetensors index is not found and safe is False.
+ if not index_path.is_file() and not safe:
+ filename_pattern = constants.PYTORCH_WEIGHTS_FILE_PATTERN
+
+ index_path = checkpoint_path / (filename_pattern.format(suffix="") + ".index.json")
+
+ if index_path.is_file():
+ return _load_sharded_checkpoint(
+ model=model,
+ save_directory=checkpoint_path,
+ strict=strict,
+ weights_only=weights_only,
+ filename_pattern=filename_pattern,
+ )
+
+ # Look for single model file
+ model_files = list(checkpoint_path.glob("*.safetensors" if safe else "*.bin"))
+ if len(model_files) == 1:
+ state_dict = load_state_dict_from_file(
+ checkpoint_file=model_files[0],
+ map_location=map_location,
+ weights_only=weights_only,
+ mmap=mmap,
+ )
+ return model.load_state_dict(state_dict, strict=strict)
+
+ raise ValueError(
+ f"Directory '{checkpoint_path}' does not contain a valid checkpoint. "
+ "Expected either a sharded checkpoint with an index file, or a single model file."
+ )
+
+
+def _load_sharded_checkpoint(
+ model: "torch.nn.Module",
+ save_directory: os.PathLike,
+ *,
+ strict: bool = False,
+ weights_only: bool = False,
+ filename_pattern: str = constants.SAFETENSORS_WEIGHTS_FILE_PATTERN,
+) -> NamedTuple:
+ """
+ Loads a sharded checkpoint into a model. This is the same as
+ [`torch.nn.Module.load_state_dict`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html?highlight=load_state_dict#torch.nn.Module.load_state_dict)
+ but for a sharded checkpoint. Each shard is loaded one by one and removed from memory after being loaded into the model.
+
+ Args:
+ model (`torch.nn.Module`):
+ The model in which to load the checkpoint.
+ save_directory (`str` or `os.PathLike`):
+ A path to a folder containing the sharded checkpoint.
+ strict (`bool`, *optional*, defaults to `False`):
+ Whether to strictly enforce that the keys in the model state dict match the keys in the sharded checkpoint.
+ weights_only (`bool`, *optional*, defaults to `False`):
+ If True, only loads the model weights without optimizer states and other metadata.
+ Only supported in PyTorch >= 1.13.
+ filename_pattern (`str`, *optional*, defaults to `"model{suffix}.safetensors"`):
+ The pattern to look for the index file. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"`.
+
+ Returns:
+ `NamedTuple`: A named tuple with `missing_keys` and `unexpected_keys` fields,
+ - `missing_keys` is a list of str containing the missing keys
+ - `unexpected_keys` is a list of str containing the unexpected keys
+ """
+
+ # 1. Load and validate index file
+ # The index file contains mapping of parameter names to shard files
+ index_path = filename_pattern.format(suffix="") + ".index.json"
+ index_file = os.path.join(save_directory, index_path)
+ with open(index_file, "r", encoding="utf-8") as f:
+ index = json.load(f)
+
+ # 2. Validate keys if in strict mode
+ # This is done before loading any shards to fail fast
+ if strict:
+ _validate_keys_for_strict_loading(model, index["weight_map"].keys())
+
+ # 3. Load each shard using `load_state_dict`
+ # Get unique shard files (multiple parameters can be in same shard)
+ shard_files = list(set(index["weight_map"].values()))
+ for shard_file in shard_files:
+ # Load shard into memory
+ shard_path = os.path.join(save_directory, shard_file)
+ state_dict = load_state_dict_from_file(
+ shard_path,
+ map_location="cpu",
+ weights_only=weights_only,
+ )
+ # Update model with parameters from this shard
+ model.load_state_dict(state_dict, strict=strict)
+ # Explicitly remove the state dict from memory
+ del state_dict
+
+ # 4. Return compatibility info
+ loaded_keys = set(index["weight_map"].keys())
+ model_keys = set(model.state_dict().keys())
+ return _IncompatibleKeys(
+ missing_keys=list(model_keys - loaded_keys), unexpected_keys=list(loaded_keys - model_keys)
+ )
+
+
+def load_state_dict_from_file(
+ checkpoint_file: Union[str, os.PathLike],
+ map_location: Optional[Union[str, "torch.device"]] = None,
+ weights_only: bool = False,
+ mmap: bool = False,
+) -> Union[Dict[str, "torch.Tensor"], Any]:
+ """
+ Loads a checkpoint file, handling both safetensors and pickle checkpoint formats.
+
+ Args:
+ checkpoint_file (`str` or `os.PathLike`):
+ Path to the checkpoint file to load. Can be either a safetensors or pickle (`.bin`) checkpoint.
+ map_location (`str` or `torch.device`, *optional*):
+ A `torch.device` object, string or a dict specifying how to remap storage locations. It
+ indicates the location where all tensors should be loaded.
+ weights_only (`bool`, *optional*, defaults to `False`):
+ If True, only loads the model weights without optimizer states and other metadata.
+ Only supported for pickle (`.bin`) checkpoints with PyTorch >= 1.13. Has no effect when
+ loading safetensors files.
+ mmap (`bool`, *optional*, defaults to `False`):
+ Whether to use memory-mapped file loading. Memory mapping can improve loading performance
+ for large models in PyTorch >= 2.1.0 with zipfile-based checkpoints. Has no effect when
+ loading safetensors files, as the `safetensors` library uses memory mapping by default.
+
+ Returns:
+ `Union[Dict[str, "torch.Tensor"], Any]`: The loaded checkpoint.
+ - For safetensors files: always returns a dictionary mapping parameter names to tensors.
+ - For pickle files: returns any Python object that was pickled (commonly a state dict, but could be
+ an entire model, optimizer state, or any other Python object).
+
+ Raises:
+ [`FileNotFoundError`](https://docs.python.org/3/library/exceptions.html#FileNotFoundError)
+ If the checkpoint file does not exist.
+ [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ If safetensors or torch is not installed when trying to load a .safetensors file or a PyTorch checkpoint respectively.
+ [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
+ If the checkpoint file format is invalid or if git-lfs files are not properly downloaded.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the checkpoint file path is empty or invalid.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import load_state_dict_from_file
+
+ # Load a PyTorch checkpoint
+ >>> state_dict = load_state_dict_from_file("path/to/model.bin", map_location="cpu")
+ >>> model.load_state_dict(state_dict)
+
+ # Load a safetensors checkpoint
+ >>> state_dict = load_state_dict_from_file("path/to/model.safetensors")
+ >>> model.load_state_dict(state_dict)
+ ```
+ """
+ checkpoint_path = Path(checkpoint_file)
+
+ # Check if file exists and is a regular file (not a directory)
+ if not checkpoint_path.is_file():
+ raise FileNotFoundError(
+ f"No checkpoint file found at '{checkpoint_path}'. Please verify the path is correct and "
+ "the file has been properly downloaded."
+ )
+
+ # Load safetensors checkpoint
+ if checkpoint_path.suffix == ".safetensors":
+ try:
+ from safetensors import safe_open
+ from safetensors.torch import load_file
+ except ImportError as e:
+ raise ImportError(
+ "Please install `safetensors` to load safetensors checkpoint. "
+ "You can install it with `pip install safetensors`."
+ ) from e
+
+ # Check format of the archive
+ with safe_open(checkpoint_file, framework="pt") as f: # type: ignore[attr-defined]
+ metadata = f.metadata()
+ # see comment: https://github.com/huggingface/transformers/blob/3d213b57fe74302e5902d68ed9478c3ad1aaa713/src/transformers/modeling_utils.py#L3966
+ if metadata is not None and metadata.get("format") not in ["pt", "mlx"]:
+ raise OSError(
+ f"The safetensors archive passed at {checkpoint_file} does not contain the valid metadata. Make sure "
+ "you save your model with the `save_torch_model` method."
+ )
+ device = str(map_location.type) if map_location is not None and hasattr(map_location, "type") else map_location
+ # meta device is not supported with safetensors, falling back to CPU
+ if device == "meta":
+ logger.warning("Meta device is not supported with safetensors. Falling back to CPU device.")
+ device = "cpu"
+ return load_file(checkpoint_file, device=device) # type: ignore[arg-type]
+ # Otherwise, load from pickle
+ try:
+ import torch
+ from torch import load
+ except ImportError as e:
+ raise ImportError(
+ "Please install `torch` to load torch tensors. You can install it with `pip install torch`."
+ ) from e
+ # Add additional kwargs, mmap is only supported in torch >= 2.1.0
+ additional_kwargs = {}
+ if version.parse(torch.__version__) >= version.parse("2.1.0"):
+ additional_kwargs["mmap"] = mmap
+
+ # weights_only is only supported in torch >= 1.13.0
+ if version.parse(torch.__version__) >= version.parse("1.13.0"):
+ additional_kwargs["weights_only"] = weights_only
+
+ return load(
+ checkpoint_file,
+ map_location=map_location,
+ **additional_kwargs,
+ )
+
+
+# HELPERS
+
+
+def _validate_keys_for_strict_loading(
+ model: "torch.nn.Module",
+ loaded_keys: Iterable[str],
+) -> None:
+ """
+ Validate that model keys match loaded keys when strict loading is enabled.
+
+ Args:
+ model: The PyTorch model being loaded
+ loaded_keys: The keys present in the checkpoint
+
+ Raises:
+ RuntimeError: If there are missing or unexpected keys in strict mode
+ """
+ loaded_keys_set = set(loaded_keys)
+ model_keys = set(model.state_dict().keys())
+ missing_keys = model_keys - loaded_keys_set # Keys in model but not in checkpoint
+ unexpected_keys = loaded_keys_set - model_keys # Keys in checkpoint but not in model
+
+ if missing_keys or unexpected_keys:
+ error_message = f"Error(s) in loading state_dict for {model.__class__.__name__}"
+ if missing_keys:
+ str_missing_keys = ",".join([f'"{k}"' for k in sorted(missing_keys)])
+ error_message += f"\nMissing key(s): {str_missing_keys}."
+ if unexpected_keys:
+ str_unexpected_keys = ",".join([f'"{k}"' for k in sorted(unexpected_keys)])
+ error_message += f"\nUnexpected key(s): {str_unexpected_keys}."
+ raise RuntimeError(error_message)
+
+
+def _get_unique_id(tensor: "torch.Tensor") -> Union[int, Tuple[Any, ...]]:
+ """Returns a unique id for plain tensor
+ or a (potentially nested) Tuple of unique id for the flattened Tensor
+ if the input is a wrapper tensor subclass Tensor
+ """
+
+ try:
+ # for torch 2.1 and above we can also handle tensor subclasses
+ from torch.utils._python_dispatch import is_traceable_wrapper_subclass
+
+ if is_traceable_wrapper_subclass(tensor):
+ attrs, _ = tensor.__tensor_flatten__() # type: ignore[attr-defined]
+ return tuple(_get_unique_id(getattr(tensor, attr)) for attr in attrs)
+
+ except ImportError:
+ # for torch version less than 2.1, we can fallback to original implementation
+ pass
+
+ if tensor.device.type == "xla" and is_torch_tpu_available():
+ # NOTE: xla tensors dont have storage
+ # use some other unique id to distinguish.
+ # this is a XLA tensor, it must be created using torch_xla's
+ # device. So the following import is safe:
+ import torch_xla # type: ignore[import]
+
+ unique_id = torch_xla._XLAC._xla_get_tensor_id(tensor)
+ else:
+ unique_id = storage_ptr(tensor)
+
+ return unique_id
+
+
+def get_torch_storage_id(tensor: "torch.Tensor") -> Optional[Tuple["torch.device", Union[int, Tuple[Any, ...]], int]]:
+ """
+ Return unique identifier to a tensor storage.
+
+ Multiple different tensors can share the same underlying storage. This identifier is
+ guaranteed to be unique and constant for this tensor's storage during its lifetime. Two tensor storages with
+ non-overlapping lifetimes may have the same id.
+ In the case of meta tensors, we return None since we can't tell if they share the same storage.
+
+ Taken from https://github.com/huggingface/transformers/blob/1ecf5f7c982d761b4daaa96719d162c324187c64/src/transformers/pytorch_utils.py#L278.
+ """
+ if tensor.device.type == "meta":
+ return None
+ else:
+ return tensor.device, _get_unique_id(tensor), get_torch_storage_size(tensor)
+
+
+def get_torch_storage_size(tensor: "torch.Tensor") -> int:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/08db34094e9e59e2f9218f2df133b7b4aaff5a99/bindings/python/py_src/safetensors/torch.py#L31C1-L41C59
+ """
+ try:
+ # for torch 2.1 and above we can also handle tensor subclasses
+ from torch.utils._python_dispatch import is_traceable_wrapper_subclass
+
+ if is_traceable_wrapper_subclass(tensor):
+ attrs, _ = tensor.__tensor_flatten__() # type: ignore[attr-defined]
+ return sum(get_torch_storage_size(getattr(tensor, attr)) for attr in attrs)
+ except ImportError:
+ # for torch version less than 2.1, we can fallback to original implementation
+ pass
+
+ try:
+ return tensor.untyped_storage().nbytes()
+ except AttributeError:
+ # Fallback for torch==1.10
+ try:
+ return tensor.storage().size() * _get_dtype_size(tensor.dtype)
+ except NotImplementedError:
+ # Fallback for meta storage
+ # On torch >=2.0 this is the tensor size
+ return tensor.nelement() * _get_dtype_size(tensor.dtype)
+
+
+@lru_cache()
+def is_torch_tpu_available(check_device=True):
+ """
+ Checks if `torch_xla` is installed and potentially if a TPU is in the environment
+
+ Taken from https://github.com/huggingface/transformers/blob/1ecf5f7c982d761b4daaa96719d162c324187c64/src/transformers/utils/import_utils.py#L463.
+ """
+ if importlib.util.find_spec("torch_xla") is not None:
+ if check_device:
+ # We need to check if `xla_device` can be found, will raise a RuntimeError if not
+ try:
+ import torch_xla.core.xla_model as xm # type: ignore[import]
+
+ _ = xm.xla_device()
+ return True
+ except RuntimeError:
+ return False
+ return True
+ return False
+
+
+def storage_ptr(tensor: "torch.Tensor") -> Union[int, Tuple[Any, ...]]:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L11.
+ """
+ try:
+ # for torch 2.1 and above we can also handle tensor subclasses
+ from torch.utils._python_dispatch import is_traceable_wrapper_subclass
+
+ if is_traceable_wrapper_subclass(tensor):
+ return _get_unique_id(tensor) # type: ignore
+ except ImportError:
+ # for torch version less than 2.1, we can fallback to original implementation
+ pass
+
+ try:
+ return tensor.untyped_storage().data_ptr()
+ except Exception:
+ # Fallback for torch==1.10
+ try:
+ return tensor.storage().data_ptr()
+ except NotImplementedError:
+ # Fallback for meta storage
+ return 0
+
+
+def _clean_state_dict_for_safetensors(
+ state_dict: Dict[str, "torch.Tensor"],
+ metadata: Dict[str, str],
+ force_contiguous: bool = True,
+ shared_tensors_to_discard: Optional[List[str]] = None,
+):
+ """Remove shared tensors from state_dict and update metadata accordingly (for reloading).
+
+ Warning: `state_dict` and `metadata` are mutated in-place!
+
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L155.
+ """
+ to_removes = _remove_duplicate_names(state_dict, discard_names=shared_tensors_to_discard)
+ for kept_name, to_remove_group in to_removes.items():
+ for to_remove in to_remove_group:
+ if metadata is None:
+ metadata = {}
+
+ if to_remove not in metadata:
+ # Do not override user data
+ metadata[to_remove] = kept_name
+ del state_dict[to_remove]
+ if force_contiguous:
+ state_dict = {k: v.contiguous() for k, v in state_dict.items()}
+ return state_dict
+
+
+def _end_ptr(tensor: "torch.Tensor") -> int:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L23.
+ """
+ if tensor.nelement():
+ stop = tensor.view(-1)[-1].data_ptr() + _get_dtype_size(tensor.dtype)
+ else:
+ stop = tensor.data_ptr()
+ return stop
+
+
+def _filter_shared_not_shared(tensors: List[Set[str]], state_dict: Dict[str, "torch.Tensor"]) -> List[Set[str]]:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L44
+ """
+ filtered_tensors = []
+ for shared in tensors:
+ if len(shared) < 2:
+ filtered_tensors.append(shared)
+ continue
+
+ areas = []
+ for name in shared:
+ tensor = state_dict[name]
+ areas.append((tensor.data_ptr(), _end_ptr(tensor), name))
+ areas.sort()
+
+ _, last_stop, last_name = areas[0]
+ filtered_tensors.append({last_name})
+ for start, stop, name in areas[1:]:
+ if start >= last_stop:
+ filtered_tensors.append({name})
+ else:
+ filtered_tensors[-1].add(name)
+ last_stop = stop
+
+ return filtered_tensors
+
+
+def _find_shared_tensors(state_dict: Dict[str, "torch.Tensor"]) -> List[Set[str]]:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L69.
+ """
+ import torch
+
+ tensors_dict = defaultdict(set)
+ for k, v in state_dict.items():
+ if v.device != torch.device("meta") and storage_ptr(v) != 0 and get_torch_storage_size(v) != 0:
+ # Need to add device as key because of multiple GPU.
+ tensors_dict[(v.device, storage_ptr(v), get_torch_storage_size(v))].add(k)
+ tensors = list(sorted(tensors_dict.values()))
+ tensors = _filter_shared_not_shared(tensors, state_dict)
+ return tensors
+
+
+def _is_complete(tensor: "torch.Tensor") -> bool:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L80
+ """
+ try:
+ # for torch 2.1 and above we can also handle tensor subclasses
+ from torch.utils._python_dispatch import is_traceable_wrapper_subclass
+
+ if is_traceable_wrapper_subclass(tensor):
+ attrs, _ = tensor.__tensor_flatten__() # type: ignore[attr-defined]
+ return all(_is_complete(getattr(tensor, attr)) for attr in attrs)
+ except ImportError:
+ # for torch version less than 2.1, we can fallback to original implementation
+ pass
+
+ return tensor.data_ptr() == storage_ptr(tensor) and tensor.nelement() * _get_dtype_size(
+ tensor.dtype
+ ) == get_torch_storage_size(tensor)
+
+
+def _remove_duplicate_names(
+ state_dict: Dict[str, "torch.Tensor"],
+ *,
+ preferred_names: Optional[List[str]] = None,
+ discard_names: Optional[List[str]] = None,
+) -> Dict[str, List[str]]:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/079781fd0dc455ba0fe851e2b4507c33d0c0d407/bindings/python/py_src/safetensors/torch.py#L80
+ """
+ if preferred_names is None:
+ preferred_names = []
+ unique_preferred_names = set(preferred_names)
+ if discard_names is None:
+ discard_names = []
+ unique_discard_names = set(discard_names)
+
+ shareds = _find_shared_tensors(state_dict)
+ to_remove = defaultdict(list)
+ for shared in shareds:
+ complete_names = set([name for name in shared if _is_complete(state_dict[name])])
+ if not complete_names:
+ raise RuntimeError(
+ "Error while trying to find names to remove to save state dict, but found no suitable name to keep"
+ f" for saving amongst: {shared}. None is covering the entire storage. Refusing to save/load the model"
+ " since you could be storing much more memory than needed. Please refer to"
+ " https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an"
+ " issue."
+ )
+
+ keep_name = sorted(list(complete_names))[0]
+
+ # Mechanism to preferentially select keys to keep
+ # coming from the on-disk file to allow
+ # loading models saved with a different choice
+ # of keep_name
+ preferred = complete_names.difference(unique_discard_names)
+ if preferred:
+ keep_name = sorted(list(preferred))[0]
+
+ if unique_preferred_names:
+ preferred = unique_preferred_names.intersection(complete_names)
+ if preferred:
+ keep_name = sorted(list(preferred))[0]
+ for name in sorted(shared):
+ if name != keep_name:
+ to_remove[keep_name].append(name)
+ return to_remove
+
+
+@lru_cache()
+def _get_dtype_size(dtype: "torch.dtype") -> int:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/08db34094e9e59e2f9218f2df133b7b4aaff5a99/bindings/python/py_src/safetensors/torch.py#L344
+ """
+ import torch
+
+ # torch.float8 formats require 2.1; we do not support these dtypes on earlier versions
+ _float8_e4m3fn = getattr(torch, "float8_e4m3fn", None)
+ _float8_e5m2 = getattr(torch, "float8_e5m2", None)
+ _SIZE = {
+ torch.int64: 8,
+ torch.float32: 4,
+ torch.int32: 4,
+ torch.bfloat16: 2,
+ torch.float16: 2,
+ torch.int16: 2,
+ torch.uint8: 1,
+ torch.int8: 1,
+ torch.bool: 1,
+ torch.float64: 8,
+ _float8_e4m3fn: 1,
+ _float8_e5m2: 1,
+ }
+ return _SIZE[dtype]
+
+
+class _IncompatibleKeys(namedtuple("IncompatibleKeys", ["missing_keys", "unexpected_keys"])):
+ """
+ This is used to report missing and unexpected keys in the state dict.
+ Taken from https://github.com/pytorch/pytorch/blob/main/torch/nn/modules/module.py#L52.
+
+ """
+
+ def __repr__(self) -> str:
+ if not self.missing_keys and not self.unexpected_keys:
+ return ""
+ return super().__repr__()
+
+ __str__ = __repr__
diff --git a/env/Lib/site-packages/huggingface_hub/templates/datasetcard_template.md b/env/Lib/site-packages/huggingface_hub/templates/datasetcard_template.md
new file mode 100644
index 0000000000000000000000000000000000000000..9af29ebbed93653ec74a8952e314e7554323ef15
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/templates/datasetcard_template.md
@@ -0,0 +1,143 @@
+---
+# For reference on dataset card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/datasetcard.md?plain=1
+# Doc / guide: https://huggingface.co/docs/hub/datasets-cards
+{{ card_data }}
+---
+
+# Dataset Card for {{ pretty_name | default("Dataset Name", true) }}
+
+
+
+{{ dataset_summary | default("", true) }}
+
+## Dataset Details
+
+### Dataset Description
+
+
+
+{{ dataset_description | default("", true) }}
+
+- **Curated by:** {{ curators | default("[More Information Needed]", true)}}
+- **Funded by [optional]:** {{ funded_by | default("[More Information Needed]", true)}}
+- **Shared by [optional]:** {{ shared_by | default("[More Information Needed]", true)}}
+- **Language(s) (NLP):** {{ language | default("[More Information Needed]", true)}}
+- **License:** {{ license | default("[More Information Needed]", true)}}
+
+### Dataset Sources [optional]
+
+
+
+- **Repository:** {{ repo | default("[More Information Needed]", true)}}
+- **Paper [optional]:** {{ paper | default("[More Information Needed]", true)}}
+- **Demo [optional]:** {{ demo | default("[More Information Needed]", true)}}
+
+## Uses
+
+
+
+### Direct Use
+
+
+
+{{ direct_use | default("[More Information Needed]", true)}}
+
+### Out-of-Scope Use
+
+
+
+{{ out_of_scope_use | default("[More Information Needed]", true)}}
+
+## Dataset Structure
+
+
+
+{{ dataset_structure | default("[More Information Needed]", true)}}
+
+## Dataset Creation
+
+### Curation Rationale
+
+
+
+{{ curation_rationale_section | default("[More Information Needed]", true)}}
+
+### Source Data
+
+
+
+#### Data Collection and Processing
+
+
+
+{{ data_collection_and_processing_section | default("[More Information Needed]", true)}}
+
+#### Who are the source data producers?
+
+
+
+{{ source_data_producers_section | default("[More Information Needed]", true)}}
+
+### Annotations [optional]
+
+
+
+#### Annotation process
+
+
+
+{{ annotation_process_section | default("[More Information Needed]", true)}}
+
+#### Who are the annotators?
+
+
+
+{{ who_are_annotators_section | default("[More Information Needed]", true)}}
+
+#### Personal and Sensitive Information
+
+
+
+{{ personal_and_sensitive_information | default("[More Information Needed]", true)}}
+
+## Bias, Risks, and Limitations
+
+
+
+{{ bias_risks_limitations | default("[More Information Needed]", true)}}
+
+### Recommendations
+
+
+
+{{ bias_recommendations | default("Users should be made aware of the risks, biases and limitations of the dataset. More information needed for further recommendations.", true)}}
+
+## Citation [optional]
+
+
+
+**BibTeX:**
+
+{{ citation_bibtex | default("[More Information Needed]", true)}}
+
+**APA:**
+
+{{ citation_apa | default("[More Information Needed]", true)}}
+
+## Glossary [optional]
+
+
+
+{{ glossary | default("[More Information Needed]", true)}}
+
+## More Information [optional]
+
+{{ more_information | default("[More Information Needed]", true)}}
+
+## Dataset Card Authors [optional]
+
+{{ dataset_card_authors | default("[More Information Needed]", true)}}
+
+## Dataset Card Contact
+
+{{ dataset_card_contact | default("[More Information Needed]", true)}}
diff --git a/env/Lib/site-packages/huggingface_hub/templates/modelcard_template.md b/env/Lib/site-packages/huggingface_hub/templates/modelcard_template.md
new file mode 100644
index 0000000000000000000000000000000000000000..79ca15e4547debac763b390ef8e4b715e6f6403f
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/templates/modelcard_template.md
@@ -0,0 +1,200 @@
+---
+# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
+# Doc / guide: https://huggingface.co/docs/hub/model-cards
+{{ card_data }}
+---
+
+# Model Card for {{ model_id | default("Model ID", true) }}
+
+
+
+{{ model_summary | default("", true) }}
+
+## Model Details
+
+### Model Description
+
+
+
+{{ model_description | default("", true) }}
+
+- **Developed by:** {{ developers | default("[More Information Needed]", true)}}
+- **Funded by [optional]:** {{ funded_by | default("[More Information Needed]", true)}}
+- **Shared by [optional]:** {{ shared_by | default("[More Information Needed]", true)}}
+- **Model type:** {{ model_type | default("[More Information Needed]", true)}}
+- **Language(s) (NLP):** {{ language | default("[More Information Needed]", true)}}
+- **License:** {{ license | default("[More Information Needed]", true)}}
+- **Finetuned from model [optional]:** {{ base_model | default("[More Information Needed]", true)}}
+
+### Model Sources [optional]
+
+
+
+- **Repository:** {{ repo | default("[More Information Needed]", true)}}
+- **Paper [optional]:** {{ paper | default("[More Information Needed]", true)}}
+- **Demo [optional]:** {{ demo | default("[More Information Needed]", true)}}
+
+## Uses
+
+
+
+### Direct Use
+
+
+
+{{ direct_use | default("[More Information Needed]", true)}}
+
+### Downstream Use [optional]
+
+
+
+{{ downstream_use | default("[More Information Needed]", true)}}
+
+### Out-of-Scope Use
+
+
+
+{{ out_of_scope_use | default("[More Information Needed]", true)}}
+
+## Bias, Risks, and Limitations
+
+
+
+{{ bias_risks_limitations | default("[More Information Needed]", true)}}
+
+### Recommendations
+
+
+
+{{ bias_recommendations | default("Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.", true)}}
+
+## How to Get Started with the Model
+
+Use the code below to get started with the model.
+
+{{ get_started_code | default("[More Information Needed]", true)}}
+
+## Training Details
+
+### Training Data
+
+
+
+{{ training_data | default("[More Information Needed]", true)}}
+
+### Training Procedure
+
+
+
+#### Preprocessing [optional]
+
+{{ preprocessing | default("[More Information Needed]", true)}}
+
+
+#### Training Hyperparameters
+
+- **Training regime:** {{ training_regime | default("[More Information Needed]", true)}}
+
+#### Speeds, Sizes, Times [optional]
+
+
+
+{{ speeds_sizes_times | default("[More Information Needed]", true)}}
+
+## Evaluation
+
+
+
+### Testing Data, Factors & Metrics
+
+#### Testing Data
+
+
+
+{{ testing_data | default("[More Information Needed]", true)}}
+
+#### Factors
+
+
+
+{{ testing_factors | default("[More Information Needed]", true)}}
+
+#### Metrics
+
+
+
+{{ testing_metrics | default("[More Information Needed]", true)}}
+
+### Results
+
+{{ results | default("[More Information Needed]", true)}}
+
+#### Summary
+
+{{ results_summary | default("", true) }}
+
+## Model Examination [optional]
+
+
+
+{{ model_examination | default("[More Information Needed]", true)}}
+
+## Environmental Impact
+
+
+
+Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700).
+
+- **Hardware Type:** {{ hardware_type | default("[More Information Needed]", true)}}
+- **Hours used:** {{ hours_used | default("[More Information Needed]", true)}}
+- **Cloud Provider:** {{ cloud_provider | default("[More Information Needed]", true)}}
+- **Compute Region:** {{ cloud_region | default("[More Information Needed]", true)}}
+- **Carbon Emitted:** {{ co2_emitted | default("[More Information Needed]", true)}}
+
+## Technical Specifications [optional]
+
+### Model Architecture and Objective
+
+{{ model_specs | default("[More Information Needed]", true)}}
+
+### Compute Infrastructure
+
+{{ compute_infrastructure | default("[More Information Needed]", true)}}
+
+#### Hardware
+
+{{ hardware_requirements | default("[More Information Needed]", true)}}
+
+#### Software
+
+{{ software | default("[More Information Needed]", true)}}
+
+## Citation [optional]
+
+
+
+**BibTeX:**
+
+{{ citation_bibtex | default("[More Information Needed]", true)}}
+
+**APA:**
+
+{{ citation_apa | default("[More Information Needed]", true)}}
+
+## Glossary [optional]
+
+
+
+{{ glossary | default("[More Information Needed]", true)}}
+
+## More Information [optional]
+
+{{ more_information | default("[More Information Needed]", true)}}
+
+## Model Card Authors [optional]
+
+{{ model_card_authors | default("[More Information Needed]", true)}}
+
+## Model Card Contact
+
+{{ model_card_contact | default("[More Information Needed]", true)}}
diff --git a/env/Lib/site-packages/huggingface_hub/utils/__init__.py b/env/Lib/site-packages/huggingface_hub/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9715dc0ad0b278fb91ba14e92a4c68905979762
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/__init__.py
@@ -0,0 +1,110 @@
+# coding=utf-8
+# Copyright 2021 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# ruff: noqa: F401
+
+from huggingface_hub.errors import (
+ BadRequestError,
+ CacheNotFound,
+ CorruptedCacheException,
+ DisabledRepoError,
+ EntryNotFoundError,
+ FileMetadataError,
+ GatedRepoError,
+ HfHubHTTPError,
+ HFValidationError,
+ LocalEntryNotFoundError,
+ LocalTokenNotFoundError,
+ NotASafetensorsRepoError,
+ OfflineModeIsEnabled,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+ SafetensorsParsingError,
+)
+
+from . import tqdm as _tqdm # _tqdm is the module
+from ._auth import get_stored_tokens, get_token
+from ._cache_assets import cached_assets_path
+from ._cache_manager import (
+ CachedFileInfo,
+ CachedRepoInfo,
+ CachedRevisionInfo,
+ DeleteCacheStrategy,
+ HFCacheInfo,
+ scan_cache_dir,
+)
+from ._chunk_utils import chunk_iterable
+from ._datetime import parse_datetime
+from ._experimental import experimental
+from ._fixes import SoftTemporaryDirectory, WeakFileLock, yaml_dump
+from ._git_credential import list_credential_helpers, set_git_credential, unset_git_credential
+from ._headers import build_hf_headers, get_token_to_send
+from ._hf_folder import HfFolder
+from ._http import (
+ configure_http_backend,
+ fix_hf_endpoint_in_url,
+ get_session,
+ hf_raise_for_status,
+ http_backoff,
+ reset_sessions,
+)
+from ._pagination import paginate
+from ._paths import DEFAULT_IGNORE_PATTERNS, FORBIDDEN_FOLDERS, filter_repo_objects
+from ._runtime import (
+ dump_environment_info,
+ get_aiohttp_version,
+ get_fastai_version,
+ get_fastapi_version,
+ get_fastcore_version,
+ get_gradio_version,
+ get_graphviz_version,
+ get_hf_hub_version,
+ get_hf_transfer_version,
+ get_jinja_version,
+ get_numpy_version,
+ get_pillow_version,
+ get_pydantic_version,
+ get_pydot_version,
+ get_python_version,
+ get_tensorboard_version,
+ get_tf_version,
+ get_torch_version,
+ is_aiohttp_available,
+ is_colab_enterprise,
+ is_fastai_available,
+ is_fastapi_available,
+ is_fastcore_available,
+ is_google_colab,
+ is_gradio_available,
+ is_graphviz_available,
+ is_hf_transfer_available,
+ is_jinja_available,
+ is_notebook,
+ is_numpy_available,
+ is_package_available,
+ is_pillow_available,
+ is_pydantic_available,
+ is_pydot_available,
+ is_safetensors_available,
+ is_tensorboard_available,
+ is_tf_available,
+ is_torch_available,
+)
+from ._safetensors import SafetensorsFileMetadata, SafetensorsRepoMetadata, TensorInfo
+from ._subprocess import capture_output, run_interactive_subprocess, run_subprocess
+from ._telemetry import send_telemetry
+from ._typing import is_jsonable, is_simple_optional_type, unwrap_simple_optional_type
+from ._validators import smoothly_deprecate_use_auth_token, validate_hf_hub_args, validate_repo_id
+from .tqdm import are_progress_bars_disabled, disable_progress_bars, enable_progress_bars, tqdm, tqdm_stream_file
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_auth.py b/env/Lib/site-packages/huggingface_hub/utils/_auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..c70280aec4e6ca1ca11f6452d1bf9e57e9953834
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_auth.py
@@ -0,0 +1,214 @@
+# Copyright 2023 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains an helper to get the token from machine (env variable, secret or config file)."""
+
+import configparser
+import logging
+import os
+import warnings
+from pathlib import Path
+from threading import Lock
+from typing import Dict, Optional
+
+from .. import constants
+from ._runtime import is_colab_enterprise, is_google_colab
+
+
+_IS_GOOGLE_COLAB_CHECKED = False
+_GOOGLE_COLAB_SECRET_LOCK = Lock()
+_GOOGLE_COLAB_SECRET: Optional[str] = None
+
+logger = logging.getLogger(__name__)
+
+
+def get_token() -> Optional[str]:
+ """
+ Get token if user is logged in.
+
+ Note: in most cases, you should use [`huggingface_hub.utils.build_hf_headers`] instead. This method is only useful
+ if you want to retrieve the token for other purposes than sending an HTTP request.
+
+ Token is retrieved in priority from the `HF_TOKEN` environment variable. Otherwise, we read the token file located
+ in the Hugging Face home folder. Returns None if user is not logged in. To log in, use [`login`] or
+ `huggingface-cli login`.
+
+ Returns:
+ `str` or `None`: The token, `None` if it doesn't exist.
+ """
+ return _get_token_from_google_colab() or _get_token_from_environment() or _get_token_from_file()
+
+
+def _get_token_from_google_colab() -> Optional[str]:
+ """Get token from Google Colab secrets vault using `google.colab.userdata.get(...)`.
+
+ Token is read from the vault only once per session and then stored in a global variable to avoid re-requesting
+ access to the vault.
+ """
+ # If it's not a Google Colab or it's Colab Enterprise, fallback to environment variable or token file authentication
+ if not is_google_colab() or is_colab_enterprise():
+ return None
+
+ # `google.colab.userdata` is not thread-safe
+ # This can lead to a deadlock if multiple threads try to access it at the same time
+ # (typically when using `snapshot_download`)
+ # => use a lock
+ # See https://github.com/huggingface/huggingface_hub/issues/1952 for more details.
+ with _GOOGLE_COLAB_SECRET_LOCK:
+ global _GOOGLE_COLAB_SECRET
+ global _IS_GOOGLE_COLAB_CHECKED
+
+ if _IS_GOOGLE_COLAB_CHECKED: # request access only once
+ return _GOOGLE_COLAB_SECRET
+
+ try:
+ from google.colab import userdata # type: ignore
+ from google.colab.errors import Error as ColabError # type: ignore
+ except ImportError:
+ return None
+
+ try:
+ token = userdata.get("HF_TOKEN")
+ _GOOGLE_COLAB_SECRET = _clean_token(token)
+ except userdata.NotebookAccessError:
+ # Means the user has a secret call `HF_TOKEN` and got a popup "please grand access to HF_TOKEN" and refused it
+ # => warn user but ignore error => do not re-request access to user
+ warnings.warn(
+ "\nAccess to the secret `HF_TOKEN` has not been granted on this notebook."
+ "\nYou will not be requested again."
+ "\nPlease restart the session if you want to be prompted again."
+ )
+ _GOOGLE_COLAB_SECRET = None
+ except userdata.SecretNotFoundError:
+ # Means the user did not define a `HF_TOKEN` secret => warn
+ warnings.warn(
+ "\nThe secret `HF_TOKEN` does not exist in your Colab secrets."
+ "\nTo authenticate with the Hugging Face Hub, create a token in your settings tab "
+ "(https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session."
+ "\nYou will be able to reuse this secret in all of your notebooks."
+ "\nPlease note that authentication is recommended but still optional to access public models or datasets."
+ )
+ _GOOGLE_COLAB_SECRET = None
+ except ColabError as e:
+ # Something happen but we don't know what => recommend to open a GitHub issue
+ warnings.warn(
+ f"\nError while fetching `HF_TOKEN` secret value from your vault: '{str(e)}'."
+ "\nYou are not authenticated with the Hugging Face Hub in this notebook."
+ "\nIf the error persists, please let us know by opening an issue on GitHub "
+ "(https://github.com/huggingface/huggingface_hub/issues/new)."
+ )
+ _GOOGLE_COLAB_SECRET = None
+
+ _IS_GOOGLE_COLAB_CHECKED = True
+ return _GOOGLE_COLAB_SECRET
+
+
+def _get_token_from_environment() -> Optional[str]:
+ # `HF_TOKEN` has priority (keep `HUGGING_FACE_HUB_TOKEN` for backward compatibility)
+ return _clean_token(os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN"))
+
+
+def _get_token_from_file() -> Optional[str]:
+ try:
+ return _clean_token(Path(constants.HF_TOKEN_PATH).read_text())
+ except FileNotFoundError:
+ return None
+
+
+def get_stored_tokens() -> Dict[str, str]:
+ """
+ Returns the parsed INI file containing the access tokens.
+ The file is located at `HF_STORED_TOKENS_PATH`, defaulting to `~/.cache/huggingface/stored_tokens`.
+ If the file does not exist, an empty dictionary is returned.
+
+ Returns: `Dict[str, str]`
+ Key is the token name and value is the token.
+ """
+ tokens_path = Path(constants.HF_STORED_TOKENS_PATH)
+ if not tokens_path.exists():
+ stored_tokens = {}
+ config = configparser.ConfigParser()
+ try:
+ config.read(tokens_path)
+ stored_tokens = {token_name: config.get(token_name, "hf_token") for token_name in config.sections()}
+ except configparser.Error as e:
+ logger.error(f"Error parsing stored tokens file: {e}")
+ stored_tokens = {}
+ return stored_tokens
+
+
+def _save_stored_tokens(stored_tokens: Dict[str, str]) -> None:
+ """
+ Saves the given configuration to the stored tokens file.
+
+ Args:
+ stored_tokens (`Dict[str, str]`):
+ The stored tokens to save. Key is the token name and value is the token.
+ """
+ stored_tokens_path = Path(constants.HF_STORED_TOKENS_PATH)
+
+ # Write the stored tokens into an INI file
+ config = configparser.ConfigParser()
+ for token_name in sorted(stored_tokens.keys()):
+ config.add_section(token_name)
+ config.set(token_name, "hf_token", stored_tokens[token_name])
+
+ stored_tokens_path.parent.mkdir(parents=True, exist_ok=True)
+ with stored_tokens_path.open("w") as config_file:
+ config.write(config_file)
+
+
+def _get_token_by_name(token_name: str) -> Optional[str]:
+ """
+ Get the token by name.
+
+ Args:
+ token_name (`str`):
+ The name of the token to get.
+
+ Returns:
+ `str` or `None`: The token, `None` if it doesn't exist.
+
+ """
+ stored_tokens = get_stored_tokens()
+ if token_name not in stored_tokens:
+ return None
+ return _clean_token(stored_tokens[token_name])
+
+
+def _save_token(token: str, token_name: str) -> None:
+ """
+ Save the given token.
+
+ If the stored tokens file does not exist, it will be created.
+ Args:
+ token (`str`):
+ The token to save.
+ token_name (`str`):
+ The name of the token.
+ """
+ tokens_path = Path(constants.HF_STORED_TOKENS_PATH)
+ stored_tokens = get_stored_tokens()
+ stored_tokens[token_name] = token
+ _save_stored_tokens(stored_tokens)
+ logger.info(f"The token `{token_name}` has been saved to {tokens_path}")
+
+
+def _clean_token(token: Optional[str]) -> Optional[str]:
+ """Clean token by removing trailing and leading spaces and newlines.
+
+ If token is an empty string, return None.
+ """
+ if token is None:
+ return None
+ return token.replace("\r", "").replace("\n", "").strip() or None
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_cache_assets.py b/env/Lib/site-packages/huggingface_hub/utils/_cache_assets.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5d435df9b0bb0c67c0bcb5ef65711e9aef367f6
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_cache_assets.py
@@ -0,0 +1,135 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from pathlib import Path
+from typing import Union
+
+from ..constants import HF_ASSETS_CACHE
+
+
+def cached_assets_path(
+ library_name: str,
+ namespace: str = "default",
+ subfolder: str = "default",
+ *,
+ assets_dir: Union[str, Path, None] = None,
+):
+ """Return a folder path to cache arbitrary files.
+
+ `huggingface_hub` provides a canonical folder path to store assets. This is the
+ recommended way to integrate cache in a downstream library as it will benefit from
+ the builtins tools to scan and delete the cache properly.
+
+ The distinction is made between files cached from the Hub and assets. Files from the
+ Hub are cached in a git-aware manner and entirely managed by `huggingface_hub`. See
+ [related documentation](https://huggingface.co/docs/huggingface_hub/how-to-cache).
+ All other files that a downstream library caches are considered to be "assets"
+ (files downloaded from external sources, extracted from a .tar archive, preprocessed
+ for training,...).
+
+ Once the folder path is generated, it is guaranteed to exist and to be a directory.
+ The path is based on 3 levels of depth: the library name, a namespace and a
+ subfolder. Those 3 levels grants flexibility while allowing `huggingface_hub` to
+ expect folders when scanning/deleting parts of the assets cache. Within a library,
+ it is expected that all namespaces share the same subset of subfolder names but this
+ is not a mandatory rule. The downstream library has then full control on which file
+ structure to adopt within its cache. Namespace and subfolder are optional (would
+ default to a `"default/"` subfolder) but library name is mandatory as we want every
+ downstream library to manage its own cache.
+
+ Expected tree:
+ ```text
+ assets/
+ └── datasets/
+ │ ├── SQuAD/
+ │ │ ├── downloaded/
+ │ │ ├── extracted/
+ │ │ └── processed/
+ │ ├── Helsinki-NLP--tatoeba_mt/
+ │ ├── downloaded/
+ │ ├── extracted/
+ │ └── processed/
+ └── transformers/
+ ├── default/
+ │ ├── something/
+ ├── bert-base-cased/
+ │ ├── default/
+ │ └── training/
+ hub/
+ └── models--julien-c--EsperBERTo-small/
+ ├── blobs/
+ │ ├── (...)
+ │ ├── (...)
+ ├── refs/
+ │ └── (...)
+ └── [ 128] snapshots/
+ ├── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
+ │ ├── (...)
+ └── bbc77c8132af1cc5cf678da3f1ddf2de43606d48/
+ └── (...)
+ ```
+
+
+ Args:
+ library_name (`str`):
+ Name of the library that will manage the cache folder. Example: `"dataset"`.
+ namespace (`str`, *optional*, defaults to "default"):
+ Namespace to which the data belongs. Example: `"SQuAD"`.
+ subfolder (`str`, *optional*, defaults to "default"):
+ Subfolder in which the data will be stored. Example: `extracted`.
+ assets_dir (`str`, `Path`, *optional*):
+ Path to the folder where assets are cached. This must not be the same folder
+ where Hub files are cached. Defaults to `HF_HOME / "assets"` if not provided.
+ Can also be set with `HF_ASSETS_CACHE` environment variable.
+
+ Returns:
+ Path to the cache folder (`Path`).
+
+ Example:
+ ```py
+ >>> from huggingface_hub import cached_assets_path
+
+ >>> cached_assets_path(library_name="datasets", namespace="SQuAD", subfolder="download")
+ PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/SQuAD/download')
+
+ >>> cached_assets_path(library_name="datasets", namespace="SQuAD", subfolder="extracted")
+ PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/SQuAD/extracted')
+
+ >>> cached_assets_path(library_name="datasets", namespace="Helsinki-NLP/tatoeba_mt")
+ PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/Helsinki-NLP--tatoeba_mt/default')
+
+ >>> cached_assets_path(library_name="datasets", assets_dir="/tmp/tmp123456")
+ PosixPath('/tmp/tmp123456/datasets/default/default')
+ ```
+ """
+ # Resolve assets_dir
+ if assets_dir is None:
+ assets_dir = HF_ASSETS_CACHE
+ assets_dir = Path(assets_dir).expanduser().resolve()
+
+ # Avoid names that could create path issues
+ for part in (" ", "/", "\\"):
+ library_name = library_name.replace(part, "--")
+ namespace = namespace.replace(part, "--")
+ subfolder = subfolder.replace(part, "--")
+
+ # Path to subfolder is created
+ path = assets_dir / library_name / namespace / subfolder
+ try:
+ path.mkdir(exist_ok=True, parents=True)
+ except (FileExistsError, NotADirectoryError):
+ raise ValueError(f"Corrupted assets folder: cannot create directory because of an existing file ({path}).")
+
+ # Return
+ return path
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_cache_manager.py b/env/Lib/site-packages/huggingface_hub/utils/_cache_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..21469c97aff138a4bd015dc537d5809ef97cf88e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_cache_manager.py
@@ -0,0 +1,896 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to manage the HF cache directory."""
+
+import os
+import shutil
+import time
+from collections import defaultdict
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Dict, FrozenSet, List, Literal, Optional, Set, Union
+
+from huggingface_hub.errors import CacheNotFound, CorruptedCacheException
+
+from ..commands._cli_utils import tabulate
+from ..constants import HF_HUB_CACHE
+from . import logging
+
+
+logger = logging.get_logger(__name__)
+
+REPO_TYPE_T = Literal["model", "dataset", "space"]
+
+# List of OS-created helper files that need to be ignored
+FILES_TO_IGNORE = [".DS_Store"]
+
+
+@dataclass(frozen=True)
+class CachedFileInfo:
+ """Frozen data structure holding information about a single cached file.
+
+ Args:
+ file_name (`str`):
+ Name of the file. Example: `config.json`.
+ file_path (`Path`):
+ Path of the file in the `snapshots` directory. The file path is a symlink
+ referring to a blob in the `blobs` folder.
+ blob_path (`Path`):
+ Path of the blob file. This is equivalent to `file_path.resolve()`.
+ size_on_disk (`int`):
+ Size of the blob file in bytes.
+ blob_last_accessed (`float`):
+ Timestamp of the last time the blob file has been accessed (from any
+ revision).
+ blob_last_modified (`float`):
+ Timestamp of the last time the blob file has been modified/created.
+
+
+
+ `blob_last_accessed` and `blob_last_modified` reliability can depend on the OS you
+ are using. See [python documentation](https://docs.python.org/3/library/os.html#os.stat_result)
+ for more details.
+
+
+ """
+
+ file_name: str
+ file_path: Path
+ blob_path: Path
+ size_on_disk: int
+
+ blob_last_accessed: float
+ blob_last_modified: float
+
+ @property
+ def blob_last_accessed_str(self) -> str:
+ """
+ (property) Timestamp of the last time the blob file has been accessed (from any
+ revision), returned as a human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.blob_last_accessed)
+
+ @property
+ def blob_last_modified_str(self) -> str:
+ """
+ (property) Timestamp of the last time the blob file has been modified, returned
+ as a human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.blob_last_modified)
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Size of the blob file as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+
+@dataclass(frozen=True)
+class CachedRevisionInfo:
+ """Frozen data structure holding information about a revision.
+
+ A revision correspond to a folder in the `snapshots` folder and is populated with
+ the exact tree structure as the repo on the Hub but contains only symlinks. A
+ revision can be either referenced by 1 or more `refs` or be "detached" (no refs).
+
+ Args:
+ commit_hash (`str`):
+ Hash of the revision (unique).
+ Example: `"9338f7b671827df886678df2bdd7cc7b4f36dffd"`.
+ snapshot_path (`Path`):
+ Path to the revision directory in the `snapshots` folder. It contains the
+ exact tree structure as the repo on the Hub.
+ files: (`FrozenSet[CachedFileInfo]`):
+ Set of [`~CachedFileInfo`] describing all files contained in the snapshot.
+ refs (`FrozenSet[str]`):
+ Set of `refs` pointing to this revision. If the revision has no `refs`, it
+ is considered detached.
+ Example: `{"main", "2.4.0"}` or `{"refs/pr/1"}`.
+ size_on_disk (`int`):
+ Sum of the blob file sizes that are symlink-ed by the revision.
+ last_modified (`float`):
+ Timestamp of the last time the revision has been created/modified.
+
+
+
+ `last_accessed` cannot be determined correctly on a single revision as blob files
+ are shared across revisions.
+
+
+
+
+
+ `size_on_disk` is not necessarily the sum of all file sizes because of possible
+ duplicated files. Besides, only blobs are taken into account, not the (negligible)
+ size of folders and symlinks.
+
+
+ """
+
+ commit_hash: str
+ snapshot_path: Path
+ size_on_disk: int
+ files: FrozenSet[CachedFileInfo]
+ refs: FrozenSet[str]
+
+ last_modified: float
+
+ @property
+ def last_modified_str(self) -> str:
+ """
+ (property) Timestamp of the last time the revision has been modified, returned
+ as a human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.last_modified)
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Sum of the blob file sizes as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+ @property
+ def nb_files(self) -> int:
+ """
+ (property) Total number of files in the revision.
+ """
+ return len(self.files)
+
+
+@dataclass(frozen=True)
+class CachedRepoInfo:
+ """Frozen data structure holding information about a cached repository.
+
+ Args:
+ repo_id (`str`):
+ Repo id of the repo on the Hub. Example: `"google/fleurs"`.
+ repo_type (`Literal["dataset", "model", "space"]`):
+ Type of the cached repo.
+ repo_path (`Path`):
+ Local path to the cached repo.
+ size_on_disk (`int`):
+ Sum of the blob file sizes in the cached repo.
+ nb_files (`int`):
+ Total number of blob files in the cached repo.
+ revisions (`FrozenSet[CachedRevisionInfo]`):
+ Set of [`~CachedRevisionInfo`] describing all revisions cached in the repo.
+ last_accessed (`float`):
+ Timestamp of the last time a blob file of the repo has been accessed.
+ last_modified (`float`):
+ Timestamp of the last time a blob file of the repo has been modified/created.
+
+
+
+ `size_on_disk` is not necessarily the sum of all revisions sizes because of
+ duplicated files. Besides, only blobs are taken into account, not the (negligible)
+ size of folders and symlinks.
+
+
+
+
+
+ `last_accessed` and `last_modified` reliability can depend on the OS you are using.
+ See [python documentation](https://docs.python.org/3/library/os.html#os.stat_result)
+ for more details.
+
+
+ """
+
+ repo_id: str
+ repo_type: REPO_TYPE_T
+ repo_path: Path
+ size_on_disk: int
+ nb_files: int
+ revisions: FrozenSet[CachedRevisionInfo]
+
+ last_accessed: float
+ last_modified: float
+
+ @property
+ def last_accessed_str(self) -> str:
+ """
+ (property) Last time a blob file of the repo has been accessed, returned as a
+ human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.last_accessed)
+
+ @property
+ def last_modified_str(self) -> str:
+ """
+ (property) Last time a blob file of the repo has been modified, returned as a
+ human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.last_modified)
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Sum of the blob file sizes as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+ @property
+ def refs(self) -> Dict[str, CachedRevisionInfo]:
+ """
+ (property) Mapping between `refs` and revision data structures.
+ """
+ return {ref: revision for revision in self.revisions for ref in revision.refs}
+
+
+@dataclass(frozen=True)
+class DeleteCacheStrategy:
+ """Frozen data structure holding the strategy to delete cached revisions.
+
+ This object is not meant to be instantiated programmatically but to be returned by
+ [`~utils.HFCacheInfo.delete_revisions`]. See documentation for usage example.
+
+ Args:
+ expected_freed_size (`float`):
+ Expected freed size once strategy is executed.
+ blobs (`FrozenSet[Path]`):
+ Set of blob file paths to be deleted.
+ refs (`FrozenSet[Path]`):
+ Set of reference file paths to be deleted.
+ repos (`FrozenSet[Path]`):
+ Set of entire repo paths to be deleted.
+ snapshots (`FrozenSet[Path]`):
+ Set of snapshots to be deleted (directory of symlinks).
+ """
+
+ expected_freed_size: int
+ blobs: FrozenSet[Path]
+ refs: FrozenSet[Path]
+ repos: FrozenSet[Path]
+ snapshots: FrozenSet[Path]
+
+ @property
+ def expected_freed_size_str(self) -> str:
+ """
+ (property) Expected size that will be freed as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.expected_freed_size)
+
+ def execute(self) -> None:
+ """Execute the defined strategy.
+
+
+
+ If this method is interrupted, the cache might get corrupted. Deletion order is
+ implemented so that references and symlinks are deleted before the actual blob
+ files.
+
+
+
+
+
+ This method is irreversible. If executed, cached files are erased and must be
+ downloaded again.
+
+
+ """
+ # Deletion order matters. Blobs are deleted in last so that the user can't end
+ # up in a state where a `ref`` refers to a missing snapshot or a snapshot
+ # symlink refers to a deleted blob.
+
+ # Delete entire repos
+ for path in self.repos:
+ _try_delete_path(path, path_type="repo")
+
+ # Delete snapshot directories
+ for path in self.snapshots:
+ _try_delete_path(path, path_type="snapshot")
+
+ # Delete refs files
+ for path in self.refs:
+ _try_delete_path(path, path_type="ref")
+
+ # Delete blob files
+ for path in self.blobs:
+ _try_delete_path(path, path_type="blob")
+
+ logger.info(f"Cache deletion done. Saved {self.expected_freed_size_str}.")
+
+
+@dataclass(frozen=True)
+class HFCacheInfo:
+ """Frozen data structure holding information about the entire cache-system.
+
+ This data structure is returned by [`scan_cache_dir`] and is immutable.
+
+ Args:
+ size_on_disk (`int`):
+ Sum of all valid repo sizes in the cache-system.
+ repos (`FrozenSet[CachedRepoInfo]`):
+ Set of [`~CachedRepoInfo`] describing all valid cached repos found on the
+ cache-system while scanning.
+ warnings (`List[CorruptedCacheException]`):
+ List of [`~CorruptedCacheException`] that occurred while scanning the cache.
+ Those exceptions are captured so that the scan can continue. Corrupted repos
+ are skipped from the scan.
+
+
+
+ Here `size_on_disk` is equal to the sum of all repo sizes (only blobs). However if
+ some cached repos are corrupted, their sizes are not taken into account.
+
+
+ """
+
+ size_on_disk: int
+ repos: FrozenSet[CachedRepoInfo]
+ warnings: List[CorruptedCacheException]
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Sum of all valid repo sizes in the cache-system as a human-readable
+ string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+ def delete_revisions(self, *revisions: str) -> DeleteCacheStrategy:
+ """Prepare the strategy to delete one or more revisions cached locally.
+
+ Input revisions can be any revision hash. If a revision hash is not found in the
+ local cache, a warning is thrown but no error is raised. Revisions can be from
+ different cached repos since hashes are unique across repos,
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import scan_cache_dir
+ >>> cache_info = scan_cache_dir()
+ >>> delete_strategy = cache_info.delete_revisions(
+ ... "81fd1d6e7847c99f5862c9fb81387956d99ec7aa"
+ ... )
+ >>> print(f"Will free {delete_strategy.expected_freed_size_str}.")
+ Will free 7.9K.
+ >>> delete_strategy.execute()
+ Cache deletion done. Saved 7.9K.
+ ```
+
+ ```py
+ >>> from huggingface_hub import scan_cache_dir
+ >>> scan_cache_dir().delete_revisions(
+ ... "81fd1d6e7847c99f5862c9fb81387956d99ec7aa",
+ ... "e2983b237dccf3ab4937c97fa717319a9ca1a96d",
+ ... "6c0e6080953db56375760c0471a8c5f2929baf11",
+ ... ).execute()
+ Cache deletion done. Saved 8.6G.
+ ```
+
+
+
+ `delete_revisions` returns a [`~utils.DeleteCacheStrategy`] object that needs to
+ be executed. The [`~utils.DeleteCacheStrategy`] is not meant to be modified but
+ allows having a dry run before actually executing the deletion.
+
+
+ """
+ hashes_to_delete: Set[str] = set(revisions)
+
+ repos_with_revisions: Dict[CachedRepoInfo, Set[CachedRevisionInfo]] = defaultdict(set)
+
+ for repo in self.repos:
+ for revision in repo.revisions:
+ if revision.commit_hash in hashes_to_delete:
+ repos_with_revisions[repo].add(revision)
+ hashes_to_delete.remove(revision.commit_hash)
+
+ if len(hashes_to_delete) > 0:
+ logger.warning(f"Revision(s) not found - cannot delete them: {', '.join(hashes_to_delete)}")
+
+ delete_strategy_blobs: Set[Path] = set()
+ delete_strategy_refs: Set[Path] = set()
+ delete_strategy_repos: Set[Path] = set()
+ delete_strategy_snapshots: Set[Path] = set()
+ delete_strategy_expected_freed_size = 0
+
+ for affected_repo, revisions_to_delete in repos_with_revisions.items():
+ other_revisions = affected_repo.revisions - revisions_to_delete
+
+ # If no other revisions, it means all revisions are deleted
+ # -> delete the entire cached repo
+ if len(other_revisions) == 0:
+ delete_strategy_repos.add(affected_repo.repo_path)
+ delete_strategy_expected_freed_size += affected_repo.size_on_disk
+ continue
+
+ # Some revisions of the repo will be deleted but not all. We need to filter
+ # which blob files will not be linked anymore.
+ for revision_to_delete in revisions_to_delete:
+ # Snapshot dir
+ delete_strategy_snapshots.add(revision_to_delete.snapshot_path)
+
+ # Refs dir
+ for ref in revision_to_delete.refs:
+ delete_strategy_refs.add(affected_repo.repo_path / "refs" / ref)
+
+ # Blobs dir
+ for file in revision_to_delete.files:
+ if file.blob_path not in delete_strategy_blobs:
+ is_file_alone = True
+ for revision in other_revisions:
+ for rev_file in revision.files:
+ if file.blob_path == rev_file.blob_path:
+ is_file_alone = False
+ break
+ if not is_file_alone:
+ break
+
+ # Blob file not referenced by remaining revisions -> delete
+ if is_file_alone:
+ delete_strategy_blobs.add(file.blob_path)
+ delete_strategy_expected_freed_size += file.size_on_disk
+
+ # Return the strategy instead of executing it.
+ return DeleteCacheStrategy(
+ blobs=frozenset(delete_strategy_blobs),
+ refs=frozenset(delete_strategy_refs),
+ repos=frozenset(delete_strategy_repos),
+ snapshots=frozenset(delete_strategy_snapshots),
+ expected_freed_size=delete_strategy_expected_freed_size,
+ )
+
+ def export_as_table(self, *, verbosity: int = 0) -> str:
+ """Generate a table from the [`HFCacheInfo`] object.
+
+ Pass `verbosity=0` to get a table with a single row per repo, with columns
+ "repo_id", "repo_type", "size_on_disk", "nb_files", "last_accessed", "last_modified", "refs", "local_path".
+
+ Pass `verbosity=1` to get a table with a row per repo and revision (thus multiple rows can appear for a single repo), with columns
+ "repo_id", "repo_type", "revision", "size_on_disk", "nb_files", "last_modified", "refs", "local_path".
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import scan_cache_dir
+
+ >>> hf_cache_info = scan_cache_dir()
+ HFCacheInfo(...)
+
+ >>> print(hf_cache_info.export_as_table())
+ REPO ID REPO TYPE SIZE ON DISK NB FILES LAST_ACCESSED LAST_MODIFIED REFS LOCAL PATH
+ --------------------------------------------------- --------- ------------ -------- ------------- ------------- ---- --------------------------------------------------------------------------------------------------
+ roberta-base model 2.7M 5 1 day ago 1 week ago main ~/.cache/huggingface/hub/models--roberta-base
+ suno/bark model 8.8K 1 1 week ago 1 week ago main ~/.cache/huggingface/hub/models--suno--bark
+ t5-base model 893.8M 4 4 days ago 7 months ago main ~/.cache/huggingface/hub/models--t5-base
+ t5-large model 3.0G 4 5 weeks ago 5 months ago main ~/.cache/huggingface/hub/models--t5-large
+
+ >>> print(hf_cache_info.export_as_table(verbosity=1))
+ REPO ID REPO TYPE REVISION SIZE ON DISK NB FILES LAST_MODIFIED REFS LOCAL PATH
+ --------------------------------------------------- --------- ---------------------------------------- ------------ -------- ------------- ---- -----------------------------------------------------------------------------------------------------------------------------------------------------
+ roberta-base model e2da8e2f811d1448a5b465c236feacd80ffbac7b 2.7M 5 1 week ago main ~/.cache/huggingface/hub/models--roberta-base/snapshots/e2da8e2f811d1448a5b465c236feacd80ffbac7b
+ suno/bark model 70a8a7d34168586dc5d028fa9666aceade177992 8.8K 1 1 week ago main ~/.cache/huggingface/hub/models--suno--bark/snapshots/70a8a7d34168586dc5d028fa9666aceade177992
+ t5-base model a9723ea7f1b39c1eae772870f3b547bf6ef7e6c1 893.8M 4 7 months ago main ~/.cache/huggingface/hub/models--t5-base/snapshots/a9723ea7f1b39c1eae772870f3b547bf6ef7e6c1
+ t5-large model 150ebc2c4b72291e770f58e6057481c8d2ed331a 3.0G 4 5 months ago main ~/.cache/huggingface/hub/models--t5-large/snapshots/150ebc2c4b72291e770f58e6057481c8d2ed331a
+ ```
+
+ Args:
+ verbosity (`int`, *optional*):
+ The verbosity level. Defaults to 0.
+
+ Returns:
+ `str`: The table as a string.
+ """
+ if verbosity == 0:
+ return tabulate(
+ rows=[
+ [
+ repo.repo_id,
+ repo.repo_type,
+ "{:>12}".format(repo.size_on_disk_str),
+ repo.nb_files,
+ repo.last_accessed_str,
+ repo.last_modified_str,
+ ", ".join(sorted(repo.refs)),
+ str(repo.repo_path),
+ ]
+ for repo in sorted(self.repos, key=lambda repo: repo.repo_path)
+ ],
+ headers=[
+ "REPO ID",
+ "REPO TYPE",
+ "SIZE ON DISK",
+ "NB FILES",
+ "LAST_ACCESSED",
+ "LAST_MODIFIED",
+ "REFS",
+ "LOCAL PATH",
+ ],
+ )
+ else:
+ return tabulate(
+ rows=[
+ [
+ repo.repo_id,
+ repo.repo_type,
+ revision.commit_hash,
+ "{:>12}".format(revision.size_on_disk_str),
+ revision.nb_files,
+ revision.last_modified_str,
+ ", ".join(sorted(revision.refs)),
+ str(revision.snapshot_path),
+ ]
+ for repo in sorted(self.repos, key=lambda repo: repo.repo_path)
+ for revision in sorted(repo.revisions, key=lambda revision: revision.commit_hash)
+ ],
+ headers=[
+ "REPO ID",
+ "REPO TYPE",
+ "REVISION",
+ "SIZE ON DISK",
+ "NB FILES",
+ "LAST_MODIFIED",
+ "REFS",
+ "LOCAL PATH",
+ ],
+ )
+
+
+def scan_cache_dir(cache_dir: Optional[Union[str, Path]] = None) -> HFCacheInfo:
+ """Scan the entire HF cache-system and return a [`~HFCacheInfo`] structure.
+
+ Use `scan_cache_dir` in order to programmatically scan your cache-system. The cache
+ will be scanned repo by repo. If a repo is corrupted, a [`~CorruptedCacheException`]
+ will be thrown internally but captured and returned in the [`~HFCacheInfo`]
+ structure. Only valid repos get a proper report.
+
+ ```py
+ >>> from huggingface_hub import scan_cache_dir
+
+ >>> hf_cache_info = scan_cache_dir()
+ HFCacheInfo(
+ size_on_disk=3398085269,
+ repos=frozenset({
+ CachedRepoInfo(
+ repo_id='t5-small',
+ repo_type='model',
+ repo_path=PosixPath(...),
+ size_on_disk=970726914,
+ nb_files=11,
+ revisions=frozenset({
+ CachedRevisionInfo(
+ commit_hash='d78aea13fa7ecd06c29e3e46195d6341255065d5',
+ size_on_disk=970726339,
+ snapshot_path=PosixPath(...),
+ files=frozenset({
+ CachedFileInfo(
+ file_name='config.json',
+ size_on_disk=1197
+ file_path=PosixPath(...),
+ blob_path=PosixPath(...),
+ ),
+ CachedFileInfo(...),
+ ...
+ }),
+ ),
+ CachedRevisionInfo(...),
+ ...
+ }),
+ ),
+ CachedRepoInfo(...),
+ ...
+ }),
+ warnings=[
+ CorruptedCacheException("Snapshots dir doesn't exist in cached repo: ..."),
+ CorruptedCacheException(...),
+ ...
+ ],
+ )
+ ```
+
+ You can also print a detailed report directly from the `huggingface-cli` using:
+ ```text
+ > huggingface-cli scan-cache
+ REPO ID REPO TYPE SIZE ON DISK NB FILES REFS LOCAL PATH
+ --------------------------- --------- ------------ -------- ------------------- -------------------------------------------------------------------------
+ glue dataset 116.3K 15 1.17.0, main, 2.4.0 /Users/lucain/.cache/huggingface/hub/datasets--glue
+ google/fleurs dataset 64.9M 6 main, refs/pr/1 /Users/lucain/.cache/huggingface/hub/datasets--google--fleurs
+ Jean-Baptiste/camembert-ner model 441.0M 7 main /Users/lucain/.cache/huggingface/hub/models--Jean-Baptiste--camembert-ner
+ bert-base-cased model 1.9G 13 main /Users/lucain/.cache/huggingface/hub/models--bert-base-cased
+ t5-base model 10.1K 3 main /Users/lucain/.cache/huggingface/hub/models--t5-base
+ t5-small model 970.7M 11 refs/pr/1, main /Users/lucain/.cache/huggingface/hub/models--t5-small
+
+ Done in 0.0s. Scanned 6 repo(s) for a total of 3.4G.
+ Got 1 warning(s) while scanning. Use -vvv to print details.
+ ```
+
+ Args:
+ cache_dir (`str` or `Path`, `optional`):
+ Cache directory to cache. Defaults to the default HF cache directory.
+
+
+
+ Raises:
+
+ `CacheNotFound`
+ If the cache directory does not exist.
+
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the cache directory is a file, instead of a directory.
+
+
+
+ Returns: a [`~HFCacheInfo`] object.
+ """
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+
+ cache_dir = Path(cache_dir).expanduser().resolve()
+ if not cache_dir.exists():
+ raise CacheNotFound(
+ f"Cache directory not found: {cache_dir}. Please use `cache_dir` argument or set `HF_HUB_CACHE` environment variable.",
+ cache_dir=cache_dir,
+ )
+
+ if cache_dir.is_file():
+ raise ValueError(
+ f"Scan cache expects a directory but found a file: {cache_dir}. Please use `cache_dir` argument or set `HF_HUB_CACHE` environment variable."
+ )
+
+ repos: Set[CachedRepoInfo] = set()
+ warnings: List[CorruptedCacheException] = []
+ for repo_path in cache_dir.iterdir():
+ if repo_path.name == ".locks": # skip './.locks/' folder
+ continue
+ try:
+ repos.add(_scan_cached_repo(repo_path))
+ except CorruptedCacheException as e:
+ warnings.append(e)
+
+ return HFCacheInfo(
+ repos=frozenset(repos),
+ size_on_disk=sum(repo.size_on_disk for repo in repos),
+ warnings=warnings,
+ )
+
+
+def _scan_cached_repo(repo_path: Path) -> CachedRepoInfo:
+ """Scan a single cache repo and return information about it.
+
+ Any unexpected behavior will raise a [`~CorruptedCacheException`].
+ """
+ if not repo_path.is_dir():
+ raise CorruptedCacheException(f"Repo path is not a directory: {repo_path}")
+
+ if "--" not in repo_path.name:
+ raise CorruptedCacheException(f"Repo path is not a valid HuggingFace cache directory: {repo_path}")
+
+ repo_type, repo_id = repo_path.name.split("--", maxsplit=1)
+ repo_type = repo_type[:-1] # "models" -> "model"
+ repo_id = repo_id.replace("--", "/") # google/fleurs -> "google/fleurs"
+
+ if repo_type not in {"dataset", "model", "space"}:
+ raise CorruptedCacheException(
+ f"Repo type must be `dataset`, `model` or `space`, found `{repo_type}` ({repo_path})."
+ )
+
+ blob_stats: Dict[Path, os.stat_result] = {} # Key is blob_path, value is blob stats
+
+ snapshots_path = repo_path / "snapshots"
+ refs_path = repo_path / "refs"
+
+ if not snapshots_path.exists() or not snapshots_path.is_dir():
+ raise CorruptedCacheException(f"Snapshots dir doesn't exist in cached repo: {snapshots_path}")
+
+ # Scan over `refs` directory
+
+ # key is revision hash, value is set of refs
+ refs_by_hash: Dict[str, Set[str]] = defaultdict(set)
+ if refs_path.exists():
+ # Example of `refs` directory
+ # ── refs
+ # ├── main
+ # └── refs
+ # └── pr
+ # └── 1
+ if refs_path.is_file():
+ raise CorruptedCacheException(f"Refs directory cannot be a file: {refs_path}")
+
+ for ref_path in refs_path.glob("**/*"):
+ # glob("**/*") iterates over all files and directories -> skip directories
+ if ref_path.is_dir() or ref_path.name in FILES_TO_IGNORE:
+ continue
+
+ ref_name = str(ref_path.relative_to(refs_path))
+ with ref_path.open() as f:
+ commit_hash = f.read()
+
+ refs_by_hash[commit_hash].add(ref_name)
+
+ # Scan snapshots directory
+ cached_revisions: Set[CachedRevisionInfo] = set()
+ for revision_path in snapshots_path.iterdir():
+ # Ignore OS-created helper files
+ if revision_path.name in FILES_TO_IGNORE:
+ continue
+ if revision_path.is_file():
+ raise CorruptedCacheException(f"Snapshots folder corrupted. Found a file: {revision_path}")
+
+ cached_files = set()
+ for file_path in revision_path.glob("**/*"):
+ # glob("**/*") iterates over all files and directories -> skip directories
+ if file_path.is_dir():
+ continue
+
+ blob_path = Path(file_path).resolve()
+ if not blob_path.exists():
+ raise CorruptedCacheException(f"Blob missing (broken symlink): {blob_path}")
+
+ if blob_path not in blob_stats:
+ blob_stats[blob_path] = blob_path.stat()
+
+ cached_files.add(
+ CachedFileInfo(
+ file_name=file_path.name,
+ file_path=file_path,
+ size_on_disk=blob_stats[blob_path].st_size,
+ blob_path=blob_path,
+ blob_last_accessed=blob_stats[blob_path].st_atime,
+ blob_last_modified=blob_stats[blob_path].st_mtime,
+ )
+ )
+
+ # Last modified is either the last modified blob file or the revision folder
+ # itself if it is empty
+ if len(cached_files) > 0:
+ revision_last_modified = max(blob_stats[file.blob_path].st_mtime for file in cached_files)
+ else:
+ revision_last_modified = revision_path.stat().st_mtime
+
+ cached_revisions.add(
+ CachedRevisionInfo(
+ commit_hash=revision_path.name,
+ files=frozenset(cached_files),
+ refs=frozenset(refs_by_hash.pop(revision_path.name, set())),
+ size_on_disk=sum(
+ blob_stats[blob_path].st_size for blob_path in set(file.blob_path for file in cached_files)
+ ),
+ snapshot_path=revision_path,
+ last_modified=revision_last_modified,
+ )
+ )
+
+ # Check that all refs referred to an existing revision
+ if len(refs_by_hash) > 0:
+ raise CorruptedCacheException(
+ f"Reference(s) refer to missing commit hashes: {dict(refs_by_hash)} ({repo_path})."
+ )
+
+ # Last modified is either the last modified blob file or the repo folder itself if
+ # no blob files has been found. Same for last accessed.
+ if len(blob_stats) > 0:
+ repo_last_accessed = max(stat.st_atime for stat in blob_stats.values())
+ repo_last_modified = max(stat.st_mtime for stat in blob_stats.values())
+ else:
+ repo_stats = repo_path.stat()
+ repo_last_accessed = repo_stats.st_atime
+ repo_last_modified = repo_stats.st_mtime
+
+ # Build and return frozen structure
+ return CachedRepoInfo(
+ nb_files=len(blob_stats),
+ repo_id=repo_id,
+ repo_path=repo_path,
+ repo_type=repo_type, # type: ignore
+ revisions=frozenset(cached_revisions),
+ size_on_disk=sum(stat.st_size for stat in blob_stats.values()),
+ last_accessed=repo_last_accessed,
+ last_modified=repo_last_modified,
+ )
+
+
+def _format_size(num: int) -> str:
+ """Format size in bytes into a human-readable string.
+
+ Taken from https://stackoverflow.com/a/1094933
+ """
+ num_f = float(num)
+ for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
+ if abs(num_f) < 1000.0:
+ return f"{num_f:3.1f}{unit}"
+ num_f /= 1000.0
+ return f"{num_f:.1f}Y"
+
+
+_TIMESINCE_CHUNKS = (
+ # Label, divider, max value
+ ("second", 1, 60),
+ ("minute", 60, 60),
+ ("hour", 60 * 60, 24),
+ ("day", 60 * 60 * 24, 6),
+ ("week", 60 * 60 * 24 * 7, 6),
+ ("month", 60 * 60 * 24 * 30, 11),
+ ("year", 60 * 60 * 24 * 365, None),
+)
+
+
+def _format_timesince(ts: float) -> str:
+ """Format timestamp in seconds into a human-readable string, relative to now.
+
+ Vaguely inspired by Django's `timesince` formatter.
+ """
+ delta = time.time() - ts
+ if delta < 20:
+ return "a few seconds ago"
+ for label, divider, max_value in _TIMESINCE_CHUNKS: # noqa: B007
+ value = round(delta / divider)
+ if max_value is not None and value <= max_value:
+ break
+ return f"{value} {label}{'s' if value > 1 else ''} ago"
+
+
+def _try_delete_path(path: Path, path_type: str) -> None:
+ """Try to delete a local file or folder.
+
+ If the path does not exists, error is logged as a warning and then ignored.
+
+ Args:
+ path (`Path`)
+ Path to delete. Can be a file or a folder.
+ path_type (`str`)
+ What path are we deleting ? Only for logging purposes. Example: "snapshot".
+ """
+ logger.info(f"Delete {path_type}: {path}")
+ try:
+ if path.is_file():
+ os.remove(path)
+ else:
+ shutil.rmtree(path)
+ except FileNotFoundError:
+ logger.warning(f"Couldn't delete {path_type}: file not found ({path})", exc_info=True)
+ except PermissionError:
+ logger.warning(f"Couldn't delete {path_type}: permission denied ({path})", exc_info=True)
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_chunk_utils.py b/env/Lib/site-packages/huggingface_hub/utils/_chunk_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0af032ae6a68f03676ad7fdb8e483248d9853f8
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_chunk_utils.py
@@ -0,0 +1,65 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a utility to iterate by chunks over an iterator."""
+
+import itertools
+from typing import Iterable, TypeVar
+
+
+T = TypeVar("T")
+
+
+def chunk_iterable(iterable: Iterable[T], chunk_size: int) -> Iterable[Iterable[T]]:
+ """Iterates over an iterator chunk by chunk.
+
+ Taken from https://stackoverflow.com/a/8998040.
+ See also https://github.com/huggingface/huggingface_hub/pull/920#discussion_r938793088.
+
+ Args:
+ iterable (`Iterable`):
+ The iterable on which we want to iterate.
+ chunk_size (`int`):
+ Size of the chunks. Must be a strictly positive integer (e.g. >0).
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub.utils import chunk_iterable
+
+ >>> for items in chunk_iterable(range(17), chunk_size=8):
+ ... print(items)
+ # [0, 1, 2, 3, 4, 5, 6, 7]
+ # [8, 9, 10, 11, 12, 13, 14, 15]
+ # [16] # smaller last chunk
+ ```
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `chunk_size` <= 0.
+
+
+ The last chunk can be smaller than `chunk_size`.
+
+ """
+ if not isinstance(chunk_size, int) or chunk_size <= 0:
+ raise ValueError("`chunk_size` must be a strictly positive integer (>0).")
+
+ iterator = iter(iterable)
+ while True:
+ try:
+ next_item = next(iterator)
+ except StopIteration:
+ return
+ yield itertools.chain((next_item,), itertools.islice(iterator, chunk_size - 1))
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_datetime.py b/env/Lib/site-packages/huggingface_hub/utils/_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a7f44285d1c826006c97176ca66c3e9c33f61c0
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_datetime.py
@@ -0,0 +1,67 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle datetimes in Huggingface Hub."""
+
+from datetime import datetime, timezone
+
+
+def parse_datetime(date_string: str) -> datetime:
+ """
+ Parses a date_string returned from the server to a datetime object.
+
+ This parser is a weak-parser is the sense that it handles only a single format of
+ date_string. It is expected that the server format will never change. The
+ implementation depends only on the standard lib to avoid an external dependency
+ (python-dateutil). See full discussion about this decision on PR:
+ https://github.com/huggingface/huggingface_hub/pull/999.
+
+ Example:
+ ```py
+ > parse_datetime('2022-08-19T07:19:38.123Z')
+ datetime.datetime(2022, 8, 19, 7, 19, 38, 123000, tzinfo=timezone.utc)
+ ```
+
+ Args:
+ date_string (`str`):
+ A string representing a datetime returned by the Hub server.
+ String is expected to follow '%Y-%m-%dT%H:%M:%S.%fZ' pattern.
+
+ Returns:
+ A python datetime object.
+
+ Raises:
+ :class:`ValueError`:
+ If `date_string` cannot be parsed.
+ """
+ try:
+ # Normalize the string to always have 6 digits of fractional seconds
+ if date_string.endswith("Z"):
+ # Case 1: No decimal point (e.g., "2024-11-16T00:27:02Z")
+ if "." not in date_string:
+ # No fractional seconds - insert .000000
+ date_string = date_string[:-1] + ".000000Z"
+ # Case 2: Has decimal point (e.g., "2022-08-19T07:19:38.123456789Z")
+ else:
+ # Get the fractional and base parts
+ base, fraction = date_string[:-1].split(".")
+ # fraction[:6] takes first 6 digits and :0<6 pads with zeros if less than 6 digits
+ date_string = f"{base}.{fraction[:6]:0<6}Z"
+
+ return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
+ except ValueError as e:
+ raise ValueError(
+ f"Cannot parse '{date_string}' as a datetime. Date string is expected to"
+ " follow '%Y-%m-%dT%H:%M:%S.%fZ' pattern."
+ ) from e
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_deprecation.py b/env/Lib/site-packages/huggingface_hub/utils/_deprecation.py
new file mode 100644
index 0000000000000000000000000000000000000000..4cb8d6e418c76accd1ecd61158b4bdd265e12f71
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_deprecation.py
@@ -0,0 +1,136 @@
+import warnings
+from functools import wraps
+from inspect import Parameter, signature
+from typing import Iterable, Optional
+
+
+def _deprecate_positional_args(*, version: str):
+ """Decorator for methods that issues warnings for positional arguments.
+ Using the keyword-only argument syntax in pep 3102, arguments after the
+ * will issue a warning when passed as a positional argument.
+
+ Args:
+ version (`str`):
+ The version when positional arguments will result in error.
+ """
+
+ def _inner_deprecate_positional_args(f):
+ sig = signature(f)
+ kwonly_args = []
+ all_args = []
+ for name, param in sig.parameters.items():
+ if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
+ all_args.append(name)
+ elif param.kind == Parameter.KEYWORD_ONLY:
+ kwonly_args.append(name)
+
+ @wraps(f)
+ def inner_f(*args, **kwargs):
+ extra_args = len(args) - len(all_args)
+ if extra_args <= 0:
+ return f(*args, **kwargs)
+ # extra_args > 0
+ args_msg = [
+ f"{name}='{arg}'" if isinstance(arg, str) else f"{name}={arg}"
+ for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:])
+ ]
+ args_msg = ", ".join(args_msg)
+ warnings.warn(
+ f"Deprecated positional argument(s) used in '{f.__name__}': pass"
+ f" {args_msg} as keyword args. From version {version} passing these"
+ " as positional arguments will result in an error,",
+ FutureWarning,
+ )
+ kwargs.update(zip(sig.parameters, args))
+ return f(**kwargs)
+
+ return inner_f
+
+ return _inner_deprecate_positional_args
+
+
+def _deprecate_arguments(
+ *,
+ version: str,
+ deprecated_args: Iterable[str],
+ custom_message: Optional[str] = None,
+):
+ """Decorator to issue warnings when using deprecated arguments.
+
+ TODO: could be useful to be able to set a custom error message.
+
+ Args:
+ version (`str`):
+ The version when deprecated arguments will result in error.
+ deprecated_args (`List[str]`):
+ List of the arguments to be deprecated.
+ custom_message (`str`, *optional*):
+ Warning message that is raised. If not passed, a default warning message
+ will be created.
+ """
+
+ def _inner_deprecate_positional_args(f):
+ sig = signature(f)
+
+ @wraps(f)
+ def inner_f(*args, **kwargs):
+ # Check for used deprecated arguments
+ used_deprecated_args = []
+ for _, parameter in zip(args, sig.parameters.values()):
+ if parameter.name in deprecated_args:
+ used_deprecated_args.append(parameter.name)
+ for kwarg_name, kwarg_value in kwargs.items():
+ if (
+ # If argument is deprecated but still used
+ kwarg_name in deprecated_args
+ # And then the value is not the default value
+ and kwarg_value != sig.parameters[kwarg_name].default
+ ):
+ used_deprecated_args.append(kwarg_name)
+
+ # Warn and proceed
+ if len(used_deprecated_args) > 0:
+ message = (
+ f"Deprecated argument(s) used in '{f.__name__}':"
+ f" {', '.join(used_deprecated_args)}. Will not be supported from"
+ f" version '{version}'."
+ )
+ if custom_message is not None:
+ message += "\n\n" + custom_message
+ warnings.warn(message, FutureWarning)
+ return f(*args, **kwargs)
+
+ return inner_f
+
+ return _inner_deprecate_positional_args
+
+
+def _deprecate_method(*, version: str, message: Optional[str] = None):
+ """Decorator to issue warnings when using a deprecated method.
+
+ Args:
+ version (`str`):
+ The version when deprecated arguments will result in error.
+ message (`str`, *optional*):
+ Warning message that is raised. If not passed, a default warning message
+ will be created.
+ """
+
+ def _inner_deprecate_method(f):
+ name = f.__name__
+ if name == "__init__":
+ name = f.__qualname__.split(".")[0] # class name instead of method name
+
+ @wraps(f)
+ def inner_f(*args, **kwargs):
+ warning_message = (
+ f"'{name}' (from '{f.__module__}') is deprecated and will be removed from version '{version}'."
+ )
+ if message is not None:
+ warning_message += " " + message
+ warnings.warn(warning_message, FutureWarning)
+ return f(*args, **kwargs)
+
+ return inner_f
+
+ return _inner_deprecate_method
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_experimental.py b/env/Lib/site-packages/huggingface_hub/utils/_experimental.py
new file mode 100644
index 0000000000000000000000000000000000000000..34141eba09123c06fbca55c929a19a0264e5788e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_experimental.py
@@ -0,0 +1,66 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to flag a feature as "experimental" in Huggingface Hub."""
+
+import warnings
+from functools import wraps
+from typing import Callable
+
+from .. import constants
+
+
+def experimental(fn: Callable) -> Callable:
+ """Decorator to flag a feature as experimental.
+
+ An experimental feature trigger a warning when used as it might be subject to breaking changes in the future.
+ Warnings can be disabled by setting the environment variable `HF_EXPERIMENTAL_WARNING` to `0`.
+
+ Args:
+ fn (`Callable`):
+ The function to flag as experimental.
+
+ Returns:
+ `Callable`: The decorated function.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub.utils import experimental
+
+ >>> @experimental
+ ... def my_function():
+ ... print("Hello world!")
+
+ >>> my_function()
+ UserWarning: 'my_function' is experimental and might be subject to breaking changes in the future. You can disable
+ this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment variable.
+ Hello world!
+ ```
+ """
+ # For classes, put the "experimental" around the "__new__" method => __new__ will be removed in warning message
+ name = fn.__qualname__[: -len(".__new__")] if fn.__qualname__.endswith(".__new__") else fn.__qualname__
+
+ @wraps(fn)
+ def _inner_fn(*args, **kwargs):
+ if not constants.HF_HUB_DISABLE_EXPERIMENTAL_WARNING:
+ warnings.warn(
+ f"'{name}' is experimental and might be subject to breaking changes in the future."
+ " You can disable this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment"
+ " variable.",
+ UserWarning,
+ )
+ return fn(*args, **kwargs)
+
+ return _inner_fn
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_fixes.py b/env/Lib/site-packages/huggingface_hub/utils/_fixes.py
new file mode 100644
index 0000000000000000000000000000000000000000..560003b6222058b03791491b1ce70ea9d7a94404
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_fixes.py
@@ -0,0 +1,133 @@
+# JSONDecodeError was introduced in requests=2.27 released in 2022.
+# This allows us to support older requests for users
+# More information: https://github.com/psf/requests/pull/5856
+try:
+ from requests import JSONDecodeError # type: ignore # noqa: F401
+except ImportError:
+ try:
+ from simplejson import JSONDecodeError # type: ignore # noqa: F401
+ except ImportError:
+ from json import JSONDecodeError # type: ignore # noqa: F401
+import contextlib
+import os
+import shutil
+import stat
+import tempfile
+import time
+from functools import partial
+from pathlib import Path
+from typing import Callable, Generator, Optional, Union
+
+import yaml
+from filelock import BaseFileLock, FileLock, SoftFileLock, Timeout
+
+from .. import constants
+from . import logging
+
+
+logger = logging.get_logger(__name__)
+
+# Wrap `yaml.dump` to set `allow_unicode=True` by default.
+#
+# Example:
+# ```py
+# >>> yaml.dump({"emoji": "👀", "some unicode": "日本か"})
+# 'emoji: "\\U0001F440"\nsome unicode: "\\u65E5\\u672C\\u304B"\n'
+#
+# >>> yaml_dump({"emoji": "👀", "some unicode": "日本か"})
+# 'emoji: "👀"\nsome unicode: "日本か"\n'
+# ```
+yaml_dump: Callable[..., str] = partial(yaml.dump, stream=None, allow_unicode=True) # type: ignore
+
+
+@contextlib.contextmanager
+def SoftTemporaryDirectory(
+ suffix: Optional[str] = None,
+ prefix: Optional[str] = None,
+ dir: Optional[Union[Path, str]] = None,
+ **kwargs,
+) -> Generator[Path, None, None]:
+ """
+ Context manager to create a temporary directory and safely delete it.
+
+ If tmp directory cannot be deleted normally, we set the WRITE permission and retry.
+ If cleanup still fails, we give up but don't raise an exception. This is equivalent
+ to `tempfile.TemporaryDirectory(..., ignore_cleanup_errors=True)` introduced in
+ Python 3.10.
+
+ See https://www.scivision.dev/python-tempfile-permission-error-windows/.
+ """
+ tmpdir = tempfile.TemporaryDirectory(prefix=prefix, suffix=suffix, dir=dir, **kwargs)
+ yield Path(tmpdir.name).resolve()
+
+ try:
+ # First once with normal cleanup
+ shutil.rmtree(tmpdir.name)
+ except Exception:
+ # If failed, try to set write permission and retry
+ try:
+ shutil.rmtree(tmpdir.name, onerror=_set_write_permission_and_retry)
+ except Exception:
+ pass
+
+ # And finally, cleanup the tmpdir.
+ # If it fails again, give up but do not throw error
+ try:
+ tmpdir.cleanup()
+ except Exception:
+ pass
+
+
+def _set_write_permission_and_retry(func, path, excinfo):
+ os.chmod(path, stat.S_IWRITE)
+ func(path)
+
+
+@contextlib.contextmanager
+def WeakFileLock(
+ lock_file: Union[str, Path], *, timeout: Optional[float] = None
+) -> Generator[BaseFileLock, None, None]:
+ """A filelock with some custom logic.
+
+ This filelock is weaker than the default filelock in that:
+ 1. It won't raise an exception if release fails.
+ 2. It will default to a SoftFileLock if the filesystem does not support flock.
+
+ An INFO log message is emitted every 10 seconds if the lock is not acquired immediately.
+ If a timeout is provided, a `filelock.Timeout` exception is raised if the lock is not acquired within the timeout.
+ """
+ log_interval = constants.FILELOCK_LOG_EVERY_SECONDS
+ lock = FileLock(lock_file, timeout=log_interval)
+ start_time = time.time()
+
+ while True:
+ elapsed_time = time.time() - start_time
+ if timeout is not None and elapsed_time >= timeout:
+ raise Timeout(str(lock_file))
+
+ try:
+ lock.acquire(timeout=min(log_interval, timeout - elapsed_time) if timeout else log_interval)
+ except Timeout:
+ logger.info(
+ f"Still waiting to acquire lock on {lock_file} (elapsed: {time.time() - start_time:.1f} seconds)"
+ )
+ except NotImplementedError as e:
+ if "use SoftFileLock instead" in str(e):
+ logger.warning(
+ "FileSystem does not appear to support flock. Falling back to SoftFileLock for %s", lock_file
+ )
+ lock = SoftFileLock(lock_file, timeout=log_interval)
+ continue
+ else:
+ break
+
+ try:
+ yield lock
+ finally:
+ try:
+ lock.release()
+ except OSError:
+ try:
+ Path(lock_file).unlink()
+ except OSError:
+ pass
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_git_credential.py b/env/Lib/site-packages/huggingface_hub/utils/_git_credential.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8ed77f4e49ca88ff4fa9aba48cbf00195036013
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_git_credential.py
@@ -0,0 +1,121 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to manage Git credentials."""
+
+import re
+import subprocess
+from typing import List, Optional
+
+from ..constants import ENDPOINT
+from ._subprocess import run_interactive_subprocess, run_subprocess
+
+
+GIT_CREDENTIAL_REGEX = re.compile(
+ r"""
+ ^\s* # start of line
+ credential\.helper # credential.helper value
+ \s*=\s* # separator
+ (\w+) # the helper name (group 1)
+ (\s|$) # whitespace or end of line
+ """,
+ flags=re.MULTILINE | re.IGNORECASE | re.VERBOSE,
+)
+
+
+def list_credential_helpers(folder: Optional[str] = None) -> List[str]:
+ """Return the list of git credential helpers configured.
+
+ See https://git-scm.com/docs/gitcredentials.
+
+ Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
+ Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.
+
+ Args:
+ folder (`str`, *optional*):
+ The folder in which to check the configured helpers.
+ """
+ try:
+ output = run_subprocess("git config --list", folder=folder).stdout
+ parsed = _parse_credential_output(output)
+ return parsed
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+
+def set_git_credential(token: str, username: str = "hf_user", folder: Optional[str] = None) -> None:
+ """Save a username/token pair in git credential for HF Hub registry.
+
+ Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
+ Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.
+
+ Args:
+ username (`str`, defaults to `"hf_user"`):
+ A git username. Defaults to `"hf_user"`, the default user used in the Hub.
+ token (`str`, defaults to `"hf_user"`):
+ A git password. In practice, the User Access Token for the Hub.
+ See https://huggingface.co/settings/tokens.
+ folder (`str`, *optional*):
+ The folder in which to check the configured helpers.
+ """
+ with run_interactive_subprocess("git credential approve", folder=folder) as (
+ stdin,
+ _,
+ ):
+ stdin.write(f"url={ENDPOINT}\nusername={username.lower()}\npassword={token}\n\n")
+ stdin.flush()
+
+
+def unset_git_credential(username: str = "hf_user", folder: Optional[str] = None) -> None:
+ """Erase credentials from git credential for HF Hub registry.
+
+ Credentials are erased from the configured helpers (store, cache, macOS
+ keychain,...), if any. If `username` is not provided, any credential configured for
+ HF Hub endpoint is erased.
+ Calls "`git credential erase`" internally. See https://git-scm.com/docs/git-credential.
+
+ Args:
+ username (`str`, defaults to `"hf_user"`):
+ A git username. Defaults to `"hf_user"`, the default user used in the Hub.
+ folder (`str`, *optional*):
+ The folder in which to check the configured helpers.
+ """
+ with run_interactive_subprocess("git credential reject", folder=folder) as (
+ stdin,
+ _,
+ ):
+ standard_input = f"url={ENDPOINT}\n"
+ if username is not None:
+ standard_input += f"username={username.lower()}\n"
+ standard_input += "\n"
+
+ stdin.write(standard_input)
+ stdin.flush()
+
+
+def _parse_credential_output(output: str) -> List[str]:
+ """Parse the output of `git credential fill` to extract the password.
+
+ Args:
+ output (`str`):
+ The output of `git credential fill`.
+ """
+ # NOTE: If user has set an helper for a custom URL, it will not we caught here.
+ # Example: `credential.https://huggingface.co.helper=store`
+ # See: https://github.com/huggingface/huggingface_hub/pull/1138#discussion_r1013324508
+ return sorted( # Sort for nice printing
+ set( # Might have some duplicates
+ match[0] for match in GIT_CREDENTIAL_REGEX.findall(output)
+ )
+ )
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_headers.py b/env/Lib/site-packages/huggingface_hub/utils/_headers.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0e1ddd87a51e170e7d0518c55cfaa3ab4f0ebb7
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_headers.py
@@ -0,0 +1,228 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle headers to send in calls to Huggingface Hub."""
+
+from typing import Dict, Optional, Union
+
+from huggingface_hub.errors import LocalTokenNotFoundError
+
+from .. import constants
+from ._auth import get_token
+from ._deprecation import _deprecate_arguments
+from ._runtime import (
+ get_fastai_version,
+ get_fastcore_version,
+ get_hf_hub_version,
+ get_python_version,
+ get_tf_version,
+ get_torch_version,
+ is_fastai_available,
+ is_fastcore_available,
+ is_tf_available,
+ is_torch_available,
+)
+from ._validators import validate_hf_hub_args
+
+
+@_deprecate_arguments(
+ version="1.0",
+ deprecated_args="is_write_action",
+ custom_message="This argument is ignored and we let the server handle the permission error instead (if any).",
+)
+@validate_hf_hub_args
+def build_hf_headers(
+ *,
+ token: Optional[Union[bool, str]] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ headers: Optional[Dict[str, str]] = None,
+ is_write_action: bool = False,
+) -> Dict[str, str]:
+ """
+ Build headers dictionary to send in a HF Hub call.
+
+ By default, authorization token is always provided either from argument (explicit
+ use) or retrieved from the cache (implicit use). To explicitly avoid sending the
+ token to the Hub, set `token=False` or set the `HF_HUB_DISABLE_IMPLICIT_TOKEN`
+ environment variable.
+
+ In case of an API call that requires write access, an error is thrown if token is
+ `None` or token is an organization token (starting with `"api_org***"`).
+
+ In addition to the auth header, a user-agent is added to provide information about
+ the installed packages (versions of python, huggingface_hub, torch, tensorflow,
+ fastai and fastcore).
+
+ Args:
+ token (`str`, `bool`, *optional*):
+ The token to be sent in authorization header for the Hub call:
+ - if a string, it is used as the Hugging Face token
+ - if `True`, the token is read from the machine (cache or env variable)
+ - if `False`, authorization header is not set
+ - if `None`, the token is read from the machine only except if
+ `HF_HUB_DISABLE_IMPLICIT_TOKEN` env variable is set.
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request. Will be added to
+ the user-agent header.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request. Will be added
+ to the user-agent header.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string. It will
+ be completed with information about the installed packages.
+ headers (`dict`, *optional*):
+ Additional headers to include in the request. Those headers take precedence
+ over the ones generated by this function.
+ is_write_action (`bool`):
+ Ignored and deprecated argument.
+
+ Returns:
+ A `Dict` of headers to pass in your API call.
+
+ Example:
+ ```py
+ >>> build_hf_headers(token="hf_***") # explicit token
+ {"authorization": "Bearer hf_***", "user-agent": ""}
+
+ >>> build_hf_headers(token=True) # explicitly use cached token
+ {"authorization": "Bearer hf_***",...}
+
+ >>> build_hf_headers(token=False) # explicitly don't use cached token
+ {"user-agent": ...}
+
+ >>> build_hf_headers() # implicit use of the cached token
+ {"authorization": "Bearer hf_***",...}
+
+ # HF_HUB_DISABLE_IMPLICIT_TOKEN=True # to set as env variable
+ >>> build_hf_headers() # token is not sent
+ {"user-agent": ...}
+
+ >>> build_hf_headers(library_name="transformers", library_version="1.2.3")
+ {"authorization": ..., "user-agent": "transformers/1.2.3; hf_hub/0.10.2; python/3.10.4; tensorflow/1.55"}
+ ```
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If organization token is passed and "write" access is required.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If "write" access is required but token is not passed and not saved locally.
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `token=True` but token is not saved locally.
+ """
+ # Get auth token to send
+ token_to_send = get_token_to_send(token)
+
+ # Combine headers
+ hf_headers = {
+ "user-agent": _http_user_agent(
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ )
+ }
+ if token_to_send is not None:
+ hf_headers["authorization"] = f"Bearer {token_to_send}"
+ if headers is not None:
+ hf_headers.update(headers)
+ return hf_headers
+
+
+def get_token_to_send(token: Optional[Union[bool, str]]) -> Optional[str]:
+ """Select the token to send from either `token` or the cache."""
+ # Case token is explicitly provided
+ if isinstance(token, str):
+ return token
+
+ # Case token is explicitly forbidden
+ if token is False:
+ return None
+
+ # Token is not provided: we get it from local cache
+ cached_token = get_token()
+
+ # Case token is explicitly required
+ if token is True:
+ if cached_token is None:
+ raise LocalTokenNotFoundError(
+ "Token is required (`token=True`), but no token found. You"
+ " need to provide a token or be logged in to Hugging Face with"
+ " `huggingface-cli login` or `huggingface_hub.login`. See"
+ " https://huggingface.co/settings/tokens."
+ )
+ return cached_token
+
+ # Case implicit use of the token is forbidden by env variable
+ if constants.HF_HUB_DISABLE_IMPLICIT_TOKEN:
+ return None
+
+ # Otherwise: we use the cached token as the user has not explicitly forbidden it
+ return cached_token
+
+
+def _http_user_agent(
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+) -> str:
+ """Format a user-agent string containing information about the installed packages.
+
+ Args:
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string.
+
+ Returns:
+ The formatted user-agent string.
+ """
+ if library_name is not None:
+ ua = f"{library_name}/{library_version}"
+ else:
+ ua = "unknown/None"
+ ua += f"; hf_hub/{get_hf_hub_version()}"
+ ua += f"; python/{get_python_version()}"
+
+ if not constants.HF_HUB_DISABLE_TELEMETRY:
+ if is_torch_available():
+ ua += f"; torch/{get_torch_version()}"
+ if is_tf_available():
+ ua += f"; tensorflow/{get_tf_version()}"
+ if is_fastai_available():
+ ua += f"; fastai/{get_fastai_version()}"
+ if is_fastcore_available():
+ ua += f"; fastcore/{get_fastcore_version()}"
+
+ if isinstance(user_agent, dict):
+ ua += "; " + "; ".join(f"{k}/{v}" for k, v in user_agent.items())
+ elif isinstance(user_agent, str):
+ ua += "; " + user_agent
+
+ # Retrieve user-agent origin headers from environment variable
+ origin = constants.HF_HUB_USER_AGENT_ORIGIN
+ if origin is not None:
+ ua += "; origin/" + origin
+
+ return _deduplicate_user_agent(ua)
+
+
+def _deduplicate_user_agent(user_agent: str) -> str:
+ """Deduplicate redundant information in the generated user-agent."""
+ # Split around ";" > Strip whitespaces > Store as dict keys (ensure unicity) > format back as string
+ # Order is implicitly preserved by dictionary structure (see https://stackoverflow.com/a/53657523).
+ return "; ".join({key.strip(): None for key in user_agent.split(";")}.keys())
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_hf_folder.py b/env/Lib/site-packages/huggingface_hub/utils/_hf_folder.py
new file mode 100644
index 0000000000000000000000000000000000000000..6418bf2fd2c59b4bcf301c1dd82bc468f2f42ddf
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_hf_folder.py
@@ -0,0 +1,68 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contain helper class to retrieve/store token from/to local cache."""
+
+from pathlib import Path
+from typing import Optional
+
+from .. import constants
+from ._auth import get_token
+
+
+class HfFolder:
+ # TODO: deprecate when adapted in transformers/datasets/gradio
+ # @_deprecate_method(version="1.0", message="Use `huggingface_hub.login` instead.")
+ @classmethod
+ def save_token(cls, token: str) -> None:
+ """
+ Save token, creating folder as needed.
+
+ Token is saved in the huggingface home folder. You can configure it by setting
+ the `HF_HOME` environment variable.
+
+ Args:
+ token (`str`):
+ The token to save to the [`HfFolder`]
+ """
+ path_token = Path(constants.HF_TOKEN_PATH)
+ path_token.parent.mkdir(parents=True, exist_ok=True)
+ path_token.write_text(token)
+
+ # TODO: deprecate when adapted in transformers/datasets/gradio
+ # @_deprecate_method(version="1.0", message="Use `huggingface_hub.get_token` instead.")
+ @classmethod
+ def get_token(cls) -> Optional[str]:
+ """
+ Get token or None if not existent.
+
+ This method is deprecated in favor of [`huggingface_hub.get_token`] but is kept for backward compatibility.
+ Its behavior is the same as [`huggingface_hub.get_token`].
+
+ Returns:
+ `str` or `None`: The token, `None` if it doesn't exist.
+ """
+ return get_token()
+
+ # TODO: deprecate when adapted in transformers/datasets/gradio
+ # @_deprecate_method(version="1.0", message="Use `huggingface_hub.logout` instead.")
+ @classmethod
+ def delete_token(cls) -> None:
+ """
+ Deletes the token from storage. Does not fail if token does not exist.
+ """
+ try:
+ Path(constants.HF_TOKEN_PATH).unlink()
+ except FileNotFoundError:
+ pass
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_http.py b/env/Lib/site-packages/huggingface_hub/utils/_http.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ed0789dc304a67c999398d13ff4389d00f7dc9d
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_http.py
@@ -0,0 +1,634 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle HTTP requests in Huggingface Hub."""
+
+import io
+import os
+import re
+import threading
+import time
+import uuid
+from functools import lru_cache
+from http import HTTPStatus
+from shlex import quote
+from typing import Any, Callable, List, Optional, Tuple, Type, Union
+
+import requests
+from requests import HTTPError, Response
+from requests.adapters import HTTPAdapter
+from requests.models import PreparedRequest
+
+from huggingface_hub.errors import OfflineModeIsEnabled
+
+from .. import constants
+from ..errors import (
+ BadRequestError,
+ DisabledRepoError,
+ EntryNotFoundError,
+ GatedRepoError,
+ HfHubHTTPError,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+)
+from . import logging
+from ._fixes import JSONDecodeError
+from ._lfs import SliceFileObj
+from ._typing import HTTP_METHOD_T
+
+
+logger = logging.get_logger(__name__)
+
+# Both headers are used by the Hub to debug failed requests.
+# `X_AMZN_TRACE_ID` is better as it also works to debug on Cloudfront and ALB.
+# If `X_AMZN_TRACE_ID` is set, the Hub will use it as well.
+X_AMZN_TRACE_ID = "X-Amzn-Trace-Id"
+X_REQUEST_ID = "x-request-id"
+
+REPO_API_REGEX = re.compile(
+ r"""
+ # staging or production endpoint
+ ^https://[^/]+
+ (
+ # on /api/repo_type/repo_id
+ /api/(models|datasets|spaces)/(.+)
+ |
+ # or /repo_id/resolve/revision/...
+ /(.+)/resolve/(.+)
+ )
+ """,
+ flags=re.VERBOSE,
+)
+
+
+class UniqueRequestIdAdapter(HTTPAdapter):
+ X_AMZN_TRACE_ID = "X-Amzn-Trace-Id"
+
+ def add_headers(self, request, **kwargs):
+ super().add_headers(request, **kwargs)
+
+ # Add random request ID => easier for server-side debug
+ if X_AMZN_TRACE_ID not in request.headers:
+ request.headers[X_AMZN_TRACE_ID] = request.headers.get(X_REQUEST_ID) or str(uuid.uuid4())
+
+ # Add debug log
+ has_token = len(str(request.headers.get("authorization", ""))) > 0
+ logger.debug(
+ f"Request {request.headers[X_AMZN_TRACE_ID]}: {request.method} {request.url} (authenticated: {has_token})"
+ )
+
+ def send(self, request: PreparedRequest, *args, **kwargs) -> Response:
+ """Catch any RequestException to append request id to the error message for debugging."""
+ if constants.HF_DEBUG:
+ logger.debug(f"Send: {_curlify(request)}")
+ try:
+ return super().send(request, *args, **kwargs)
+ except requests.RequestException as e:
+ request_id = request.headers.get(X_AMZN_TRACE_ID)
+ if request_id is not None:
+ # Taken from https://stackoverflow.com/a/58270258
+ e.args = (*e.args, f"(Request ID: {request_id})")
+ raise
+
+
+class OfflineAdapter(HTTPAdapter):
+ def send(self, request: PreparedRequest, *args, **kwargs) -> Response:
+ raise OfflineModeIsEnabled(
+ f"Cannot reach {request.url}: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable."
+ )
+
+
+def _default_backend_factory() -> requests.Session:
+ session = requests.Session()
+ if constants.HF_HUB_OFFLINE:
+ session.mount("http://", OfflineAdapter())
+ session.mount("https://", OfflineAdapter())
+ else:
+ session.mount("http://", UniqueRequestIdAdapter())
+ session.mount("https://", UniqueRequestIdAdapter())
+ return session
+
+
+BACKEND_FACTORY_T = Callable[[], requests.Session]
+_GLOBAL_BACKEND_FACTORY: BACKEND_FACTORY_T = _default_backend_factory
+
+
+def configure_http_backend(backend_factory: BACKEND_FACTORY_T = _default_backend_factory) -> None:
+ """
+ Configure the HTTP backend by providing a `backend_factory`. Any HTTP calls made by `huggingface_hub` will use a
+ Session object instantiated by this factory. This can be useful if you are running your scripts in a specific
+ environment requiring custom configuration (e.g. custom proxy or certifications).
+
+ Use [`get_session`] to get a configured Session. Since `requests.Session` is not guaranteed to be thread-safe,
+ `huggingface_hub` creates 1 Session instance per thread. They are all instantiated using the same `backend_factory`
+ set in [`configure_http_backend`]. A LRU cache is used to cache the created sessions (and connections) between
+ calls. Max size is 128 to avoid memory leaks if thousands of threads are spawned.
+
+ See [this issue](https://github.com/psf/requests/issues/2766) to know more about thread-safety in `requests`.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub import configure_http_backend, get_session
+
+ # Create a factory function that returns a Session with configured proxies
+ def backend_factory() -> requests.Session:
+ session = requests.Session()
+ session.proxies = {"http": "http://10.10.1.10:3128", "https": "https://10.10.1.11:1080"}
+ return session
+
+ # Set it as the default session factory
+ configure_http_backend(backend_factory=backend_factory)
+
+ # In practice, this is mostly done internally in `huggingface_hub`
+ session = get_session()
+ ```
+ """
+ global _GLOBAL_BACKEND_FACTORY
+ _GLOBAL_BACKEND_FACTORY = backend_factory
+ reset_sessions()
+
+
+def get_session() -> requests.Session:
+ """
+ Get a `requests.Session` object, using the session factory from the user.
+
+ Use [`get_session`] to get a configured Session. Since `requests.Session` is not guaranteed to be thread-safe,
+ `huggingface_hub` creates 1 Session instance per thread. They are all instantiated using the same `backend_factory`
+ set in [`configure_http_backend`]. A LRU cache is used to cache the created sessions (and connections) between
+ calls. Max size is 128 to avoid memory leaks if thousands of threads are spawned.
+
+ See [this issue](https://github.com/psf/requests/issues/2766) to know more about thread-safety in `requests`.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub import configure_http_backend, get_session
+
+ # Create a factory function that returns a Session with configured proxies
+ def backend_factory() -> requests.Session:
+ session = requests.Session()
+ session.proxies = {"http": "http://10.10.1.10:3128", "https": "https://10.10.1.11:1080"}
+ return session
+
+ # Set it as the default session factory
+ configure_http_backend(backend_factory=backend_factory)
+
+ # In practice, this is mostly done internally in `huggingface_hub`
+ session = get_session()
+ ```
+ """
+ return _get_session_from_cache(process_id=os.getpid(), thread_id=threading.get_ident())
+
+
+def reset_sessions() -> None:
+ """Reset the cache of sessions.
+
+ Mostly used internally when sessions are reconfigured or an SSLError is raised.
+ See [`configure_http_backend`] for more details.
+ """
+ _get_session_from_cache.cache_clear()
+
+
+@lru_cache
+def _get_session_from_cache(process_id: int, thread_id: int) -> requests.Session:
+ """
+ Create a new session per thread using global factory. Using LRU cache (maxsize 128) to avoid memory leaks when
+ using thousands of threads. Cache is cleared when `configure_http_backend` is called.
+ """
+ return _GLOBAL_BACKEND_FACTORY()
+
+
+def http_backoff(
+ method: HTTP_METHOD_T,
+ url: str,
+ *,
+ max_retries: int = 5,
+ base_wait_time: float = 1,
+ max_wait_time: float = 8,
+ retry_on_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = (
+ requests.Timeout,
+ requests.ConnectionError,
+ ),
+ retry_on_status_codes: Union[int, Tuple[int, ...]] = HTTPStatus.SERVICE_UNAVAILABLE,
+ **kwargs,
+) -> Response:
+ """Wrapper around requests to retry calls on an endpoint, with exponential backoff.
+
+ Endpoint call is retried on exceptions (ex: connection timeout, proxy error,...)
+ and/or on specific status codes (ex: service unavailable). If the call failed more
+ than `max_retries`, the exception is thrown or `raise_for_status` is called on the
+ response object.
+
+ Re-implement mechanisms from the `backoff` library to avoid adding an external
+ dependencies to `hugging_face_hub`. See https://github.com/litl/backoff.
+
+ Args:
+ method (`Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]`):
+ HTTP method to perform.
+ url (`str`):
+ The URL of the resource to fetch.
+ max_retries (`int`, *optional*, defaults to `5`):
+ Maximum number of retries, defaults to 5 (no retries).
+ base_wait_time (`float`, *optional*, defaults to `1`):
+ Duration (in seconds) to wait before retrying the first time.
+ Wait time between retries then grows exponentially, capped by
+ `max_wait_time`.
+ max_wait_time (`float`, *optional*, defaults to `8`):
+ Maximum duration (in seconds) to wait before retrying.
+ retry_on_exceptions (`Type[Exception]` or `Tuple[Type[Exception]]`, *optional*):
+ Define which exceptions must be caught to retry the request. Can be a single type or a tuple of types.
+ By default, retry on `requests.Timeout` and `requests.ConnectionError`.
+ retry_on_status_codes (`int` or `Tuple[int]`, *optional*, defaults to `503`):
+ Define on which status codes the request must be retried. By default, only
+ HTTP 503 Service Unavailable is retried.
+ **kwargs (`dict`, *optional*):
+ kwargs to pass to `requests.request`.
+
+ Example:
+ ```
+ >>> from huggingface_hub.utils import http_backoff
+
+ # Same usage as "requests.request".
+ >>> response = http_backoff("GET", "https://www.google.com")
+ >>> response.raise_for_status()
+
+ # If you expect a Gateway Timeout from time to time
+ >>> http_backoff("PUT", upload_url, data=data, retry_on_status_codes=504)
+ >>> response.raise_for_status()
+ ```
+
+
+
+ When using `requests` it is possible to stream data by passing an iterator to the
+ `data` argument. On http backoff this is a problem as the iterator is not reset
+ after a failed call. This issue is mitigated for file objects or any IO streams
+ by saving the initial position of the cursor (with `data.tell()`) and resetting the
+ cursor between each call (with `data.seek()`). For arbitrary iterators, http backoff
+ will fail. If this is a hard constraint for you, please let us know by opening an
+ issue on [Github](https://github.com/huggingface/huggingface_hub).
+
+
+ """
+ if isinstance(retry_on_exceptions, type): # Tuple from single exception type
+ retry_on_exceptions = (retry_on_exceptions,)
+
+ if isinstance(retry_on_status_codes, int): # Tuple from single status code
+ retry_on_status_codes = (retry_on_status_codes,)
+
+ nb_tries = 0
+ sleep_time = base_wait_time
+
+ # If `data` is used and is a file object (or any IO), it will be consumed on the
+ # first HTTP request. We need to save the initial position so that the full content
+ # of the file is re-sent on http backoff. See warning tip in docstring.
+ io_obj_initial_pos = None
+ if "data" in kwargs and isinstance(kwargs["data"], (io.IOBase, SliceFileObj)):
+ io_obj_initial_pos = kwargs["data"].tell()
+
+ session = get_session()
+ while True:
+ nb_tries += 1
+ try:
+ # If `data` is used and is a file object (or any IO), set back cursor to
+ # initial position.
+ if io_obj_initial_pos is not None:
+ kwargs["data"].seek(io_obj_initial_pos)
+
+ # Perform request and return if status_code is not in the retry list.
+ response = session.request(method=method, url=url, **kwargs)
+ if response.status_code not in retry_on_status_codes:
+ return response
+
+ # Wrong status code returned (HTTP 503 for instance)
+ logger.warning(f"HTTP Error {response.status_code} thrown while requesting {method} {url}")
+ if nb_tries > max_retries:
+ response.raise_for_status() # Will raise uncaught exception
+ # We return response to avoid infinite loop in the corner case where the
+ # user ask for retry on a status code that doesn't raise_for_status.
+ return response
+
+ except retry_on_exceptions as err:
+ logger.warning(f"'{err}' thrown while requesting {method} {url}")
+
+ if isinstance(err, requests.ConnectionError):
+ reset_sessions() # In case of SSLError it's best to reset the shared requests.Session objects
+
+ if nb_tries > max_retries:
+ raise err
+
+ # Sleep for X seconds
+ logger.warning(f"Retrying in {sleep_time}s [Retry {nb_tries}/{max_retries}].")
+ time.sleep(sleep_time)
+
+ # Update sleep time for next retry
+ sleep_time = min(max_wait_time, sleep_time * 2) # Exponential backoff
+
+
+def fix_hf_endpoint_in_url(url: str, endpoint: Optional[str]) -> str:
+ """Replace the default endpoint in a URL by a custom one.
+
+ This is useful when using a proxy and the Hugging Face Hub returns a URL with the default endpoint.
+ """
+ endpoint = endpoint.rstrip("/") if endpoint else constants.ENDPOINT
+ # check if a proxy has been set => if yes, update the returned URL to use the proxy
+ if endpoint not in (constants._HF_DEFAULT_ENDPOINT, constants._HF_DEFAULT_STAGING_ENDPOINT):
+ url = url.replace(constants._HF_DEFAULT_ENDPOINT, endpoint)
+ url = url.replace(constants._HF_DEFAULT_STAGING_ENDPOINT, endpoint)
+ return url
+
+
+def hf_raise_for_status(response: Response, endpoint_name: Optional[str] = None) -> None:
+ """
+ Internal version of `response.raise_for_status()` that will refine a
+ potential HTTPError. Raised exception will be an instance of `HfHubHTTPError`.
+
+ This helper is meant to be the unique method to raise_for_status when making a call
+ to the Hugging Face Hub.
+
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub.utils import get_session, hf_raise_for_status, HfHubHTTPError
+
+ response = get_session().post(...)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ print(str(e)) # formatted message
+ e.request_id, e.server_message # details returned by server
+
+ # Complete the error message with additional information once it's raised
+ e.append_to_message("\n`create_commit` expects the repository to exist.")
+ raise
+ ```
+
+ Args:
+ response (`Response`):
+ Response from the server.
+ endpoint_name (`str`, *optional*):
+ Name of the endpoint that has been called. If provided, the error message
+ will be more complete.
+
+
+
+ Raises when the request has failed:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it
+ doesn't exist, because `repo_type` is not set correctly, or because the repo
+ is `private` and you do not have access.
+ - [`~utils.GatedRepoError`]
+ If the repository exists but is gated and the user is not on the authorized
+ list.
+ - [`~utils.RevisionNotFoundError`]
+ If the repository exists but the revision couldn't be find.
+ - [`~utils.EntryNotFoundError`]
+ If the repository exists but the entry (e.g. the requested file) couldn't be
+ find.
+ - [`~utils.BadRequestError`]
+ If request failed with a HTTP 400 BadRequest error.
+ - [`~utils.HfHubHTTPError`]
+ If request failed for a reason not listed above.
+
+
+ """
+ try:
+ response.raise_for_status()
+ except HTTPError as e:
+ error_code = response.headers.get("X-Error-Code")
+ error_message = response.headers.get("X-Error-Message")
+
+ if error_code == "RevisionNotFound":
+ message = f"{response.status_code} Client Error." + "\n\n" + f"Revision Not Found for url: {response.url}."
+ raise _format(RevisionNotFoundError, message, response) from e
+
+ elif error_code == "EntryNotFound":
+ message = f"{response.status_code} Client Error." + "\n\n" + f"Entry Not Found for url: {response.url}."
+ raise _format(EntryNotFoundError, message, response) from e
+
+ elif error_code == "GatedRepo":
+ message = (
+ f"{response.status_code} Client Error." + "\n\n" + f"Cannot access gated repo for url {response.url}."
+ )
+ raise _format(GatedRepoError, message, response) from e
+
+ elif error_message == "Access to this resource is disabled.":
+ message = (
+ f"{response.status_code} Client Error."
+ + "\n\n"
+ + f"Cannot access repository for url {response.url}."
+ + "\n"
+ + "Access to this resource is disabled."
+ )
+ raise _format(DisabledRepoError, message, response) from e
+
+ elif error_code == "RepoNotFound" or (
+ response.status_code == 401
+ and error_message != "Invalid credentials in Authorization header"
+ and response.request is not None
+ and response.request.url is not None
+ and REPO_API_REGEX.search(response.request.url) is not None
+ ):
+ # 401 is misleading as it is returned for:
+ # - private and gated repos if user is not authenticated
+ # - missing repos
+ # => for now, we process them as `RepoNotFound` anyway.
+ # See https://gist.github.com/Wauplin/46c27ad266b15998ce56a6603796f0b9
+ message = (
+ f"{response.status_code} Client Error."
+ + "\n\n"
+ + f"Repository Not Found for url: {response.url}."
+ + "\nPlease make sure you specified the correct `repo_id` and"
+ " `repo_type`.\nIf you are trying to access a private or gated repo,"
+ " make sure you are authenticated."
+ )
+ raise _format(RepositoryNotFoundError, message, response) from e
+
+ elif response.status_code == 400:
+ message = (
+ f"\n\nBad request for {endpoint_name} endpoint:" if endpoint_name is not None else "\n\nBad request:"
+ )
+ raise _format(BadRequestError, message, response) from e
+
+ elif response.status_code == 403:
+ message = (
+ f"\n\n{response.status_code} Forbidden: {error_message}."
+ + f"\nCannot access content at: {response.url}."
+ + "\nMake sure your token has the correct permissions."
+ )
+ raise _format(HfHubHTTPError, message, response) from e
+
+ elif response.status_code == 416:
+ range_header = response.request.headers.get("Range")
+ message = f"{e}. Requested range: {range_header}. Content-Range: {response.headers.get('Content-Range')}."
+ raise _format(HfHubHTTPError, message, response) from e
+
+ # Convert `HTTPError` into a `HfHubHTTPError` to display request information
+ # as well (request id and/or server error message)
+ raise _format(HfHubHTTPError, str(e), response) from e
+
+
+def _format(error_type: Type[HfHubHTTPError], custom_message: str, response: Response) -> HfHubHTTPError:
+ server_errors = []
+
+ # Retrieve server error from header
+ from_headers = response.headers.get("X-Error-Message")
+ if from_headers is not None:
+ server_errors.append(from_headers)
+
+ # Retrieve server error from body
+ try:
+ # Case errors are returned in a JSON format
+ data = response.json()
+
+ error = data.get("error")
+ if error is not None:
+ if isinstance(error, list):
+ # Case {'error': ['my error 1', 'my error 2']}
+ server_errors.extend(error)
+ else:
+ # Case {'error': 'my error'}
+ server_errors.append(error)
+
+ errors = data.get("errors")
+ if errors is not None:
+ # Case {'errors': [{'message': 'my error 1'}, {'message': 'my error 2'}]}
+ for error in errors:
+ if "message" in error:
+ server_errors.append(error["message"])
+
+ except JSONDecodeError:
+ # If content is not JSON and not HTML, append the text
+ content_type = response.headers.get("Content-Type", "")
+ if response.text and "html" not in content_type.lower():
+ server_errors.append(response.text)
+
+ # Strip all server messages
+ server_errors = [str(line).strip() for line in server_errors if str(line).strip()]
+
+ # Deduplicate server messages (keep order)
+ # taken from https://stackoverflow.com/a/17016257
+ server_errors = list(dict.fromkeys(server_errors))
+
+ # Format server error
+ server_message = "\n".join(server_errors)
+
+ # Add server error to custom message
+ final_error_message = custom_message
+ if server_message and server_message.lower() not in custom_message.lower():
+ if "\n\n" in custom_message:
+ final_error_message += "\n" + server_message
+ else:
+ final_error_message += "\n\n" + server_message
+ # Add Request ID
+ request_id = str(response.headers.get(X_REQUEST_ID, ""))
+ if request_id:
+ request_id_message = f" (Request ID: {request_id})"
+ else:
+ # Fallback to X-Amzn-Trace-Id
+ request_id = str(response.headers.get(X_AMZN_TRACE_ID, ""))
+ if request_id:
+ request_id_message = f" (Amzn Trace ID: {request_id})"
+ if request_id and request_id.lower() not in final_error_message.lower():
+ if "\n" in final_error_message:
+ newline_index = final_error_message.index("\n")
+ final_error_message = (
+ final_error_message[:newline_index] + request_id_message + final_error_message[newline_index:]
+ )
+ else:
+ final_error_message += request_id_message
+
+ # Return
+ return error_type(final_error_message.strip(), response=response, server_message=server_message or None)
+
+
+def _curlify(request: requests.PreparedRequest) -> str:
+ """Convert a `requests.PreparedRequest` into a curl command (str).
+
+ Used for debug purposes only.
+
+ Implementation vendored from https://github.com/ofw/curlify/blob/master/curlify.py.
+ MIT License Copyright (c) 2016 Egor.
+ """
+ parts: List[Tuple[Any, Any]] = [
+ ("curl", None),
+ ("-X", request.method),
+ ]
+
+ for k, v in sorted(request.headers.items()):
+ if k.lower() == "authorization":
+ v = "" # Hide authorization header, no matter its value (can be Bearer, Key, etc.)
+ parts += [("-H", "{0}: {1}".format(k, v))]
+
+ if request.body:
+ body = request.body
+ if isinstance(body, bytes):
+ body = body.decode("utf-8", errors="ignore")
+ if len(body) > 1000:
+ body = body[:1000] + " ... [truncated]"
+ parts += [("-d", body.replace("\n", ""))]
+
+ parts += [(None, request.url)]
+
+ flat_parts = []
+ for k, v in parts:
+ if k:
+ flat_parts.append(quote(k))
+ if v:
+ flat_parts.append(quote(v))
+
+ return " ".join(flat_parts)
+
+
+# Regex to parse HTTP Range header
+RANGE_REGEX = re.compile(r"^\s*bytes\s*=\s*(\d*)\s*-\s*(\d*)\s*$", re.IGNORECASE)
+
+
+def _adjust_range_header(original_range: Optional[str], resume_size: int) -> Optional[str]:
+ """
+ Adjust HTTP Range header to account for resume position.
+ """
+ if not original_range:
+ return f"bytes={resume_size}-"
+
+ if "," in original_range:
+ raise ValueError(f"Multiple ranges detected - {original_range!r}, not supported yet.")
+
+ match = RANGE_REGEX.match(original_range)
+ if not match:
+ raise RuntimeError(f"Invalid range format - {original_range!r}.")
+ start, end = match.groups()
+
+ if not start:
+ if not end:
+ raise RuntimeError(f"Invalid range format - {original_range!r}.")
+
+ new_suffix = int(end) - resume_size
+ new_range = f"bytes=-{new_suffix}"
+ if new_suffix <= 0:
+ raise RuntimeError(f"Empty new range - {new_range!r}.")
+ return new_range
+
+ start = int(start)
+ new_start = start + resume_size
+ if end:
+ end = int(end)
+ new_range = f"bytes={new_start}-{end}"
+ if new_start > end:
+ raise RuntimeError(f"Empty new range - {new_range!r}.")
+ return new_range
+
+ return f"bytes={new_start}-"
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_lfs.py b/env/Lib/site-packages/huggingface_hub/utils/_lfs.py
new file mode 100644
index 0000000000000000000000000000000000000000..307f371ffa79a8ae726ee03458c52e230a792898
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_lfs.py
@@ -0,0 +1,110 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Git LFS related utilities"""
+
+import io
+import os
+from contextlib import AbstractContextManager
+from typing import BinaryIO
+
+
+class SliceFileObj(AbstractContextManager):
+ """
+ Utility context manager to read a *slice* of a seekable file-like object as a seekable, file-like object.
+
+ This is NOT thread safe
+
+ Inspired by stackoverflow.com/a/29838711/593036
+
+ Credits to @julien-c
+
+ Args:
+ fileobj (`BinaryIO`):
+ A file-like object to slice. MUST implement `tell()` and `seek()` (and `read()` of course).
+ `fileobj` will be reset to its original position when exiting the context manager.
+ seek_from (`int`):
+ The start of the slice (offset from position 0 in bytes).
+ read_limit (`int`):
+ The maximum number of bytes to read from the slice.
+
+ Attributes:
+ previous_position (`int`):
+ The previous position
+
+ Examples:
+
+ Reading 200 bytes with an offset of 128 bytes from a file (ie bytes 128 to 327):
+ ```python
+ >>> with open("path/to/file", "rb") as file:
+ ... with SliceFileObj(file, seek_from=128, read_limit=200) as fslice:
+ ... fslice.read(...)
+ ```
+
+ Reading a file in chunks of 512 bytes
+ ```python
+ >>> import os
+ >>> chunk_size = 512
+ >>> file_size = os.getsize("path/to/file")
+ >>> with open("path/to/file", "rb") as file:
+ ... for chunk_idx in range(ceil(file_size / chunk_size)):
+ ... with SliceFileObj(file, seek_from=chunk_idx * chunk_size, read_limit=chunk_size) as fslice:
+ ... chunk = fslice.read(...)
+
+ ```
+ """
+
+ def __init__(self, fileobj: BinaryIO, seek_from: int, read_limit: int):
+ self.fileobj = fileobj
+ self.seek_from = seek_from
+ self.read_limit = read_limit
+
+ def __enter__(self):
+ self._previous_position = self.fileobj.tell()
+ end_of_stream = self.fileobj.seek(0, os.SEEK_END)
+ self._len = min(self.read_limit, end_of_stream - self.seek_from)
+ # ^^ The actual number of bytes that can be read from the slice
+ self.fileobj.seek(self.seek_from, io.SEEK_SET)
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.fileobj.seek(self._previous_position, io.SEEK_SET)
+
+ def read(self, n: int = -1):
+ pos = self.tell()
+ if pos >= self._len:
+ return b""
+ remaining_amount = self._len - pos
+ data = self.fileobj.read(remaining_amount if n < 0 else min(n, remaining_amount))
+ return data
+
+ def tell(self) -> int:
+ return self.fileobj.tell() - self.seek_from
+
+ def seek(self, offset: int, whence: int = os.SEEK_SET) -> int:
+ start = self.seek_from
+ end = start + self._len
+ if whence in (os.SEEK_SET, os.SEEK_END):
+ offset = start + offset if whence == os.SEEK_SET else end + offset
+ offset = max(start, min(offset, end))
+ whence = os.SEEK_SET
+ elif whence == os.SEEK_CUR:
+ cur_pos = self.fileobj.tell()
+ offset = max(start - cur_pos, min(offset, end - cur_pos))
+ else:
+ raise ValueError(f"whence value {whence} is not supported")
+ return self.fileobj.seek(offset, whence) - self.seek_from
+
+ def __iter__(self):
+ yield self.read(n=4 * 1024 * 1024)
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_pagination.py b/env/Lib/site-packages/huggingface_hub/utils/_pagination.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7ab4fe7cba9bd13f01d9c81854a00fd30b7f0d9
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_pagination.py
@@ -0,0 +1,52 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle pagination on Huggingface Hub."""
+
+from typing import Dict, Iterable, Optional
+
+import requests
+
+from . import get_session, hf_raise_for_status, logging
+
+
+logger = logging.get_logger(__name__)
+
+
+def paginate(path: str, params: Dict, headers: Dict) -> Iterable:
+ """Fetch a list of models/datasets/spaces and paginate through results.
+
+ This is using the same "Link" header format as GitHub.
+ See:
+ - https://requests.readthedocs.io/en/latest/api/#requests.Response.links
+ - https://docs.github.com/en/rest/guides/traversing-with-pagination#link-header
+ """
+ session = get_session()
+ r = session.get(path, params=params, headers=headers)
+ hf_raise_for_status(r)
+ yield from r.json()
+
+ # Follow pages
+ # Next link already contains query params
+ next_page = _get_next_page(r)
+ while next_page is not None:
+ logger.debug(f"Pagination detected. Requesting next page: {next_page}")
+ r = session.get(next_page, headers=headers)
+ hf_raise_for_status(r)
+ yield from r.json()
+ next_page = _get_next_page(r)
+
+
+def _get_next_page(response: requests.Response) -> Optional[str]:
+ return response.links.get("next", {}).get("url")
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_paths.py b/env/Lib/site-packages/huggingface_hub/utils/_paths.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f2c0ebce070bbde4900e919a3aca7cfc331e747
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_paths.py
@@ -0,0 +1,141 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle paths in Huggingface Hub."""
+
+from fnmatch import fnmatch
+from pathlib import Path
+from typing import Callable, Generator, Iterable, List, Optional, TypeVar, Union
+
+
+T = TypeVar("T")
+
+# Always ignore `.git` and `.cache/huggingface` folders in commits
+DEFAULT_IGNORE_PATTERNS = [
+ ".git",
+ ".git/*",
+ "*/.git",
+ "**/.git/**",
+ ".cache/huggingface",
+ ".cache/huggingface/*",
+ "*/.cache/huggingface",
+ "**/.cache/huggingface/**",
+]
+# Forbidden to commit these folders
+FORBIDDEN_FOLDERS = [".git", ".cache"]
+
+
+def filter_repo_objects(
+ items: Iterable[T],
+ *,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ key: Optional[Callable[[T], str]] = None,
+) -> Generator[T, None, None]:
+ """Filter repo objects based on an allowlist and a denylist.
+
+ Input must be a list of paths (`str` or `Path`) or a list of arbitrary objects.
+ In the later case, `key` must be provided and specifies a function of one argument
+ that is used to extract a path from each element in iterable.
+
+ Patterns are Unix shell-style wildcards which are NOT regular expressions. See
+ https://docs.python.org/3/library/fnmatch.html for more details.
+
+ Args:
+ items (`Iterable`):
+ List of items to filter.
+ allow_patterns (`str` or `List[str]`, *optional*):
+ Patterns constituting the allowlist. If provided, item paths must match at
+ least one pattern from the allowlist.
+ ignore_patterns (`str` or `List[str]`, *optional*):
+ Patterns constituting the denylist. If provided, item paths must not match
+ any patterns from the denylist.
+ key (`Callable[[T], str]`, *optional*):
+ Single-argument function to extract a path from each item. If not provided,
+ the `items` must already be `str` or `Path`.
+
+ Returns:
+ Filtered list of objects, as a generator.
+
+ Raises:
+ :class:`ValueError`:
+ If `key` is not provided and items are not `str` or `Path`.
+
+ Example usage with paths:
+ ```python
+ >>> # Filter only PDFs that are not hidden.
+ >>> list(filter_repo_objects(
+ ... ["aaa.PDF", "bbb.jpg", ".ccc.pdf", ".ddd.png"],
+ ... allow_patterns=["*.pdf"],
+ ... ignore_patterns=[".*"],
+ ... ))
+ ["aaa.pdf"]
+ ```
+
+ Example usage with objects:
+ ```python
+ >>> list(filter_repo_objects(
+ ... [
+ ... CommitOperationAdd(path_or_fileobj="/tmp/aaa.pdf", path_in_repo="aaa.pdf")
+ ... CommitOperationAdd(path_or_fileobj="/tmp/bbb.jpg", path_in_repo="bbb.jpg")
+ ... CommitOperationAdd(path_or_fileobj="/tmp/.ccc.pdf", path_in_repo=".ccc.pdf")
+ ... CommitOperationAdd(path_or_fileobj="/tmp/.ddd.png", path_in_repo=".ddd.png")
+ ... ],
+ ... allow_patterns=["*.pdf"],
+ ... ignore_patterns=[".*"],
+ ... key=lambda x: x.repo_in_path
+ ... ))
+ [CommitOperationAdd(path_or_fileobj="/tmp/aaa.pdf", path_in_repo="aaa.pdf")]
+ ```
+ """
+ if isinstance(allow_patterns, str):
+ allow_patterns = [allow_patterns]
+
+ if isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+
+ if allow_patterns is not None:
+ allow_patterns = [_add_wildcard_to_directories(p) for p in allow_patterns]
+ if ignore_patterns is not None:
+ ignore_patterns = [_add_wildcard_to_directories(p) for p in ignore_patterns]
+
+ if key is None:
+
+ def _identity(item: T) -> str:
+ if isinstance(item, str):
+ return item
+ if isinstance(item, Path):
+ return str(item)
+ raise ValueError(f"Please provide `key` argument in `filter_repo_objects`: `{item}` is not a string.")
+
+ key = _identity # Items must be `str` or `Path`, otherwise raise ValueError
+
+ for item in items:
+ path = key(item)
+
+ # Skip if there's an allowlist and path doesn't match any
+ if allow_patterns is not None and not any(fnmatch(path, r) for r in allow_patterns):
+ continue
+
+ # Skip if there's a denylist and path matches any
+ if ignore_patterns is not None and any(fnmatch(path, r) for r in ignore_patterns):
+ continue
+
+ yield item
+
+
+def _add_wildcard_to_directories(pattern: str) -> str:
+ if pattern[-1] == "/":
+ return pattern + "*"
+ return pattern
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_runtime.py b/env/Lib/site-packages/huggingface_hub/utils/_runtime.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8d82d4129d4846422ecbf278b6550491c986e50
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_runtime.py
@@ -0,0 +1,379 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Check presence of installed packages at runtime."""
+
+import importlib.metadata
+import os
+import platform
+import sys
+import warnings
+from typing import Any, Dict
+
+from .. import __version__, constants
+
+
+_PY_VERSION: str = sys.version.split()[0].rstrip("+")
+
+_package_versions = {}
+
+_CANDIDATES = {
+ "aiohttp": {"aiohttp"},
+ "fastai": {"fastai"},
+ "fastapi": {"fastapi"},
+ "fastcore": {"fastcore"},
+ "gradio": {"gradio"},
+ "graphviz": {"graphviz"},
+ "hf_transfer": {"hf_transfer"},
+ "jinja": {"Jinja2"},
+ "keras": {"keras"},
+ "numpy": {"numpy"},
+ "pillow": {"Pillow"},
+ "pydantic": {"pydantic"},
+ "pydot": {"pydot"},
+ "safetensors": {"safetensors"},
+ "tensorboard": {"tensorboardX"},
+ "tensorflow": (
+ "tensorflow",
+ "tensorflow-cpu",
+ "tensorflow-gpu",
+ "tf-nightly",
+ "tf-nightly-cpu",
+ "tf-nightly-gpu",
+ "intel-tensorflow",
+ "intel-tensorflow-avx512",
+ "tensorflow-rocm",
+ "tensorflow-macos",
+ ),
+ "torch": {"torch"},
+}
+
+# Check once at runtime
+for candidate_name, package_names in _CANDIDATES.items():
+ _package_versions[candidate_name] = "N/A"
+ for name in package_names:
+ try:
+ _package_versions[candidate_name] = importlib.metadata.version(name)
+ break
+ except importlib.metadata.PackageNotFoundError:
+ pass
+
+
+def _get_version(package_name: str) -> str:
+ return _package_versions.get(package_name, "N/A")
+
+
+def is_package_available(package_name: str) -> bool:
+ return _get_version(package_name) != "N/A"
+
+
+# Python
+def get_python_version() -> str:
+ return _PY_VERSION
+
+
+# Huggingface Hub
+def get_hf_hub_version() -> str:
+ return __version__
+
+
+# aiohttp
+def is_aiohttp_available() -> bool:
+ return is_package_available("aiohttp")
+
+
+def get_aiohttp_version() -> str:
+ return _get_version("aiohttp")
+
+
+# FastAI
+def is_fastai_available() -> bool:
+ return is_package_available("fastai")
+
+
+def get_fastai_version() -> str:
+ return _get_version("fastai")
+
+
+# FastAPI
+def is_fastapi_available() -> bool:
+ return is_package_available("fastapi")
+
+
+def get_fastapi_version() -> str:
+ return _get_version("fastapi")
+
+
+# Fastcore
+def is_fastcore_available() -> bool:
+ return is_package_available("fastcore")
+
+
+def get_fastcore_version() -> str:
+ return _get_version("fastcore")
+
+
+# FastAI
+def is_gradio_available() -> bool:
+ return is_package_available("gradio")
+
+
+def get_gradio_version() -> str:
+ return _get_version("gradio")
+
+
+# Graphviz
+def is_graphviz_available() -> bool:
+ return is_package_available("graphviz")
+
+
+def get_graphviz_version() -> str:
+ return _get_version("graphviz")
+
+
+# hf_transfer
+def is_hf_transfer_available() -> bool:
+ return is_package_available("hf_transfer")
+
+
+def get_hf_transfer_version() -> str:
+ return _get_version("hf_transfer")
+
+
+# keras
+def is_keras_available() -> bool:
+ return is_package_available("keras")
+
+
+def get_keras_version() -> str:
+ return _get_version("keras")
+
+
+# Numpy
+def is_numpy_available() -> bool:
+ return is_package_available("numpy")
+
+
+def get_numpy_version() -> str:
+ return _get_version("numpy")
+
+
+# Jinja
+def is_jinja_available() -> bool:
+ return is_package_available("jinja")
+
+
+def get_jinja_version() -> str:
+ return _get_version("jinja")
+
+
+# Pillow
+def is_pillow_available() -> bool:
+ return is_package_available("pillow")
+
+
+def get_pillow_version() -> str:
+ return _get_version("pillow")
+
+
+# Pydantic
+def is_pydantic_available() -> bool:
+ if not is_package_available("pydantic"):
+ return False
+ # For Pydantic, we add an extra check to test whether it is correctly installed or not. If both pydantic 2.x and
+ # typing_extensions<=4.5.0 are installed, then pydantic will fail at import time. This should not happen when
+ # it is installed with `pip install huggingface_hub[inference]` but it can happen when it is installed manually
+ # by the user in an environment that we don't control.
+ #
+ # Usually we won't need to do this kind of check on optional dependencies. However, pydantic is a special case
+ # as it is automatically imported when doing `from huggingface_hub import ...` even if the user doesn't use it.
+ #
+ # See https://github.com/huggingface/huggingface_hub/pull/1829 for more details.
+ try:
+ from pydantic import validator # noqa: F401
+ except ImportError:
+ # Example: "ImportError: cannot import name 'TypeAliasType' from 'typing_extensions'"
+ warnings.warn(
+ "Pydantic is installed but cannot be imported. Please check your installation. `huggingface_hub` will "
+ "default to not using Pydantic. Error message: '{e}'"
+ )
+ return False
+ return True
+
+
+def get_pydantic_version() -> str:
+ return _get_version("pydantic")
+
+
+# Pydot
+def is_pydot_available() -> bool:
+ return is_package_available("pydot")
+
+
+def get_pydot_version() -> str:
+ return _get_version("pydot")
+
+
+# Tensorboard
+def is_tensorboard_available() -> bool:
+ return is_package_available("tensorboard")
+
+
+def get_tensorboard_version() -> str:
+ return _get_version("tensorboard")
+
+
+# Tensorflow
+def is_tf_available() -> bool:
+ return is_package_available("tensorflow")
+
+
+def get_tf_version() -> str:
+ return _get_version("tensorflow")
+
+
+# Torch
+def is_torch_available() -> bool:
+ return is_package_available("torch")
+
+
+def get_torch_version() -> str:
+ return _get_version("torch")
+
+
+# Safetensors
+def is_safetensors_available() -> bool:
+ return is_package_available("safetensors")
+
+
+# Shell-related helpers
+try:
+ # Set to `True` if script is running in a Google Colab notebook.
+ # If running in Google Colab, git credential store is set globally which makes the
+ # warning disappear. See https://github.com/huggingface/huggingface_hub/issues/1043
+ #
+ # Taken from https://stackoverflow.com/a/63519730.
+ _is_google_colab = "google.colab" in str(get_ipython()) # type: ignore # noqa: F821
+except NameError:
+ _is_google_colab = False
+
+
+def is_notebook() -> bool:
+ """Return `True` if code is executed in a notebook (Jupyter, Colab, QTconsole).
+
+ Taken from https://stackoverflow.com/a/39662359.
+ Adapted to make it work with Google colab as well.
+ """
+ try:
+ shell_class = get_ipython().__class__ # type: ignore # noqa: F821
+ for parent_class in shell_class.__mro__: # e.g. "is subclass of"
+ if parent_class.__name__ == "ZMQInteractiveShell":
+ return True # Jupyter notebook, Google colab or qtconsole
+ return False
+ except NameError:
+ return False # Probably standard Python interpreter
+
+
+def is_google_colab() -> bool:
+ """Return `True` if code is executed in a Google colab.
+
+ Taken from https://stackoverflow.com/a/63519730.
+ """
+ return _is_google_colab
+
+
+def is_colab_enterprise() -> bool:
+ """Return `True` if code is executed in a Google Colab Enterprise environment."""
+ return os.environ.get("VERTEX_PRODUCT") == "COLAB_ENTERPRISE"
+
+
+def dump_environment_info() -> Dict[str, Any]:
+ """Dump information about the machine to help debugging issues.
+
+ Similar helper exist in:
+ - `datasets` (https://github.com/huggingface/datasets/blob/main/src/datasets/commands/env.py)
+ - `diffusers` (https://github.com/huggingface/diffusers/blob/main/src/diffusers/commands/env.py)
+ - `transformers` (https://github.com/huggingface/transformers/blob/main/src/transformers/commands/env.py)
+ """
+ from huggingface_hub import get_token, whoami
+ from huggingface_hub.utils import list_credential_helpers
+
+ token = get_token()
+
+ # Generic machine info
+ info: Dict[str, Any] = {
+ "huggingface_hub version": get_hf_hub_version(),
+ "Platform": platform.platform(),
+ "Python version": get_python_version(),
+ }
+
+ # Interpreter info
+ try:
+ shell_class = get_ipython().__class__ # type: ignore # noqa: F821
+ info["Running in iPython ?"] = "Yes"
+ info["iPython shell"] = shell_class.__name__
+ except NameError:
+ info["Running in iPython ?"] = "No"
+ info["Running in notebook ?"] = "Yes" if is_notebook() else "No"
+ info["Running in Google Colab ?"] = "Yes" if is_google_colab() else "No"
+ info["Running in Google Colab Enterprise ?"] = "Yes" if is_colab_enterprise() else "No"
+ # Login info
+ info["Token path ?"] = constants.HF_TOKEN_PATH
+ info["Has saved token ?"] = token is not None
+ if token is not None:
+ try:
+ info["Who am I ?"] = whoami()["name"]
+ except Exception:
+ pass
+
+ try:
+ info["Configured git credential helpers"] = ", ".join(list_credential_helpers())
+ except Exception:
+ pass
+
+ # Installed dependencies
+ info["FastAI"] = get_fastai_version()
+ info["Tensorflow"] = get_tf_version()
+ info["Torch"] = get_torch_version()
+ info["Jinja2"] = get_jinja_version()
+ info["Graphviz"] = get_graphviz_version()
+ info["keras"] = get_keras_version()
+ info["Pydot"] = get_pydot_version()
+ info["Pillow"] = get_pillow_version()
+ info["hf_transfer"] = get_hf_transfer_version()
+ info["gradio"] = get_gradio_version()
+ info["tensorboard"] = get_tensorboard_version()
+ info["numpy"] = get_numpy_version()
+ info["pydantic"] = get_pydantic_version()
+ info["aiohttp"] = get_aiohttp_version()
+
+ # Environment variables
+ info["ENDPOINT"] = constants.ENDPOINT
+ info["HF_HUB_CACHE"] = constants.HF_HUB_CACHE
+ info["HF_ASSETS_CACHE"] = constants.HF_ASSETS_CACHE
+ info["HF_TOKEN_PATH"] = constants.HF_TOKEN_PATH
+ info["HF_STORED_TOKENS_PATH"] = constants.HF_STORED_TOKENS_PATH
+ info["HF_HUB_OFFLINE"] = constants.HF_HUB_OFFLINE
+ info["HF_HUB_DISABLE_TELEMETRY"] = constants.HF_HUB_DISABLE_TELEMETRY
+ info["HF_HUB_DISABLE_PROGRESS_BARS"] = constants.HF_HUB_DISABLE_PROGRESS_BARS
+ info["HF_HUB_DISABLE_SYMLINKS_WARNING"] = constants.HF_HUB_DISABLE_SYMLINKS_WARNING
+ info["HF_HUB_DISABLE_EXPERIMENTAL_WARNING"] = constants.HF_HUB_DISABLE_EXPERIMENTAL_WARNING
+ info["HF_HUB_DISABLE_IMPLICIT_TOKEN"] = constants.HF_HUB_DISABLE_IMPLICIT_TOKEN
+ info["HF_HUB_ENABLE_HF_TRANSFER"] = constants.HF_HUB_ENABLE_HF_TRANSFER
+ info["HF_HUB_ETAG_TIMEOUT"] = constants.HF_HUB_ETAG_TIMEOUT
+ info["HF_HUB_DOWNLOAD_TIMEOUT"] = constants.HF_HUB_DOWNLOAD_TIMEOUT
+
+ print("\nCopy-and-paste the text below in your GitHub issue.\n")
+ print("\n".join([f"- {prop}: {val}" for prop, val in info.items()]) + "\n")
+ return info
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_safetensors.py b/env/Lib/site-packages/huggingface_hub/utils/_safetensors.py
new file mode 100644
index 0000000000000000000000000000000000000000..38546c6d34db786c62861e1706f747a21b7012bf
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_safetensors.py
@@ -0,0 +1,111 @@
+import functools
+import operator
+from collections import defaultdict
+from dataclasses import dataclass, field
+from typing import Dict, List, Literal, Optional, Tuple
+
+
+FILENAME_T = str
+TENSOR_NAME_T = str
+DTYPE_T = Literal["F64", "F32", "F16", "BF16", "I64", "I32", "I16", "I8", "U8", "BOOL"]
+
+
+@dataclass
+class TensorInfo:
+ """Information about a tensor.
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Attributes:
+ dtype (`str`):
+ The data type of the tensor ("F64", "F32", "F16", "BF16", "I64", "I32", "I16", "I8", "U8", "BOOL").
+ shape (`List[int]`):
+ The shape of the tensor.
+ data_offsets (`Tuple[int, int]`):
+ The offsets of the data in the file as a tuple `[BEGIN, END]`.
+ parameter_count (`int`):
+ The number of parameters in the tensor.
+ """
+
+ dtype: DTYPE_T
+ shape: List[int]
+ data_offsets: Tuple[int, int]
+ parameter_count: int = field(init=False)
+
+ def __post_init__(self) -> None:
+ # Taken from https://stackoverflow.com/a/13840436
+ try:
+ self.parameter_count = functools.reduce(operator.mul, self.shape)
+ except TypeError:
+ self.parameter_count = 1 # scalar value has no shape
+
+
+@dataclass
+class SafetensorsFileMetadata:
+ """Metadata for a Safetensors file hosted on the Hub.
+
+ This class is returned by [`parse_safetensors_file_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Attributes:
+ metadata (`Dict`):
+ The metadata contained in the file.
+ tensors (`Dict[str, TensorInfo]`):
+ A map of all tensors. Keys are tensor names and values are information about the corresponding tensor, as a
+ [`TensorInfo`] object.
+ parameter_count (`Dict[str, int]`):
+ A map of the number of parameters per data type. Keys are data types and values are the number of parameters
+ of that data type.
+ """
+
+ metadata: Dict[str, str]
+ tensors: Dict[TENSOR_NAME_T, TensorInfo]
+ parameter_count: Dict[DTYPE_T, int] = field(init=False)
+
+ def __post_init__(self) -> None:
+ parameter_count: Dict[DTYPE_T, int] = defaultdict(int)
+ for tensor in self.tensors.values():
+ parameter_count[tensor.dtype] += tensor.parameter_count
+ self.parameter_count = dict(parameter_count)
+
+
+@dataclass
+class SafetensorsRepoMetadata:
+ """Metadata for a Safetensors repo.
+
+ A repo is considered to be a Safetensors repo if it contains either a 'model.safetensors' weight file (non-shared
+ model) or a 'model.safetensors.index.json' index file (sharded model) at its root.
+
+ This class is returned by [`get_safetensors_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Attributes:
+ metadata (`Dict`, *optional*):
+ The metadata contained in the 'model.safetensors.index.json' file, if it exists. Only populated for sharded
+ models.
+ sharded (`bool`):
+ Whether the repo contains a sharded model or not.
+ weight_map (`Dict[str, str]`):
+ A map of all weights. Keys are tensor names and values are filenames of the files containing the tensors.
+ files_metadata (`Dict[str, SafetensorsFileMetadata]`):
+ A map of all files metadata. Keys are filenames and values are the metadata of the corresponding file, as
+ a [`SafetensorsFileMetadata`] object.
+ parameter_count (`Dict[str, int]`):
+ A map of the number of parameters per data type. Keys are data types and values are the number of parameters
+ of that data type.
+ """
+
+ metadata: Optional[Dict]
+ sharded: bool
+ weight_map: Dict[TENSOR_NAME_T, FILENAME_T] # tensor name -> filename
+ files_metadata: Dict[FILENAME_T, SafetensorsFileMetadata] # filename -> metadata
+ parameter_count: Dict[DTYPE_T, int] = field(init=False)
+
+ def __post_init__(self) -> None:
+ parameter_count: Dict[DTYPE_T, int] = defaultdict(int)
+ for file_metadata in self.files_metadata.values():
+ for dtype, nb_parameters_ in file_metadata.parameter_count.items():
+ parameter_count[dtype] += nb_parameters_
+ self.parameter_count = dict(parameter_count)
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_subprocess.py b/env/Lib/site-packages/huggingface_hub/utils/_subprocess.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd51294a909e16e0c74af131d3a7d20f9c32fb1b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_subprocess.py
@@ -0,0 +1,142 @@
+# coding=utf-8
+# Copyright 2021 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+"""Contains utilities to easily handle subprocesses in `huggingface_hub`."""
+
+import os
+import subprocess
+import sys
+from contextlib import contextmanager
+from io import StringIO
+from pathlib import Path
+from typing import IO, Generator, List, Optional, Tuple, Union
+
+from .logging import get_logger
+
+
+logger = get_logger(__name__)
+
+
+@contextmanager
+def capture_output() -> Generator[StringIO, None, None]:
+ """Capture output that is printed to terminal.
+
+ Taken from https://stackoverflow.com/a/34738440
+
+ Example:
+ ```py
+ >>> with capture_output() as output:
+ ... print("hello world")
+ >>> assert output.getvalue() == "hello world\n"
+ ```
+ """
+ output = StringIO()
+ previous_output = sys.stdout
+ sys.stdout = output
+ yield output
+ sys.stdout = previous_output
+
+
+def run_subprocess(
+ command: Union[str, List[str]],
+ folder: Optional[Union[str, Path]] = None,
+ check=True,
+ **kwargs,
+) -> subprocess.CompletedProcess:
+ """
+ Method to run subprocesses. Calling this will capture the `stderr` and `stdout`,
+ please call `subprocess.run` manually in case you would like for them not to
+ be captured.
+
+ Args:
+ command (`str` or `List[str]`):
+ The command to execute as a string or list of strings.
+ folder (`str`, *optional*):
+ The folder in which to run the command. Defaults to current working
+ directory (from `os.getcwd()`).
+ check (`bool`, *optional*, defaults to `True`):
+ Setting `check` to `True` will raise a `subprocess.CalledProcessError`
+ when the subprocess has a non-zero exit code.
+ kwargs (`Dict[str]`):
+ Keyword arguments to be passed to the `subprocess.run` underlying command.
+
+ Returns:
+ `subprocess.CompletedProcess`: The completed process.
+ """
+ if isinstance(command, str):
+ command = command.split()
+
+ if isinstance(folder, Path):
+ folder = str(folder)
+
+ return subprocess.run(
+ command,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ check=check,
+ encoding="utf-8",
+ errors="replace", # if not utf-8, replace char by �
+ cwd=folder or os.getcwd(),
+ **kwargs,
+ )
+
+
+@contextmanager
+def run_interactive_subprocess(
+ command: Union[str, List[str]],
+ folder: Optional[Union[str, Path]] = None,
+ **kwargs,
+) -> Generator[Tuple[IO[str], IO[str]], None, None]:
+ """Run a subprocess in an interactive mode in a context manager.
+
+ Args:
+ command (`str` or `List[str]`):
+ The command to execute as a string or list of strings.
+ folder (`str`, *optional*):
+ The folder in which to run the command. Defaults to current working
+ directory (from `os.getcwd()`).
+ kwargs (`Dict[str]`):
+ Keyword arguments to be passed to the `subprocess.run` underlying command.
+
+ Returns:
+ `Tuple[IO[str], IO[str]]`: A tuple with `stdin` and `stdout` to interact
+ with the process (input and output are utf-8 encoded).
+
+ Example:
+ ```python
+ with _interactive_subprocess("git credential-store get") as (stdin, stdout):
+ # Write to stdin
+ stdin.write("url=hf.co\nusername=obama\n".encode("utf-8"))
+ stdin.flush()
+
+ # Read from stdout
+ output = stdout.read().decode("utf-8")
+ ```
+ """
+ if isinstance(command, str):
+ command = command.split()
+
+ with subprocess.Popen(
+ command,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ encoding="utf-8",
+ errors="replace", # if not utf-8, replace char by �
+ cwd=folder or os.getcwd(),
+ **kwargs,
+ ) as process:
+ assert process.stdin is not None, "subprocess is opened as subprocess.PIPE"
+ assert process.stdout is not None, "subprocess is opened as subprocess.PIPE"
+ yield process.stdin, process.stdout
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_telemetry.py b/env/Lib/site-packages/huggingface_hub/utils/_telemetry.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ba4a6349a8de1c565263ec73d235d36f88b68cf
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_telemetry.py
@@ -0,0 +1,126 @@
+from queue import Queue
+from threading import Lock, Thread
+from typing import Dict, Optional, Union
+from urllib.parse import quote
+
+from .. import constants, logging
+from . import build_hf_headers, get_session, hf_raise_for_status
+
+
+logger = logging.get_logger(__name__)
+
+# Telemetry is sent by a separate thread to avoid blocking the main thread.
+# A daemon thread is started once and consume tasks from the _TELEMETRY_QUEUE.
+# If the thread stops for some reason -shouldn't happen-, we restart a new one.
+_TELEMETRY_THREAD: Optional[Thread] = None
+_TELEMETRY_THREAD_LOCK = Lock() # Lock to avoid starting multiple threads in parallel
+_TELEMETRY_QUEUE: Queue = Queue()
+
+
+def send_telemetry(
+ topic: str,
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+) -> None:
+ """
+ Sends telemetry that helps tracking usage of different HF libraries.
+
+ This usage data helps us debug issues and prioritize new features. However, we understand that not everyone wants
+ to share additional information, and we respect your privacy. You can disable telemetry collection by setting the
+ `HF_HUB_DISABLE_TELEMETRY=1` as environment variable. Telemetry is also disabled in offline mode (i.e. when setting
+ `HF_HUB_OFFLINE=1`).
+
+ Telemetry collection is run in a separate thread to minimize impact for the user.
+
+ Args:
+ topic (`str`):
+ Name of the topic that is monitored. The topic is directly used to build the URL. If you want to monitor
+ subtopics, just use "/" separation. Examples: "gradio", "transformers/examples",...
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request. Will be added to the user-agent header.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request. Will be added to the user-agent header.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string. It will be completed with information about the installed packages.
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import send_telemetry
+
+ # Send telemetry without library information
+ >>> send_telemetry("ping")
+
+ # Send telemetry to subtopic with library information
+ >>> send_telemetry("gradio/local_link", library_name="gradio", library_version="3.22.1")
+
+ # Send telemetry with additional data
+ >>> send_telemetry(
+ ... topic="examples",
+ ... library_name="transformers",
+ ... library_version="4.26.0",
+ ... user_agent={"pipeline": "text_classification", "framework": "flax"},
+ ... )
+ ```
+ """
+ if constants.HF_HUB_OFFLINE or constants.HF_HUB_DISABLE_TELEMETRY:
+ return
+
+ _start_telemetry_thread() # starts thread only if doesn't exist yet
+ _TELEMETRY_QUEUE.put(
+ {"topic": topic, "library_name": library_name, "library_version": library_version, "user_agent": user_agent}
+ )
+
+
+def _start_telemetry_thread():
+ """Start a daemon thread to consume tasks from the telemetry queue.
+
+ If the thread is interrupted, start a new one.
+ """
+ with _TELEMETRY_THREAD_LOCK: # avoid to start multiple threads if called concurrently
+ global _TELEMETRY_THREAD
+ if _TELEMETRY_THREAD is None or not _TELEMETRY_THREAD.is_alive():
+ _TELEMETRY_THREAD = Thread(target=_telemetry_worker, daemon=True)
+ _TELEMETRY_THREAD.start()
+
+
+def _telemetry_worker():
+ """Wait for a task and consume it."""
+ while True:
+ kwargs = _TELEMETRY_QUEUE.get()
+ _send_telemetry_in_thread(**kwargs)
+ _TELEMETRY_QUEUE.task_done()
+
+
+def _send_telemetry_in_thread(
+ topic: str,
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+) -> None:
+ """Contains the actual data sending data to the Hub.
+
+ This function is called directly in gradio's analytics because
+ it is not possible to send telemetry from a daemon thread.
+
+ See here: https://github.com/gradio-app/gradio/pull/8180
+
+ Please do not rename or remove this function.
+ """
+ path = "/".join(quote(part) for part in topic.split("/") if len(part) > 0)
+ try:
+ r = get_session().head(
+ f"{constants.ENDPOINT}/api/telemetry/{path}",
+ headers=build_hf_headers(
+ token=False, # no need to send a token for telemetry
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ ),
+ )
+ hf_raise_for_status(r)
+ except Exception as e:
+ # We don't want to error in case of connection errors of any kind.
+ logger.debug(f"Error while sending telemetry: {e}")
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_typing.py b/env/Lib/site-packages/huggingface_hub/utils/_typing.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8388ca0c003e4741a44b298f02ef69932737f93
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_typing.py
@@ -0,0 +1,75 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Handle typing imports based on system compatibility."""
+
+import sys
+from typing import Any, Callable, List, Literal, Type, TypeVar, Union, get_args, get_origin
+
+
+UNION_TYPES: List[Any] = [Union]
+if sys.version_info >= (3, 10):
+ from types import UnionType
+
+ UNION_TYPES += [UnionType]
+
+
+HTTP_METHOD_T = Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]
+
+# type hint meaning "function signature not changed by decorator"
+CallableT = TypeVar("CallableT", bound=Callable)
+
+_JSON_SERIALIZABLE_TYPES = (int, float, str, bool, type(None))
+
+
+def is_jsonable(obj: Any) -> bool:
+ """Check if an object is JSON serializable.
+
+ This is a weak check, as it does not check for the actual JSON serialization, but only for the types of the object.
+ It works correctly for basic use cases but do not guarantee an exhaustive check.
+
+ Object is considered to be recursively json serializable if:
+ - it is an instance of int, float, str, bool, or NoneType
+ - it is a list or tuple and all its items are json serializable
+ - it is a dict and all its keys are strings and all its values are json serializable
+ """
+ try:
+ if isinstance(obj, _JSON_SERIALIZABLE_TYPES):
+ return True
+ if isinstance(obj, (list, tuple)):
+ return all(is_jsonable(item) for item in obj)
+ if isinstance(obj, dict):
+ return all(isinstance(key, _JSON_SERIALIZABLE_TYPES) and is_jsonable(value) for key, value in obj.items())
+ if hasattr(obj, "__json__"):
+ return True
+ return False
+ except RecursionError:
+ return False
+
+
+def is_simple_optional_type(type_: Type) -> bool:
+ """Check if a type is optional, i.e. Optional[Type] or Union[Type, None] or Type | None, where Type is a non-composite type."""
+ if get_origin(type_) in UNION_TYPES:
+ union_args = get_args(type_)
+ if len(union_args) == 2 and type(None) in union_args:
+ return True
+ return False
+
+
+def unwrap_simple_optional_type(optional_type: Type) -> Type:
+ """Unwraps a simple optional type, i.e. returns Type from Optional[Type]."""
+ for arg in get_args(optional_type):
+ if arg is not type(None):
+ return arg
+ raise ValueError(f"'{optional_type}' is not an optional type")
diff --git a/env/Lib/site-packages/huggingface_hub/utils/_validators.py b/env/Lib/site-packages/huggingface_hub/utils/_validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..27833f28e3e2030680fb72b95a547521bc08831b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/_validators.py
@@ -0,0 +1,226 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to validate argument values in `huggingface_hub`."""
+
+import inspect
+import re
+import warnings
+from functools import wraps
+from itertools import chain
+from typing import Any, Dict
+
+from huggingface_hub.errors import HFValidationError
+
+from ._typing import CallableT
+
+
+REPO_ID_REGEX = re.compile(
+ r"""
+ ^
+ (\b[\w\-.]+\b/)? # optional namespace (username or organization)
+ \b # starts with a word boundary
+ [\w\-.]{1,96} # repo_name: alphanumeric + . _ -
+ \b # ends with a word boundary
+ $
+ """,
+ flags=re.VERBOSE,
+)
+
+
+def validate_hf_hub_args(fn: CallableT) -> CallableT:
+ """Validate values received as argument for any public method of `huggingface_hub`.
+
+ The goal of this decorator is to harmonize validation of arguments reused
+ everywhere. By default, all defined validators are tested.
+
+ Validators:
+ - [`~utils.validate_repo_id`]: `repo_id` must be `"repo_name"`
+ or `"namespace/repo_name"`. Namespace is a username or an organization.
+ - [`~utils.smoothly_deprecate_use_auth_token`]: Use `token` instead of
+ `use_auth_token` (only if `use_auth_token` is not expected by the decorated
+ function - in practice, always the case in `huggingface_hub`).
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import validate_hf_hub_args
+
+ >>> @validate_hf_hub_args
+ ... def my_cool_method(repo_id: str):
+ ... print(repo_id)
+
+ >>> my_cool_method(repo_id="valid_repo_id")
+ valid_repo_id
+
+ >>> my_cool_method("other..repo..id")
+ huggingface_hub.utils._validators.HFValidationError: Cannot have -- or .. in repo_id: 'other..repo..id'.
+
+ >>> my_cool_method(repo_id="other..repo..id")
+ huggingface_hub.utils._validators.HFValidationError: Cannot have -- or .. in repo_id: 'other..repo..id'.
+
+ >>> @validate_hf_hub_args
+ ... def my_cool_auth_method(token: str):
+ ... print(token)
+
+ >>> my_cool_auth_method(token="a token")
+ "a token"
+
+ >>> my_cool_auth_method(use_auth_token="a use_auth_token")
+ "a use_auth_token"
+
+ >>> my_cool_auth_method(token="a token", use_auth_token="a use_auth_token")
+ UserWarning: Both `token` and `use_auth_token` are passed (...)
+ "a token"
+ ```
+
+ Raises:
+ [`~utils.HFValidationError`]:
+ If an input is not valid.
+ """
+ # TODO: add an argument to opt-out validation for specific argument?
+ signature = inspect.signature(fn)
+
+ # Should the validator switch `use_auth_token` values to `token`? In practice, always
+ # True in `huggingface_hub`. Might not be the case in a downstream library.
+ check_use_auth_token = "use_auth_token" not in signature.parameters and "token" in signature.parameters
+
+ @wraps(fn)
+ def _inner_fn(*args, **kwargs):
+ has_token = False
+ for arg_name, arg_value in chain(
+ zip(signature.parameters, args), # Args values
+ kwargs.items(), # Kwargs values
+ ):
+ if arg_name in ["repo_id", "from_id", "to_id"]:
+ validate_repo_id(arg_value)
+
+ elif arg_name == "token" and arg_value is not None:
+ has_token = True
+
+ if check_use_auth_token:
+ kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
+
+ return fn(*args, **kwargs)
+
+ return _inner_fn # type: ignore
+
+
+def validate_repo_id(repo_id: str) -> None:
+ """Validate `repo_id` is valid.
+
+ This is not meant to replace the proper validation made on the Hub but rather to
+ avoid local inconsistencies whenever possible (example: passing `repo_type` in the
+ `repo_id` is forbidden).
+
+ Rules:
+ - Between 1 and 96 characters.
+ - Either "repo_name" or "namespace/repo_name"
+ - [a-zA-Z0-9] or "-", "_", "."
+ - "--" and ".." are forbidden
+
+ Valid: `"foo"`, `"foo/bar"`, `"123"`, `"Foo-BAR_foo.bar123"`
+
+ Not valid: `"datasets/foo/bar"`, `".repo_id"`, `"foo--bar"`, `"foo.git"`
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import validate_repo_id
+ >>> validate_repo_id(repo_id="valid_repo_id")
+ >>> validate_repo_id(repo_id="other..repo..id")
+ huggingface_hub.utils._validators.HFValidationError: Cannot have -- or .. in repo_id: 'other..repo..id'.
+ ```
+
+ Discussed in https://github.com/huggingface/huggingface_hub/issues/1008.
+ In moon-landing (internal repository):
+ - https://github.com/huggingface/moon-landing/blob/main/server/lib/Names.ts#L27
+ - https://github.com/huggingface/moon-landing/blob/main/server/views/components/NewRepoForm/NewRepoForm.svelte#L138
+ """
+ if not isinstance(repo_id, str):
+ # Typically, a Path is not a repo_id
+ raise HFValidationError(f"Repo id must be a string, not {type(repo_id)}: '{repo_id}'.")
+
+ if repo_id.count("/") > 1:
+ raise HFValidationError(
+ "Repo id must be in the form 'repo_name' or 'namespace/repo_name':"
+ f" '{repo_id}'. Use `repo_type` argument if needed."
+ )
+
+ if not REPO_ID_REGEX.match(repo_id):
+ raise HFValidationError(
+ "Repo id must use alphanumeric chars or '-', '_', '.', '--' and '..' are"
+ " forbidden, '-' and '.' cannot start or end the name, max length is 96:"
+ f" '{repo_id}'."
+ )
+
+ if "--" in repo_id or ".." in repo_id:
+ raise HFValidationError(f"Cannot have -- or .. in repo_id: '{repo_id}'.")
+
+ if repo_id.endswith(".git"):
+ raise HFValidationError(f"Repo_id cannot end by '.git': '{repo_id}'.")
+
+
+def smoothly_deprecate_use_auth_token(fn_name: str, has_token: bool, kwargs: Dict[str, Any]) -> Dict[str, Any]:
+ """Smoothly deprecate `use_auth_token` in the `huggingface_hub` codebase.
+
+ The long-term goal is to remove any mention of `use_auth_token` in the codebase in
+ favor of a unique and less verbose `token` argument. This will be done a few steps:
+
+ 0. Step 0: methods that require a read-access to the Hub use the `use_auth_token`
+ argument (`str`, `bool` or `None`). Methods requiring write-access have a `token`
+ argument (`str`, `None`). This implicit rule exists to be able to not send the
+ token when not necessary (`use_auth_token=False`) even if logged in.
+
+ 1. Step 1: we want to harmonize everything and use `token` everywhere (supporting
+ `token=False` for read-only methods). In order not to break existing code, if
+ `use_auth_token` is passed to a function, the `use_auth_token` value is passed
+ as `token` instead, without any warning.
+ a. Corner case: if both `use_auth_token` and `token` values are passed, a warning
+ is thrown and the `use_auth_token` value is ignored.
+
+ 2. Step 2: Once it is release, we should push downstream libraries to switch from
+ `use_auth_token` to `token` as much as possible, but without throwing a warning
+ (e.g. manually create issues on the corresponding repos).
+
+ 3. Step 3: After a transitional period (6 months e.g. until April 2023?), we update
+ `huggingface_hub` to throw a warning on `use_auth_token`. Hopefully, very few
+ users will be impacted as it would have already been fixed.
+ In addition, unit tests in `huggingface_hub` must be adapted to expect warnings
+ to be thrown (but still use `use_auth_token` as before).
+
+ 4. Step 4: After a normal deprecation cycle (3 releases ?), remove this validator.
+ `use_auth_token` will definitely not be supported.
+ In addition, we update unit tests in `huggingface_hub` to use `token` everywhere.
+
+ This has been discussed in:
+ - https://github.com/huggingface/huggingface_hub/issues/1094.
+ - https://github.com/huggingface/huggingface_hub/pull/928
+ - (related) https://github.com/huggingface/huggingface_hub/pull/1064
+ """
+ new_kwargs = kwargs.copy() # do not mutate input !
+
+ use_auth_token = new_kwargs.pop("use_auth_token", None) # remove from kwargs
+ if use_auth_token is not None:
+ if has_token:
+ warnings.warn(
+ "Both `token` and `use_auth_token` are passed to"
+ f" `{fn_name}` with non-None values. `token` is now the"
+ " preferred argument to pass a User Access Token."
+ " `use_auth_token` value will be ignored."
+ )
+ else:
+ # `token` argument is not passed and a non-None value is passed in
+ # `use_auth_token` => use `use_auth_token` value as `token` kwarg.
+ new_kwargs["token"] = use_auth_token
+
+ return new_kwargs
diff --git a/env/Lib/site-packages/huggingface_hub/utils/endpoint_helpers.py b/env/Lib/site-packages/huggingface_hub/utils/endpoint_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..85cd86011b78bcdc57034aeebc3c01e9e721ab50
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/endpoint_helpers.py
@@ -0,0 +1,66 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Helpful utility functions and classes in relation to exploring API endpoints
+with the aim for a user-friendly interface.
+"""
+
+import math
+import re
+from typing import TYPE_CHECKING
+
+from ..repocard_data import ModelCardData
+
+
+if TYPE_CHECKING:
+ from ..hf_api import ModelInfo
+
+
+def _is_emission_within_threshold(model_info: "ModelInfo", minimum_threshold: float, maximum_threshold: float) -> bool:
+ """Checks if a model's emission is within a given threshold.
+
+ Args:
+ model_info (`ModelInfo`):
+ A model info object containing the model's emission information.
+ minimum_threshold (`float`):
+ A minimum carbon threshold to filter by, such as 1.
+ maximum_threshold (`float`):
+ A maximum carbon threshold to filter by, such as 10.
+
+ Returns:
+ `bool`: Whether the model's emission is within the given threshold.
+ """
+ if minimum_threshold is None and maximum_threshold is None:
+ raise ValueError("Both `minimum_threshold` and `maximum_threshold` cannot both be `None`")
+ if minimum_threshold is None:
+ minimum_threshold = -1
+ if maximum_threshold is None:
+ maximum_threshold = math.inf
+
+ card_data = getattr(model_info, "card_data", None)
+ if card_data is None or not isinstance(card_data, (dict, ModelCardData)):
+ return False
+
+ # Get CO2 emission metadata
+ emission = card_data.get("co2_eq_emissions", None)
+ if isinstance(emission, dict):
+ emission = emission["emissions"]
+ if not emission:
+ return False
+
+ # Filter out if value is missing or out of range
+ matched = re.search(r"\d+\.\d+|\d+", str(emission))
+ if matched is None:
+ return False
+
+ emission_value = float(matched.group(0))
+ return minimum_threshold <= emission_value <= maximum_threshold
diff --git a/env/Lib/site-packages/huggingface_hub/utils/insecure_hashlib.py b/env/Lib/site-packages/huggingface_hub/utils/insecure_hashlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..f232ee0adcfc52dcc18b5ea4d9c913b206521f71
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/insecure_hashlib.py
@@ -0,0 +1,34 @@
+# Taken from https://github.com/mlflow/mlflow/pull/10119
+#
+# DO NOT use this function for security purposes (e.g., password hashing).
+#
+# In Python >= 3.9, insecure hashing algorithms such as MD5 fail in FIPS-compliant
+# environments unless `usedforsecurity=False` is explicitly passed.
+#
+# References:
+# - https://github.com/mlflow/mlflow/issues/9905
+# - https://github.com/mlflow/mlflow/pull/10119
+# - https://docs.python.org/3/library/hashlib.html
+# - https://github.com/huggingface/transformers/pull/27038
+#
+# Usage:
+# ```python
+# # Use
+# from huggingface_hub.utils.insecure_hashlib import sha256
+# # instead of
+# from hashlib import sha256
+#
+# # Use
+# from huggingface_hub.utils import insecure_hashlib
+# # instead of
+# import hashlib
+# ```
+import functools
+import hashlib
+import sys
+
+
+_kwargs = {"usedforsecurity": False} if sys.version_info >= (3, 9) else {}
+md5 = functools.partial(hashlib.md5, **_kwargs)
+sha1 = functools.partial(hashlib.sha1, **_kwargs)
+sha256 = functools.partial(hashlib.sha256, **_kwargs)
diff --git a/env/Lib/site-packages/huggingface_hub/utils/logging.py b/env/Lib/site-packages/huggingface_hub/utils/logging.py
new file mode 100644
index 0000000000000000000000000000000000000000..813719683a54cc65768bab5488e7ea153ad08d7e
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/logging.py
@@ -0,0 +1,188 @@
+# coding=utf-8
+# Copyright 2020 Optuna, Hugging Face
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Logging utilities."""
+
+import logging
+import os
+from logging import (
+ CRITICAL, # NOQA
+ DEBUG, # NOQA
+ ERROR, # NOQA
+ FATAL, # NOQA
+ INFO, # NOQA
+ NOTSET, # NOQA
+ WARN, # NOQA
+ WARNING, # NOQA
+)
+from typing import Optional
+
+from .. import constants
+
+
+log_levels = {
+ "debug": logging.DEBUG,
+ "info": logging.INFO,
+ "warning": logging.WARNING,
+ "error": logging.ERROR,
+ "critical": logging.CRITICAL,
+}
+
+_default_log_level = logging.WARNING
+
+
+def _get_library_name() -> str:
+ return __name__.split(".")[0]
+
+
+def _get_library_root_logger() -> logging.Logger:
+ return logging.getLogger(_get_library_name())
+
+
+def _get_default_logging_level():
+ """
+ If `HF_HUB_VERBOSITY` env var is set to one of the valid choices return that as the new default level. If it is not
+ - fall back to `_default_log_level`
+ """
+ env_level_str = os.getenv("HF_HUB_VERBOSITY", None)
+ if env_level_str:
+ if env_level_str in log_levels:
+ return log_levels[env_level_str]
+ else:
+ logging.getLogger().warning(
+ f"Unknown option HF_HUB_VERBOSITY={env_level_str}, has to be one of: {', '.join(log_levels.keys())}"
+ )
+ return _default_log_level
+
+
+def _configure_library_root_logger() -> None:
+ library_root_logger = _get_library_root_logger()
+ library_root_logger.addHandler(logging.StreamHandler())
+ library_root_logger.setLevel(_get_default_logging_level())
+
+
+def _reset_library_root_logger() -> None:
+ library_root_logger = _get_library_root_logger()
+ library_root_logger.setLevel(logging.NOTSET)
+
+
+def get_logger(name: Optional[str] = None) -> logging.Logger:
+ """
+ Returns a logger with the specified name. This function is not supposed
+ to be directly accessed by library users.
+
+ Args:
+ name (`str`, *optional*):
+ The name of the logger to get, usually the filename
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import get_logger
+
+ >>> logger = get_logger(__file__)
+ >>> logger.set_verbosity_info()
+ ```
+ """
+
+ if name is None:
+ name = _get_library_name()
+
+ return logging.getLogger(name)
+
+
+def get_verbosity() -> int:
+ """Return the current level for the HuggingFace Hub's root logger.
+
+ Returns:
+ Logging level, e.g., `huggingface_hub.logging.DEBUG` and
+ `huggingface_hub.logging.INFO`.
+
+
+
+ HuggingFace Hub has following logging levels:
+
+ - `huggingface_hub.logging.CRITICAL`, `huggingface_hub.logging.FATAL`
+ - `huggingface_hub.logging.ERROR`
+ - `huggingface_hub.logging.WARNING`, `huggingface_hub.logging.WARN`
+ - `huggingface_hub.logging.INFO`
+ - `huggingface_hub.logging.DEBUG`
+
+
+ """
+ return _get_library_root_logger().getEffectiveLevel()
+
+
+def set_verbosity(verbosity: int) -> None:
+ """
+ Sets the level for the HuggingFace Hub's root logger.
+
+ Args:
+ verbosity (`int`):
+ Logging level, e.g., `huggingface_hub.logging.DEBUG` and
+ `huggingface_hub.logging.INFO`.
+ """
+ _get_library_root_logger().setLevel(verbosity)
+
+
+def set_verbosity_info():
+ """
+ Sets the verbosity to `logging.INFO`.
+ """
+ return set_verbosity(INFO)
+
+
+def set_verbosity_warning():
+ """
+ Sets the verbosity to `logging.WARNING`.
+ """
+ return set_verbosity(WARNING)
+
+
+def set_verbosity_debug():
+ """
+ Sets the verbosity to `logging.DEBUG`.
+ """
+ return set_verbosity(DEBUG)
+
+
+def set_verbosity_error():
+ """
+ Sets the verbosity to `logging.ERROR`.
+ """
+ return set_verbosity(ERROR)
+
+
+def disable_propagation() -> None:
+ """
+ Disable propagation of the library log outputs. Note that log propagation is
+ disabled by default.
+ """
+ _get_library_root_logger().propagate = False
+
+
+def enable_propagation() -> None:
+ """
+ Enable propagation of the library log outputs. Please disable the
+ HuggingFace Hub's default handler to prevent double logging if the root
+ logger has been configured.
+ """
+ _get_library_root_logger().propagate = True
+
+
+_configure_library_root_logger()
+
+if constants.HF_DEBUG:
+ # If `HF_DEBUG` environment variable is set, set the verbosity of `huggingface_hub` logger to `DEBUG`.
+ set_verbosity_debug()
diff --git a/env/Lib/site-packages/huggingface_hub/utils/sha.py b/env/Lib/site-packages/huggingface_hub/utils/sha.py
new file mode 100644
index 0000000000000000000000000000000000000000..001c3fe8b2f37a64e890888ca3d521c10ec8f03b
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/sha.py
@@ -0,0 +1,64 @@
+"""Utilities to efficiently compute the SHA 256 hash of a bunch of bytes."""
+
+from typing import BinaryIO, Optional
+
+from .insecure_hashlib import sha1, sha256
+
+
+def sha_fileobj(fileobj: BinaryIO, chunk_size: Optional[int] = None) -> bytes:
+ """
+ Computes the sha256 hash of the given file object, by chunks of size `chunk_size`.
+
+ Args:
+ fileobj (file-like object):
+ The File object to compute sha256 for, typically obtained with `open(path, "rb")`
+ chunk_size (`int`, *optional*):
+ The number of bytes to read from `fileobj` at once, defaults to 1MB.
+
+ Returns:
+ `bytes`: `fileobj`'s sha256 hash as bytes
+ """
+ chunk_size = chunk_size if chunk_size is not None else 1024 * 1024
+
+ sha = sha256()
+ while True:
+ chunk = fileobj.read(chunk_size)
+ sha.update(chunk)
+ if not chunk:
+ break
+ return sha.digest()
+
+
+def git_hash(data: bytes) -> str:
+ """
+ Computes the git-sha1 hash of the given bytes, using the same algorithm as git.
+
+ This is equivalent to running `git hash-object`. See https://git-scm.com/docs/git-hash-object
+ for more details.
+
+ Note: this method is valid for regular files. For LFS files, the proper git hash is supposed to be computed on the
+ pointer file content, not the actual file content. However, for simplicity, we directly compare the sha256 of
+ the LFS file content when we want to compare LFS files.
+
+ Args:
+ data (`bytes`):
+ The data to compute the git-hash for.
+
+ Returns:
+ `str`: the git-hash of `data` as an hexadecimal string.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.utils.sha import git_hash
+ >>> git_hash(b"Hello, World!")
+ 'b45ef6fec89518d314f546fd6c3025367b721684'
+ ```
+ """
+ # Taken from https://gist.github.com/msabramo/763200
+ # Note: no need to optimize by reading the file in chunks as we're not supposed to hash huge files (5MB maximum).
+ sha = sha1()
+ sha.update(b"blob ")
+ sha.update(str(len(data)).encode())
+ sha.update(b"\0")
+ sha.update(data)
+ return sha.hexdigest()
diff --git a/env/Lib/site-packages/huggingface_hub/utils/tqdm.py b/env/Lib/site-packages/huggingface_hub/utils/tqdm.py
new file mode 100644
index 0000000000000000000000000000000000000000..b22b797c32b6a0938eb73272b4365a5caa3ba250
--- /dev/null
+++ b/env/Lib/site-packages/huggingface_hub/utils/tqdm.py
@@ -0,0 +1,279 @@
+# coding=utf-8
+# Copyright 2021 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+"""Utility helpers to handle progress bars in `huggingface_hub`.
+
+Example:
+ 1. Use `huggingface_hub.utils.tqdm` as you would use `tqdm.tqdm` or `tqdm.auto.tqdm`.
+ 2. To disable progress bars, either use `disable_progress_bars()` helper or set the
+ environment variable `HF_HUB_DISABLE_PROGRESS_BARS` to 1.
+ 3. To re-enable progress bars, use `enable_progress_bars()`.
+ 4. To check whether progress bars are disabled, use `are_progress_bars_disabled()`.
+
+NOTE: Environment variable `HF_HUB_DISABLE_PROGRESS_BARS` has the priority.
+
+Example:
+ ```py
+ >>> from huggingface_hub.utils import are_progress_bars_disabled, disable_progress_bars, enable_progress_bars, tqdm
+
+ # Disable progress bars globally
+ >>> disable_progress_bars()
+
+ # Use as normal `tqdm`
+ >>> for _ in tqdm(range(5)):
+ ... pass
+
+ # Still not showing progress bars, as `disable=False` is overwritten to `True`.
+ >>> for _ in tqdm(range(5), disable=False):
+ ... pass
+
+ >>> are_progress_bars_disabled()
+ True
+
+ # Re-enable progress bars globally
+ >>> enable_progress_bars()
+
+ # Progress bar will be shown !
+ >>> for _ in tqdm(range(5)):
+ ... pass
+ 100%|███████████████████████████████████████| 5/5 [00:00<00:00, 117817.53it/s]
+ ```
+
+Group-based control:
+ ```python
+ # Disable progress bars for a specific group
+ >>> disable_progress_bars("peft.foo")
+
+ # Check state of different groups
+ >>> assert not are_progress_bars_disabled("peft"))
+ >>> assert not are_progress_bars_disabled("peft.something")
+ >>> assert are_progress_bars_disabled("peft.foo"))
+ >>> assert are_progress_bars_disabled("peft.foo.bar"))
+
+ # Enable progress bars for a subgroup
+ >>> enable_progress_bars("peft.foo.bar")
+
+ # Check if enabling a subgroup affects the parent group
+ >>> assert are_progress_bars_disabled("peft.foo"))
+ >>> assert not are_progress_bars_disabled("peft.foo.bar"))
+
+ # No progress bar for `name="peft.foo"`
+ >>> for _ in tqdm(range(5), name="peft.foo"):
+ ... pass
+
+ # Progress bar will be shown for `name="peft.foo.bar"`
+ >>> for _ in tqdm(range(5), name="peft.foo.bar"):
+ ... pass
+ 100%|███████████████████████████████████████| 5/5 [00:00<00:00, 117817.53it/s]
+
+ ```
+"""
+
+import io
+import logging
+import os
+import warnings
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Dict, Iterator, Optional, Union
+
+from tqdm.auto import tqdm as old_tqdm
+
+from ..constants import HF_HUB_DISABLE_PROGRESS_BARS
+
+
+# The `HF_HUB_DISABLE_PROGRESS_BARS` environment variable can be True, False, or not set (None),
+# allowing for control over progress bar visibility. When set, this variable takes precedence
+# over programmatic settings, dictating whether progress bars should be shown or hidden globally.
+# Essentially, the environment variable's setting overrides any code-based configurations.
+#
+# If `HF_HUB_DISABLE_PROGRESS_BARS` is not defined (None), it implies that users can manage
+# progress bar visibility through code. By default, progress bars are turned on.
+
+
+progress_bar_states: Dict[str, bool] = {}
+
+
+def disable_progress_bars(name: Optional[str] = None) -> None:
+ """
+ Disable progress bars either globally or for a specified group.
+
+ This function updates the state of progress bars based on a group name.
+ If no group name is provided, all progress bars are disabled. The operation
+ respects the `HF_HUB_DISABLE_PROGRESS_BARS` environment variable's setting.
+
+ Args:
+ name (`str`, *optional*):
+ The name of the group for which to disable the progress bars. If None,
+ progress bars are disabled globally.
+
+ Raises:
+ Warning: If the environment variable precludes changes.
+ """
+ if HF_HUB_DISABLE_PROGRESS_BARS is False:
+ warnings.warn(
+ "Cannot disable progress bars: environment variable `HF_HUB_DISABLE_PROGRESS_BARS=0` is set and has priority."
+ )
+ return
+
+ if name is None:
+ progress_bar_states.clear()
+ progress_bar_states["_global"] = False
+ else:
+ keys_to_remove = [key for key in progress_bar_states if key.startswith(f"{name}.")]
+ for key in keys_to_remove:
+ del progress_bar_states[key]
+ progress_bar_states[name] = False
+
+
+def enable_progress_bars(name: Optional[str] = None) -> None:
+ """
+ Enable progress bars either globally or for a specified group.
+
+ This function sets the progress bars to enabled for the specified group or globally
+ if no group is specified. The operation is subject to the `HF_HUB_DISABLE_PROGRESS_BARS`
+ environment setting.
+
+ Args:
+ name (`str`, *optional*):
+ The name of the group for which to enable the progress bars. If None,
+ progress bars are enabled globally.
+
+ Raises:
+ Warning: If the environment variable precludes changes.
+ """
+ if HF_HUB_DISABLE_PROGRESS_BARS is True:
+ warnings.warn(
+ "Cannot enable progress bars: environment variable `HF_HUB_DISABLE_PROGRESS_BARS=1` is set and has priority."
+ )
+ return
+
+ if name is None:
+ progress_bar_states.clear()
+ progress_bar_states["_global"] = True
+ else:
+ keys_to_remove = [key for key in progress_bar_states if key.startswith(f"{name}.")]
+ for key in keys_to_remove:
+ del progress_bar_states[key]
+ progress_bar_states[name] = True
+
+
+def are_progress_bars_disabled(name: Optional[str] = None) -> bool:
+ """
+ Check if progress bars are disabled globally or for a specific group.
+
+ This function returns whether progress bars are disabled for a given group or globally.
+ It checks the `HF_HUB_DISABLE_PROGRESS_BARS` environment variable first, then the programmatic
+ settings.
+
+ Args:
+ name (`str`, *optional*):
+ The group name to check; if None, checks the global setting.
+
+ Returns:
+ `bool`: True if progress bars are disabled, False otherwise.
+ """
+ if HF_HUB_DISABLE_PROGRESS_BARS is True:
+ return True
+
+ if name is None:
+ return not progress_bar_states.get("_global", True)
+
+ while name:
+ if name in progress_bar_states:
+ return not progress_bar_states[name]
+ name = ".".join(name.split(".")[:-1])
+
+ return not progress_bar_states.get("_global", True)
+
+
+def is_tqdm_disabled(log_level: int) -> Optional[bool]:
+ """
+ Determine if tqdm progress bars should be disabled based on logging level and environment settings.
+
+ see https://github.com/huggingface/huggingface_hub/pull/2000 and https://github.com/huggingface/huggingface_hub/pull/2698.
+ """
+ if log_level == logging.NOTSET:
+ return True
+ if os.getenv("TQDM_POSITION") == "-1":
+ return False
+ return None
+
+
+class tqdm(old_tqdm):
+ """
+ Class to override `disable` argument in case progress bars are globally disabled.
+
+ Taken from https://github.com/tqdm/tqdm/issues/619#issuecomment-619639324.
+ """
+
+ def __init__(self, *args, **kwargs):
+ name = kwargs.pop("name", None) # do not pass `name` to `tqdm`
+ if are_progress_bars_disabled(name):
+ kwargs["disable"] = True
+ super().__init__(*args, **kwargs)
+
+ def __delattr__(self, attr: str) -> None:
+ """Fix for https://github.com/huggingface/huggingface_hub/issues/1603"""
+ try:
+ super().__delattr__(attr)
+ except AttributeError:
+ if attr != "_lock":
+ raise
+
+
+@contextmanager
+def tqdm_stream_file(path: Union[Path, str]) -> Iterator[io.BufferedReader]:
+ """
+ Open a file as binary and wrap the `read` method to display a progress bar when it's streamed.
+
+ First implemented in `transformers` in 2019 but removed when switched to git-lfs. Used in `huggingface_hub` to show
+ progress bar when uploading an LFS file to the Hub. See github.com/huggingface/transformers/pull/2078#discussion_r354739608
+ for implementation details.
+
+ Note: currently implementation handles only files stored on disk as it is the most common use case. Could be
+ extended to stream any `BinaryIO` object but we might have to debug some corner cases.
+
+ Example:
+ ```py
+ >>> with tqdm_stream_file("config.json") as f:
+ >>> requests.put(url, data=f)
+ config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
+ ```
+ """
+ if isinstance(path, str):
+ path = Path(path)
+
+ with path.open("rb") as f:
+ total_size = path.stat().st_size
+ pbar = tqdm(
+ unit="B",
+ unit_scale=True,
+ total=total_size,
+ initial=0,
+ desc=path.name,
+ )
+
+ f_read = f.read
+
+ def _inner_read(size: Optional[int] = -1) -> bytes:
+ data = f_read(size)
+ pbar.update(len(data))
+ return data
+
+ f.read = _inner_read # type: ignore
+
+ yield f
+
+ pbar.close()
diff --git a/env/Lib/site-packages/idna-3.10.dist-info/INSTALLER b/env/Lib/site-packages/idna-3.10.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/env/Lib/site-packages/idna-3.10.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/env/Lib/site-packages/idna-3.10.dist-info/LICENSE.md b/env/Lib/site-packages/idna-3.10.dist-info/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..19b6b45242c16a1025465309eec2ca5009319de3
--- /dev/null
+++ b/env/Lib/site-packages/idna-3.10.dist-info/LICENSE.md
@@ -0,0 +1,31 @@
+BSD 3-Clause License
+
+Copyright (c) 2013-2024, Kim Davies and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/env/Lib/site-packages/idna-3.10.dist-info/METADATA b/env/Lib/site-packages/idna-3.10.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..c42623e9423c23b555d9d352bc5dab518ede02c2
--- /dev/null
+++ b/env/Lib/site-packages/idna-3.10.dist-info/METADATA
@@ -0,0 +1,250 @@
+Metadata-Version: 2.1
+Name: idna
+Version: 3.10
+Summary: Internationalized Domain Names in Applications (IDNA)
+Author-email: Kim Davies
+Requires-Python: >=3.6
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Internet :: Name Service (DNS)
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Utilities
+Requires-Dist: ruff >= 0.6.2 ; extra == "all"
+Requires-Dist: mypy >= 1.11.2 ; extra == "all"
+Requires-Dist: pytest >= 8.3.2 ; extra == "all"
+Requires-Dist: flake8 >= 7.1.1 ; extra == "all"
+Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst
+Project-URL: Issue tracker, https://github.com/kjd/idna/issues
+Project-URL: Source, https://github.com/kjd/idna
+Provides-Extra: all
+
+Internationalized Domain Names in Applications (IDNA)
+=====================================================
+
+Support for the Internationalized Domain Names in
+Applications (IDNA) protocol as specified in `RFC 5891
+`_. This is the latest version of
+the protocol and is sometimes referred to as “IDNA 2008”.
+
+This library also provides support for Unicode Technical
+Standard 46, `Unicode IDNA Compatibility Processing
+`_.
+
+This acts as a suitable replacement for the “encodings.idna”
+module that comes with the Python standard library, but which
+only supports the older superseded IDNA specification (`RFC 3490
+`_).
+
+Basic functions are simply executed:
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('ドメイン.テスト')
+ b'xn--eckwd4c7c.xn--zckzah'
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
+ ドメイン.テスト
+
+
+Installation
+------------
+
+This package is available for installation from PyPI:
+
+.. code-block:: bash
+
+ $ python3 -m pip install idna
+
+
+Usage
+-----
+
+For typical usage, the ``encode`` and ``decode`` functions will take a
+domain name argument and perform a conversion to A-labels or U-labels
+respectively.
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('ドメイン.テスト')
+ b'xn--eckwd4c7c.xn--zckzah'
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
+ ドメイン.テスト
+
+You may use the codec encoding and decoding methods using the
+``idna.codec`` module:
+
+.. code-block:: pycon
+
+ >>> import idna.codec
+ >>> print('домен.испытание'.encode('idna2008'))
+ b'xn--d1acufc.xn--80akhbyknj4f'
+ >>> print(b'xn--d1acufc.xn--80akhbyknj4f'.decode('idna2008'))
+ домен.испытание
+
+Conversions can be applied at a per-label basis using the ``ulabel`` or
+``alabel`` functions if necessary:
+
+.. code-block:: pycon
+
+ >>> idna.alabel('测试')
+ b'xn--0zwm56d'
+
+Compatibility Mapping (UTS #46)
++++++++++++++++++++++++++++++++
+
+As described in `RFC 5895 `_, the
+IDNA specification does not normalize input from different potential
+ways a user may input a domain name. This functionality, known as
+a “mapping”, is considered by the specification to be a local
+user-interface issue distinct from IDNA conversion functionality.
+
+This library provides one such mapping that was developed by the
+Unicode Consortium. Known as `Unicode IDNA Compatibility Processing
+`_, it provides for both a regular
+mapping for typical applications, as well as a transitional mapping to
+help migrate from older IDNA 2003 applications. Strings are
+preprocessed according to Section 4.4 “Preprocessing for IDNA2008”
+prior to the IDNA operations.
+
+For example, “Königsgäßchen” is not a permissible label as *LATIN
+CAPITAL LETTER K* is not allowed (nor are capital letters in general).
+UTS 46 will convert this into lower case prior to applying the IDNA
+conversion.
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('Königsgäßchen')
+ ...
+ idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed
+ >>> idna.encode('Königsgäßchen', uts46=True)
+ b'xn--knigsgchen-b4a3dun'
+ >>> print(idna.decode('xn--knigsgchen-b4a3dun'))
+ königsgäßchen
+
+Transitional processing provides conversions to help transition from
+the older 2003 standard to the current standard. For example, in the
+original IDNA specification, the *LATIN SMALL LETTER SHARP S* (ß) was
+converted into two *LATIN SMALL LETTER S* (ss), whereas in the current
+IDNA specification this conversion is not performed.
+
+.. code-block:: pycon
+
+ >>> idna.encode('Königsgäßchen', uts46=True, transitional=True)
+ 'xn--knigsgsschen-lcb0w'
+
+Implementers should use transitional processing with caution, only in
+rare cases where conversion from legacy labels to current labels must be
+performed (i.e. IDNA implementations that pre-date 2008). For typical
+applications that just need to convert labels, transitional processing
+is unlikely to be beneficial and could produce unexpected incompatible
+results.
+
+``encodings.idna`` Compatibility
+++++++++++++++++++++++++++++++++
+
+Function calls from the Python built-in ``encodings.idna`` module are
+mapped to their IDNA 2008 equivalents using the ``idna.compat`` module.
+Simply substitute the ``import`` clause in your code to refer to the new
+module name.
+
+Exceptions
+----------
+
+All errors raised during the conversion following the specification
+should raise an exception derived from the ``idna.IDNAError`` base
+class.
+
+More specific exceptions that may be generated as ``idna.IDNABidiError``
+when the error reflects an illegal combination of left-to-right and
+right-to-left characters in a label; ``idna.InvalidCodepoint`` when
+a specific codepoint is an illegal character in an IDN label (i.e.
+INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is
+illegal based on its positional context (i.e. it is CONTEXTO or CONTEXTJ
+but the contextual requirements are not satisfied.)
+
+Building and Diagnostics
+------------------------
+
+The IDNA and UTS 46 functionality relies upon pre-calculated lookup
+tables for performance. These tables are derived from computing against
+eligibility criteria in the respective standards. These tables are
+computed using the command-line script ``tools/idna-data``.
+
+This tool will fetch relevant codepoint data from the Unicode repository
+and perform the required calculations to identify eligibility. There are
+three main modes:
+
+* ``idna-data make-libdata``. Generates ``idnadata.py`` and
+ ``uts46data.py``, the pre-calculated lookup tables used for IDNA and
+ UTS 46 conversions. Implementers who wish to track this library against
+ a different Unicode version may use this tool to manually generate a
+ different version of the ``idnadata.py`` and ``uts46data.py`` files.
+
+* ``idna-data make-table``. Generate a table of the IDNA disposition
+ (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix
+ B.1 of RFC 5892 and the pre-computed tables published by `IANA
+ `_.
+
+* ``idna-data U+0061``. Prints debugging output on the various
+ properties associated with an individual Unicode codepoint (in this
+ case, U+0061), that are used to assess the IDNA and UTS 46 status of a
+ codepoint. This is helpful in debugging or analysis.
+
+The tool accepts a number of arguments, described using ``idna-data
+-h``. Most notably, the ``--version`` argument allows the specification
+of the version of Unicode to be used in computing the table data. For
+example, ``idna-data --version 9.0.0 make-libdata`` will generate
+library data against Unicode 9.0.0.
+
+
+Additional Notes
+----------------
+
+* **Packages**. The latest tagged release version is published in the
+ `Python Package Index `_.
+
+* **Version support**. This library supports Python 3.6 and higher.
+ As this library serves as a low-level toolkit for a variety of
+ applications, many of which strive for broad compatibility with older
+ Python versions, there is no rush to remove older interpreter support.
+ Removing support for older versions should be well justified in that the
+ maintenance burden has become too high.
+
+* **Python 2**. Python 2 is supported by version 2.x of this library.
+ Use "idna<3" in your requirements file if you need this library for
+ a Python 2 application. Be advised that these versions are no longer
+ actively developed.
+
+* **Testing**. The library has a test suite based on each rule of the
+ IDNA specification, as well as tests that are provided as part of the
+ Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing
+ `_.
+
+* **Emoji**. It is an occasional request to support emoji domains in
+ this library. Encoding of symbols like emoji is expressly prohibited by
+ the technical standard IDNA 2008 and emoji domains are broadly phased
+ out across the domain industry due to associated security risks. For
+ now, applications that need to support these non-compliant labels
+ may wish to consider trying the encode/decode operation in this library
+ first, and then falling back to using `encodings.idna`. See `the Github
+ project `_ for more discussion.
+
diff --git a/env/Lib/site-packages/idna-3.10.dist-info/RECORD b/env/Lib/site-packages/idna-3.10.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..9cfce7f9f4c1a64d85642f865f9d0a3d138a82c6
--- /dev/null
+++ b/env/Lib/site-packages/idna-3.10.dist-info/RECORD
@@ -0,0 +1,22 @@
+idna-3.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+idna-3.10.dist-info/LICENSE.md,sha256=pZ8LDvNjWHQQmkRhykT_enDVBpboFHZ7-vch1Mmw2w8,1541
+idna-3.10.dist-info/METADATA,sha256=URR5ZyDfQ1PCEGhkYoojqfi2Ra0tau2--lhwG4XSfjI,10158
+idna-3.10.dist-info/RECORD,,
+idna-3.10.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868
+idna/__pycache__/__init__.cpython-312.pyc,,
+idna/__pycache__/codec.cpython-312.pyc,,
+idna/__pycache__/compat.cpython-312.pyc,,
+idna/__pycache__/core.cpython-312.pyc,,
+idna/__pycache__/idnadata.cpython-312.pyc,,
+idna/__pycache__/intranges.cpython-312.pyc,,
+idna/__pycache__/package_data.cpython-312.pyc,,
+idna/__pycache__/uts46data.cpython-312.pyc,,
+idna/codec.py,sha256=PEew3ItwzjW4hymbasnty2N2OXvNcgHB-JjrBuxHPYY,3422
+idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316
+idna/core.py,sha256=YJYyAMnwiQEPjVC4-Fqu_p4CJ6yKKuDGmppBNQNQpFs,13239
+idna/idnadata.py,sha256=W30GcIGvtOWYwAjZj4ZjuouUutC6ffgNuyjJy7fZ-lo,78306
+idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898
+idna/package_data.py,sha256=q59S3OXsc5VI8j6vSD0sGBMyk6zZ4vWFREE88yCJYKs,21
+idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+idna/uts46data.py,sha256=rt90K9J40gUSwppDPCrhjgi5AA6pWM65dEGRSf6rIhM,239289
diff --git a/env/Lib/site-packages/idna-3.10.dist-info/WHEEL b/env/Lib/site-packages/idna-3.10.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..3b5e64b5e6c4a210201d1676a891fd57b15cda99
--- /dev/null
+++ b/env/Lib/site-packages/idna-3.10.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/env/Lib/site-packages/idna/__init__.py b/env/Lib/site-packages/idna/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfdc030a751b089fc7e38fc88093b791605d501d
--- /dev/null
+++ b/env/Lib/site-packages/idna/__init__.py
@@ -0,0 +1,45 @@
+from .core import (
+ IDNABidiError,
+ IDNAError,
+ InvalidCodepoint,
+ InvalidCodepointContext,
+ alabel,
+ check_bidi,
+ check_hyphen_ok,
+ check_initial_combiner,
+ check_label,
+ check_nfc,
+ decode,
+ encode,
+ ulabel,
+ uts46_remap,
+ valid_contextj,
+ valid_contexto,
+ valid_label_length,
+ valid_string_length,
+)
+from .intranges import intranges_contain
+from .package_data import __version__
+
+__all__ = [
+ "__version__",
+ "IDNABidiError",
+ "IDNAError",
+ "InvalidCodepoint",
+ "InvalidCodepointContext",
+ "alabel",
+ "check_bidi",
+ "check_hyphen_ok",
+ "check_initial_combiner",
+ "check_label",
+ "check_nfc",
+ "decode",
+ "encode",
+ "intranges_contain",
+ "ulabel",
+ "uts46_remap",
+ "valid_contextj",
+ "valid_contexto",
+ "valid_label_length",
+ "valid_string_length",
+]
diff --git a/env/Lib/site-packages/idna/codec.py b/env/Lib/site-packages/idna/codec.py
new file mode 100644
index 0000000000000000000000000000000000000000..913abfd6a23ce547f84de2adc41221012f1007d6
--- /dev/null
+++ b/env/Lib/site-packages/idna/codec.py
@@ -0,0 +1,122 @@
+import codecs
+import re
+from typing import Any, Optional, Tuple
+
+from .core import IDNAError, alabel, decode, encode, ulabel
+
+_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]")
+
+
+class Codec(codecs.Codec):
+ def encode(self, data: str, errors: str = "strict") -> Tuple[bytes, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return b"", 0
+
+ return encode(data), len(data)
+
+ def decode(self, data: bytes, errors: str = "strict") -> Tuple[str, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return "", 0
+
+ return decode(data), len(data)
+
+
+class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
+ def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return b"", 0
+
+ labels = _unicode_dots_re.split(data)
+ trailing_dot = b""
+ if labels:
+ if not labels[-1]:
+ trailing_dot = b"."
+ del labels[-1]
+ elif not final:
+ # Keep potentially unfinished label until the next call
+ del labels[-1]
+ if labels:
+ trailing_dot = b"."
+
+ result = []
+ size = 0
+ for label in labels:
+ result.append(alabel(label))
+ if size:
+ size += 1
+ size += len(label)
+
+ # Join with U+002E
+ result_bytes = b".".join(result) + trailing_dot
+ size += len(trailing_dot)
+ return result_bytes, size
+
+
+class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
+ def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return ("", 0)
+
+ if not isinstance(data, str):
+ data = str(data, "ascii")
+
+ labels = _unicode_dots_re.split(data)
+ trailing_dot = ""
+ if labels:
+ if not labels[-1]:
+ trailing_dot = "."
+ del labels[-1]
+ elif not final:
+ # Keep potentially unfinished label until the next call
+ del labels[-1]
+ if labels:
+ trailing_dot = "."
+
+ result = []
+ size = 0
+ for label in labels:
+ result.append(ulabel(label))
+ if size:
+ size += 1
+ size += len(label)
+
+ result_str = ".".join(result) + trailing_dot
+ size += len(trailing_dot)
+ return (result_str, size)
+
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ pass
+
+
+class StreamReader(Codec, codecs.StreamReader):
+ pass
+
+
+def search_function(name: str) -> Optional[codecs.CodecInfo]:
+ if name != "idna2008":
+ return None
+ return codecs.CodecInfo(
+ name=name,
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
+
+
+codecs.register(search_function)
diff --git a/env/Lib/site-packages/idna/compat.py b/env/Lib/site-packages/idna/compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..1df9f2a70e6815908f2784e88897a9a359eef84c
--- /dev/null
+++ b/env/Lib/site-packages/idna/compat.py
@@ -0,0 +1,15 @@
+from typing import Any, Union
+
+from .core import decode, encode
+
+
+def ToASCII(label: str) -> bytes:
+ return encode(label)
+
+
+def ToUnicode(label: Union[bytes, bytearray]) -> str:
+ return decode(label)
+
+
+def nameprep(s: Any) -> None:
+ raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol")
diff --git a/env/Lib/site-packages/idna/core.py b/env/Lib/site-packages/idna/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..9115f123f0274832af5ba1cf3c5481cc5353eecd
--- /dev/null
+++ b/env/Lib/site-packages/idna/core.py
@@ -0,0 +1,437 @@
+import bisect
+import re
+import unicodedata
+from typing import Optional, Union
+
+from . import idnadata
+from .intranges import intranges_contain
+
+_virama_combining_class = 9
+_alabel_prefix = b"xn--"
+_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]")
+
+
+class IDNAError(UnicodeError):
+ """Base exception for all IDNA-encoding related problems"""
+
+ pass
+
+
+class IDNABidiError(IDNAError):
+ """Exception when bidirectional requirements are not satisfied"""
+
+ pass
+
+
+class InvalidCodepoint(IDNAError):
+ """Exception when a disallowed or unallocated codepoint is used"""
+
+ pass
+
+
+class InvalidCodepointContext(IDNAError):
+ """Exception when the codepoint is not valid in the context it is used"""
+
+ pass
+
+
+def _combining_class(cp: int) -> int:
+ v = unicodedata.combining(chr(cp))
+ if v == 0:
+ if not unicodedata.name(chr(cp)):
+ raise ValueError("Unknown character in unicodedata")
+ return v
+
+
+def _is_script(cp: str, script: str) -> bool:
+ return intranges_contain(ord(cp), idnadata.scripts[script])
+
+
+def _punycode(s: str) -> bytes:
+ return s.encode("punycode")
+
+
+def _unot(s: int) -> str:
+ return "U+{:04X}".format(s)
+
+
+def valid_label_length(label: Union[bytes, str]) -> bool:
+ if len(label) > 63:
+ return False
+ return True
+
+
+def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool:
+ if len(label) > (254 if trailing_dot else 253):
+ return False
+ return True
+
+
+def check_bidi(label: str, check_ltr: bool = False) -> bool:
+ # Bidi rules should only be applied if string contains RTL characters
+ bidi_label = False
+ for idx, cp in enumerate(label, 1):
+ direction = unicodedata.bidirectional(cp)
+ if direction == "":
+ # String likely comes from a newer version of Unicode
+ raise IDNABidiError("Unknown directionality in label {} at position {}".format(repr(label), idx))
+ if direction in ["R", "AL", "AN"]:
+ bidi_label = True
+ if not bidi_label and not check_ltr:
+ return True
+
+ # Bidi rule 1
+ direction = unicodedata.bidirectional(label[0])
+ if direction in ["R", "AL"]:
+ rtl = True
+ elif direction == "L":
+ rtl = False
+ else:
+ raise IDNABidiError("First codepoint in label {} must be directionality L, R or AL".format(repr(label)))
+
+ valid_ending = False
+ number_type: Optional[str] = None
+ for idx, cp in enumerate(label, 1):
+ direction = unicodedata.bidirectional(cp)
+
+ if rtl:
+ # Bidi rule 2
+ if direction not in [
+ "R",
+ "AL",
+ "AN",
+ "EN",
+ "ES",
+ "CS",
+ "ET",
+ "ON",
+ "BN",
+ "NSM",
+ ]:
+ raise IDNABidiError("Invalid direction for codepoint at position {} in a right-to-left label".format(idx))
+ # Bidi rule 3
+ if direction in ["R", "AL", "EN", "AN"]:
+ valid_ending = True
+ elif direction != "NSM":
+ valid_ending = False
+ # Bidi rule 4
+ if direction in ["AN", "EN"]:
+ if not number_type:
+ number_type = direction
+ else:
+ if number_type != direction:
+ raise IDNABidiError("Can not mix numeral types in a right-to-left label")
+ else:
+ # Bidi rule 5
+ if direction not in ["L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"]:
+ raise IDNABidiError("Invalid direction for codepoint at position {} in a left-to-right label".format(idx))
+ # Bidi rule 6
+ if direction in ["L", "EN"]:
+ valid_ending = True
+ elif direction != "NSM":
+ valid_ending = False
+
+ if not valid_ending:
+ raise IDNABidiError("Label ends with illegal codepoint directionality")
+
+ return True
+
+
+def check_initial_combiner(label: str) -> bool:
+ if unicodedata.category(label[0])[0] == "M":
+ raise IDNAError("Label begins with an illegal combining character")
+ return True
+
+
+def check_hyphen_ok(label: str) -> bool:
+ if label[2:4] == "--":
+ raise IDNAError("Label has disallowed hyphens in 3rd and 4th position")
+ if label[0] == "-" or label[-1] == "-":
+ raise IDNAError("Label must not start or end with a hyphen")
+ return True
+
+
+def check_nfc(label: str) -> None:
+ if unicodedata.normalize("NFC", label) != label:
+ raise IDNAError("Label must be in Normalization Form C")
+
+
+def valid_contextj(label: str, pos: int) -> bool:
+ cp_value = ord(label[pos])
+
+ if cp_value == 0x200C:
+ if pos > 0:
+ if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
+ return True
+
+ ok = False
+ for i in range(pos - 1, -1, -1):
+ joining_type = idnadata.joining_types.get(ord(label[i]))
+ if joining_type == ord("T"):
+ continue
+ elif joining_type in [ord("L"), ord("D")]:
+ ok = True
+ break
+ else:
+ break
+
+ if not ok:
+ return False
+
+ ok = False
+ for i in range(pos + 1, len(label)):
+ joining_type = idnadata.joining_types.get(ord(label[i]))
+ if joining_type == ord("T"):
+ continue
+ elif joining_type in [ord("R"), ord("D")]:
+ ok = True
+ break
+ else:
+ break
+ return ok
+
+ if cp_value == 0x200D:
+ if pos > 0:
+ if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
+ return True
+ return False
+
+ else:
+ return False
+
+
+def valid_contexto(label: str, pos: int, exception: bool = False) -> bool:
+ cp_value = ord(label[pos])
+
+ if cp_value == 0x00B7:
+ if 0 < pos < len(label) - 1:
+ if ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C:
+ return True
+ return False
+
+ elif cp_value == 0x0375:
+ if pos < len(label) - 1 and len(label) > 1:
+ return _is_script(label[pos + 1], "Greek")
+ return False
+
+ elif cp_value == 0x05F3 or cp_value == 0x05F4:
+ if pos > 0:
+ return _is_script(label[pos - 1], "Hebrew")
+ return False
+
+ elif cp_value == 0x30FB:
+ for cp in label:
+ if cp == "\u30fb":
+ continue
+ if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"):
+ return True
+ return False
+
+ elif 0x660 <= cp_value <= 0x669:
+ for cp in label:
+ if 0x6F0 <= ord(cp) <= 0x06F9:
+ return False
+ return True
+
+ elif 0x6F0 <= cp_value <= 0x6F9:
+ for cp in label:
+ if 0x660 <= ord(cp) <= 0x0669:
+ return False
+ return True
+
+ return False
+
+
+def check_label(label: Union[str, bytes, bytearray]) -> None:
+ if isinstance(label, (bytes, bytearray)):
+ label = label.decode("utf-8")
+ if len(label) == 0:
+ raise IDNAError("Empty Label")
+
+ check_nfc(label)
+ check_hyphen_ok(label)
+ check_initial_combiner(label)
+
+ for pos, cp in enumerate(label):
+ cp_value = ord(cp)
+ if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]):
+ continue
+ elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]):
+ try:
+ if not valid_contextj(label, pos):
+ raise InvalidCodepointContext(
+ "Joiner {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label))
+ )
+ except ValueError:
+ raise IDNAError(
+ "Unknown codepoint adjacent to joiner {} at position {} in {}".format(
+ _unot(cp_value), pos + 1, repr(label)
+ )
+ )
+ elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]):
+ if not valid_contexto(label, pos):
+ raise InvalidCodepointContext(
+ "Codepoint {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label))
+ )
+ else:
+ raise InvalidCodepoint(
+ "Codepoint {} at position {} of {} not allowed".format(_unot(cp_value), pos + 1, repr(label))
+ )
+
+ check_bidi(label)
+
+
+def alabel(label: str) -> bytes:
+ try:
+ label_bytes = label.encode("ascii")
+ ulabel(label_bytes)
+ if not valid_label_length(label_bytes):
+ raise IDNAError("Label too long")
+ return label_bytes
+ except UnicodeEncodeError:
+ pass
+
+ check_label(label)
+ label_bytes = _alabel_prefix + _punycode(label)
+
+ if not valid_label_length(label_bytes):
+ raise IDNAError("Label too long")
+
+ return label_bytes
+
+
+def ulabel(label: Union[str, bytes, bytearray]) -> str:
+ if not isinstance(label, (bytes, bytearray)):
+ try:
+ label_bytes = label.encode("ascii")
+ except UnicodeEncodeError:
+ check_label(label)
+ return label
+ else:
+ label_bytes = label
+
+ label_bytes = label_bytes.lower()
+ if label_bytes.startswith(_alabel_prefix):
+ label_bytes = label_bytes[len(_alabel_prefix) :]
+ if not label_bytes:
+ raise IDNAError("Malformed A-label, no Punycode eligible content found")
+ if label_bytes.decode("ascii")[-1] == "-":
+ raise IDNAError("A-label must not end with a hyphen")
+ else:
+ check_label(label_bytes)
+ return label_bytes.decode("ascii")
+
+ try:
+ label = label_bytes.decode("punycode")
+ except UnicodeError:
+ raise IDNAError("Invalid A-label")
+ check_label(label)
+ return label
+
+
+def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:
+ """Re-map the characters in the string according to UTS46 processing."""
+ from .uts46data import uts46data
+
+ output = ""
+
+ for pos, char in enumerate(domain):
+ code_point = ord(char)
+ try:
+ uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1]
+ status = uts46row[1]
+ replacement: Optional[str] = None
+ if len(uts46row) == 3:
+ replacement = uts46row[2]
+ if (
+ status == "V"
+ or (status == "D" and not transitional)
+ or (status == "3" and not std3_rules and replacement is None)
+ ):
+ output += char
+ elif replacement is not None and (
+ status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional)
+ ):
+ output += replacement
+ elif status != "I":
+ raise IndexError()
+ except IndexError:
+ raise InvalidCodepoint(
+ "Codepoint {} not allowed at position {} in {}".format(_unot(code_point), pos + 1, repr(domain))
+ )
+
+ return unicodedata.normalize("NFC", output)
+
+
+def encode(
+ s: Union[str, bytes, bytearray],
+ strict: bool = False,
+ uts46: bool = False,
+ std3_rules: bool = False,
+ transitional: bool = False,
+) -> bytes:
+ if not isinstance(s, str):
+ try:
+ s = str(s, "ascii")
+ except UnicodeDecodeError:
+ raise IDNAError("should pass a unicode string to the function rather than a byte string.")
+ if uts46:
+ s = uts46_remap(s, std3_rules, transitional)
+ trailing_dot = False
+ result = []
+ if strict:
+ labels = s.split(".")
+ else:
+ labels = _unicode_dots_re.split(s)
+ if not labels or labels == [""]:
+ raise IDNAError("Empty domain")
+ if labels[-1] == "":
+ del labels[-1]
+ trailing_dot = True
+ for label in labels:
+ s = alabel(label)
+ if s:
+ result.append(s)
+ else:
+ raise IDNAError("Empty label")
+ if trailing_dot:
+ result.append(b"")
+ s = b".".join(result)
+ if not valid_string_length(s, trailing_dot):
+ raise IDNAError("Domain too long")
+ return s
+
+
+def decode(
+ s: Union[str, bytes, bytearray],
+ strict: bool = False,
+ uts46: bool = False,
+ std3_rules: bool = False,
+) -> str:
+ try:
+ if not isinstance(s, str):
+ s = str(s, "ascii")
+ except UnicodeDecodeError:
+ raise IDNAError("Invalid ASCII in A-label")
+ if uts46:
+ s = uts46_remap(s, std3_rules, False)
+ trailing_dot = False
+ result = []
+ if not strict:
+ labels = _unicode_dots_re.split(s)
+ else:
+ labels = s.split(".")
+ if not labels or labels == [""]:
+ raise IDNAError("Empty domain")
+ if not labels[-1]:
+ del labels[-1]
+ trailing_dot = True
+ for label in labels:
+ s = ulabel(label)
+ if s:
+ result.append(s)
+ else:
+ raise IDNAError("Empty label")
+ if trailing_dot:
+ result.append("")
+ return ".".join(result)
diff --git a/env/Lib/site-packages/idna/idnadata.py b/env/Lib/site-packages/idna/idnadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..4be6004622efcdc36a8d15efc0ac3e138a4bae02
--- /dev/null
+++ b/env/Lib/site-packages/idna/idnadata.py
@@ -0,0 +1,4243 @@
+# This file is automatically generated by tools/idna-data
+
+__version__ = "15.1.0"
+scripts = {
+ "Greek": (
+ 0x37000000374,
+ 0x37500000378,
+ 0x37A0000037E,
+ 0x37F00000380,
+ 0x38400000385,
+ 0x38600000387,
+ 0x3880000038B,
+ 0x38C0000038D,
+ 0x38E000003A2,
+ 0x3A3000003E2,
+ 0x3F000000400,
+ 0x1D2600001D2B,
+ 0x1D5D00001D62,
+ 0x1D6600001D6B,
+ 0x1DBF00001DC0,
+ 0x1F0000001F16,
+ 0x1F1800001F1E,
+ 0x1F2000001F46,
+ 0x1F4800001F4E,
+ 0x1F5000001F58,
+ 0x1F5900001F5A,
+ 0x1F5B00001F5C,
+ 0x1F5D00001F5E,
+ 0x1F5F00001F7E,
+ 0x1F8000001FB5,
+ 0x1FB600001FC5,
+ 0x1FC600001FD4,
+ 0x1FD600001FDC,
+ 0x1FDD00001FF0,
+ 0x1FF200001FF5,
+ 0x1FF600001FFF,
+ 0x212600002127,
+ 0xAB650000AB66,
+ 0x101400001018F,
+ 0x101A0000101A1,
+ 0x1D2000001D246,
+ ),
+ "Han": (
+ 0x2E8000002E9A,
+ 0x2E9B00002EF4,
+ 0x2F0000002FD6,
+ 0x300500003006,
+ 0x300700003008,
+ 0x30210000302A,
+ 0x30380000303C,
+ 0x340000004DC0,
+ 0x4E000000A000,
+ 0xF9000000FA6E,
+ 0xFA700000FADA,
+ 0x16FE200016FE4,
+ 0x16FF000016FF2,
+ 0x200000002A6E0,
+ 0x2A7000002B73A,
+ 0x2B7400002B81E,
+ 0x2B8200002CEA2,
+ 0x2CEB00002EBE1,
+ 0x2EBF00002EE5E,
+ 0x2F8000002FA1E,
+ 0x300000003134B,
+ 0x31350000323B0,
+ ),
+ "Hebrew": (
+ 0x591000005C8,
+ 0x5D0000005EB,
+ 0x5EF000005F5,
+ 0xFB1D0000FB37,
+ 0xFB380000FB3D,
+ 0xFB3E0000FB3F,
+ 0xFB400000FB42,
+ 0xFB430000FB45,
+ 0xFB460000FB50,
+ ),
+ "Hiragana": (
+ 0x304100003097,
+ 0x309D000030A0,
+ 0x1B0010001B120,
+ 0x1B1320001B133,
+ 0x1B1500001B153,
+ 0x1F2000001F201,
+ ),
+ "Katakana": (
+ 0x30A1000030FB,
+ 0x30FD00003100,
+ 0x31F000003200,
+ 0x32D0000032FF,
+ 0x330000003358,
+ 0xFF660000FF70,
+ 0xFF710000FF9E,
+ 0x1AFF00001AFF4,
+ 0x1AFF50001AFFC,
+ 0x1AFFD0001AFFF,
+ 0x1B0000001B001,
+ 0x1B1200001B123,
+ 0x1B1550001B156,
+ 0x1B1640001B168,
+ ),
+}
+joining_types = {
+ 0xAD: 84,
+ 0x300: 84,
+ 0x301: 84,
+ 0x302: 84,
+ 0x303: 84,
+ 0x304: 84,
+ 0x305: 84,
+ 0x306: 84,
+ 0x307: 84,
+ 0x308: 84,
+ 0x309: 84,
+ 0x30A: 84,
+ 0x30B: 84,
+ 0x30C: 84,
+ 0x30D: 84,
+ 0x30E: 84,
+ 0x30F: 84,
+ 0x310: 84,
+ 0x311: 84,
+ 0x312: 84,
+ 0x313: 84,
+ 0x314: 84,
+ 0x315: 84,
+ 0x316: 84,
+ 0x317: 84,
+ 0x318: 84,
+ 0x319: 84,
+ 0x31A: 84,
+ 0x31B: 84,
+ 0x31C: 84,
+ 0x31D: 84,
+ 0x31E: 84,
+ 0x31F: 84,
+ 0x320: 84,
+ 0x321: 84,
+ 0x322: 84,
+ 0x323: 84,
+ 0x324: 84,
+ 0x325: 84,
+ 0x326: 84,
+ 0x327: 84,
+ 0x328: 84,
+ 0x329: 84,
+ 0x32A: 84,
+ 0x32B: 84,
+ 0x32C: 84,
+ 0x32D: 84,
+ 0x32E: 84,
+ 0x32F: 84,
+ 0x330: 84,
+ 0x331: 84,
+ 0x332: 84,
+ 0x333: 84,
+ 0x334: 84,
+ 0x335: 84,
+ 0x336: 84,
+ 0x337: 84,
+ 0x338: 84,
+ 0x339: 84,
+ 0x33A: 84,
+ 0x33B: 84,
+ 0x33C: 84,
+ 0x33D: 84,
+ 0x33E: 84,
+ 0x33F: 84,
+ 0x340: 84,
+ 0x341: 84,
+ 0x342: 84,
+ 0x343: 84,
+ 0x344: 84,
+ 0x345: 84,
+ 0x346: 84,
+ 0x347: 84,
+ 0x348: 84,
+ 0x349: 84,
+ 0x34A: 84,
+ 0x34B: 84,
+ 0x34C: 84,
+ 0x34D: 84,
+ 0x34E: 84,
+ 0x34F: 84,
+ 0x350: 84,
+ 0x351: 84,
+ 0x352: 84,
+ 0x353: 84,
+ 0x354: 84,
+ 0x355: 84,
+ 0x356: 84,
+ 0x357: 84,
+ 0x358: 84,
+ 0x359: 84,
+ 0x35A: 84,
+ 0x35B: 84,
+ 0x35C: 84,
+ 0x35D: 84,
+ 0x35E: 84,
+ 0x35F: 84,
+ 0x360: 84,
+ 0x361: 84,
+ 0x362: 84,
+ 0x363: 84,
+ 0x364: 84,
+ 0x365: 84,
+ 0x366: 84,
+ 0x367: 84,
+ 0x368: 84,
+ 0x369: 84,
+ 0x36A: 84,
+ 0x36B: 84,
+ 0x36C: 84,
+ 0x36D: 84,
+ 0x36E: 84,
+ 0x36F: 84,
+ 0x483: 84,
+ 0x484: 84,
+ 0x485: 84,
+ 0x486: 84,
+ 0x487: 84,
+ 0x488: 84,
+ 0x489: 84,
+ 0x591: 84,
+ 0x592: 84,
+ 0x593: 84,
+ 0x594: 84,
+ 0x595: 84,
+ 0x596: 84,
+ 0x597: 84,
+ 0x598: 84,
+ 0x599: 84,
+ 0x59A: 84,
+ 0x59B: 84,
+ 0x59C: 84,
+ 0x59D: 84,
+ 0x59E: 84,
+ 0x59F: 84,
+ 0x5A0: 84,
+ 0x5A1: 84,
+ 0x5A2: 84,
+ 0x5A3: 84,
+ 0x5A4: 84,
+ 0x5A5: 84,
+ 0x5A6: 84,
+ 0x5A7: 84,
+ 0x5A8: 84,
+ 0x5A9: 84,
+ 0x5AA: 84,
+ 0x5AB: 84,
+ 0x5AC: 84,
+ 0x5AD: 84,
+ 0x5AE: 84,
+ 0x5AF: 84,
+ 0x5B0: 84,
+ 0x5B1: 84,
+ 0x5B2: 84,
+ 0x5B3: 84,
+ 0x5B4: 84,
+ 0x5B5: 84,
+ 0x5B6: 84,
+ 0x5B7: 84,
+ 0x5B8: 84,
+ 0x5B9: 84,
+ 0x5BA: 84,
+ 0x5BB: 84,
+ 0x5BC: 84,
+ 0x5BD: 84,
+ 0x5BF: 84,
+ 0x5C1: 84,
+ 0x5C2: 84,
+ 0x5C4: 84,
+ 0x5C5: 84,
+ 0x5C7: 84,
+ 0x610: 84,
+ 0x611: 84,
+ 0x612: 84,
+ 0x613: 84,
+ 0x614: 84,
+ 0x615: 84,
+ 0x616: 84,
+ 0x617: 84,
+ 0x618: 84,
+ 0x619: 84,
+ 0x61A: 84,
+ 0x61C: 84,
+ 0x620: 68,
+ 0x622: 82,
+ 0x623: 82,
+ 0x624: 82,
+ 0x625: 82,
+ 0x626: 68,
+ 0x627: 82,
+ 0x628: 68,
+ 0x629: 82,
+ 0x62A: 68,
+ 0x62B: 68,
+ 0x62C: 68,
+ 0x62D: 68,
+ 0x62E: 68,
+ 0x62F: 82,
+ 0x630: 82,
+ 0x631: 82,
+ 0x632: 82,
+ 0x633: 68,
+ 0x634: 68,
+ 0x635: 68,
+ 0x636: 68,
+ 0x637: 68,
+ 0x638: 68,
+ 0x639: 68,
+ 0x63A: 68,
+ 0x63B: 68,
+ 0x63C: 68,
+ 0x63D: 68,
+ 0x63E: 68,
+ 0x63F: 68,
+ 0x640: 67,
+ 0x641: 68,
+ 0x642: 68,
+ 0x643: 68,
+ 0x644: 68,
+ 0x645: 68,
+ 0x646: 68,
+ 0x647: 68,
+ 0x648: 82,
+ 0x649: 68,
+ 0x64A: 68,
+ 0x64B: 84,
+ 0x64C: 84,
+ 0x64D: 84,
+ 0x64E: 84,
+ 0x64F: 84,
+ 0x650: 84,
+ 0x651: 84,
+ 0x652: 84,
+ 0x653: 84,
+ 0x654: 84,
+ 0x655: 84,
+ 0x656: 84,
+ 0x657: 84,
+ 0x658: 84,
+ 0x659: 84,
+ 0x65A: 84,
+ 0x65B: 84,
+ 0x65C: 84,
+ 0x65D: 84,
+ 0x65E: 84,
+ 0x65F: 84,
+ 0x66E: 68,
+ 0x66F: 68,
+ 0x670: 84,
+ 0x671: 82,
+ 0x672: 82,
+ 0x673: 82,
+ 0x675: 82,
+ 0x676: 82,
+ 0x677: 82,
+ 0x678: 68,
+ 0x679: 68,
+ 0x67A: 68,
+ 0x67B: 68,
+ 0x67C: 68,
+ 0x67D: 68,
+ 0x67E: 68,
+ 0x67F: 68,
+ 0x680: 68,
+ 0x681: 68,
+ 0x682: 68,
+ 0x683: 68,
+ 0x684: 68,
+ 0x685: 68,
+ 0x686: 68,
+ 0x687: 68,
+ 0x688: 82,
+ 0x689: 82,
+ 0x68A: 82,
+ 0x68B: 82,
+ 0x68C: 82,
+ 0x68D: 82,
+ 0x68E: 82,
+ 0x68F: 82,
+ 0x690: 82,
+ 0x691: 82,
+ 0x692: 82,
+ 0x693: 82,
+ 0x694: 82,
+ 0x695: 82,
+ 0x696: 82,
+ 0x697: 82,
+ 0x698: 82,
+ 0x699: 82,
+ 0x69A: 68,
+ 0x69B: 68,
+ 0x69C: 68,
+ 0x69D: 68,
+ 0x69E: 68,
+ 0x69F: 68,
+ 0x6A0: 68,
+ 0x6A1: 68,
+ 0x6A2: 68,
+ 0x6A3: 68,
+ 0x6A4: 68,
+ 0x6A5: 68,
+ 0x6A6: 68,
+ 0x6A7: 68,
+ 0x6A8: 68,
+ 0x6A9: 68,
+ 0x6AA: 68,
+ 0x6AB: 68,
+ 0x6AC: 68,
+ 0x6AD: 68,
+ 0x6AE: 68,
+ 0x6AF: 68,
+ 0x6B0: 68,
+ 0x6B1: 68,
+ 0x6B2: 68,
+ 0x6B3: 68,
+ 0x6B4: 68,
+ 0x6B5: 68,
+ 0x6B6: 68,
+ 0x6B7: 68,
+ 0x6B8: 68,
+ 0x6B9: 68,
+ 0x6BA: 68,
+ 0x6BB: 68,
+ 0x6BC: 68,
+ 0x6BD: 68,
+ 0x6BE: 68,
+ 0x6BF: 68,
+ 0x6C0: 82,
+ 0x6C1: 68,
+ 0x6C2: 68,
+ 0x6C3: 82,
+ 0x6C4: 82,
+ 0x6C5: 82,
+ 0x6C6: 82,
+ 0x6C7: 82,
+ 0x6C8: 82,
+ 0x6C9: 82,
+ 0x6CA: 82,
+ 0x6CB: 82,
+ 0x6CC: 68,
+ 0x6CD: 82,
+ 0x6CE: 68,
+ 0x6CF: 82,
+ 0x6D0: 68,
+ 0x6D1: 68,
+ 0x6D2: 82,
+ 0x6D3: 82,
+ 0x6D5: 82,
+ 0x6D6: 84,
+ 0x6D7: 84,
+ 0x6D8: 84,
+ 0x6D9: 84,
+ 0x6DA: 84,
+ 0x6DB: 84,
+ 0x6DC: 84,
+ 0x6DF: 84,
+ 0x6E0: 84,
+ 0x6E1: 84,
+ 0x6E2: 84,
+ 0x6E3: 84,
+ 0x6E4: 84,
+ 0x6E7: 84,
+ 0x6E8: 84,
+ 0x6EA: 84,
+ 0x6EB: 84,
+ 0x6EC: 84,
+ 0x6ED: 84,
+ 0x6EE: 82,
+ 0x6EF: 82,
+ 0x6FA: 68,
+ 0x6FB: 68,
+ 0x6FC: 68,
+ 0x6FF: 68,
+ 0x70F: 84,
+ 0x710: 82,
+ 0x711: 84,
+ 0x712: 68,
+ 0x713: 68,
+ 0x714: 68,
+ 0x715: 82,
+ 0x716: 82,
+ 0x717: 82,
+ 0x718: 82,
+ 0x719: 82,
+ 0x71A: 68,
+ 0x71B: 68,
+ 0x71C: 68,
+ 0x71D: 68,
+ 0x71E: 82,
+ 0x71F: 68,
+ 0x720: 68,
+ 0x721: 68,
+ 0x722: 68,
+ 0x723: 68,
+ 0x724: 68,
+ 0x725: 68,
+ 0x726: 68,
+ 0x727: 68,
+ 0x728: 82,
+ 0x729: 68,
+ 0x72A: 82,
+ 0x72B: 68,
+ 0x72C: 82,
+ 0x72D: 68,
+ 0x72E: 68,
+ 0x72F: 82,
+ 0x730: 84,
+ 0x731: 84,
+ 0x732: 84,
+ 0x733: 84,
+ 0x734: 84,
+ 0x735: 84,
+ 0x736: 84,
+ 0x737: 84,
+ 0x738: 84,
+ 0x739: 84,
+ 0x73A: 84,
+ 0x73B: 84,
+ 0x73C: 84,
+ 0x73D: 84,
+ 0x73E: 84,
+ 0x73F: 84,
+ 0x740: 84,
+ 0x741: 84,
+ 0x742: 84,
+ 0x743: 84,
+ 0x744: 84,
+ 0x745: 84,
+ 0x746: 84,
+ 0x747: 84,
+ 0x748: 84,
+ 0x749: 84,
+ 0x74A: 84,
+ 0x74D: 82,
+ 0x74E: 68,
+ 0x74F: 68,
+ 0x750: 68,
+ 0x751: 68,
+ 0x752: 68,
+ 0x753: 68,
+ 0x754: 68,
+ 0x755: 68,
+ 0x756: 68,
+ 0x757: 68,
+ 0x758: 68,
+ 0x759: 82,
+ 0x75A: 82,
+ 0x75B: 82,
+ 0x75C: 68,
+ 0x75D: 68,
+ 0x75E: 68,
+ 0x75F: 68,
+ 0x760: 68,
+ 0x761: 68,
+ 0x762: 68,
+ 0x763: 68,
+ 0x764: 68,
+ 0x765: 68,
+ 0x766: 68,
+ 0x767: 68,
+ 0x768: 68,
+ 0x769: 68,
+ 0x76A: 68,
+ 0x76B: 82,
+ 0x76C: 82,
+ 0x76D: 68,
+ 0x76E: 68,
+ 0x76F: 68,
+ 0x770: 68,
+ 0x771: 82,
+ 0x772: 68,
+ 0x773: 82,
+ 0x774: 82,
+ 0x775: 68,
+ 0x776: 68,
+ 0x777: 68,
+ 0x778: 82,
+ 0x779: 82,
+ 0x77A: 68,
+ 0x77B: 68,
+ 0x77C: 68,
+ 0x77D: 68,
+ 0x77E: 68,
+ 0x77F: 68,
+ 0x7A6: 84,
+ 0x7A7: 84,
+ 0x7A8: 84,
+ 0x7A9: 84,
+ 0x7AA: 84,
+ 0x7AB: 84,
+ 0x7AC: 84,
+ 0x7AD: 84,
+ 0x7AE: 84,
+ 0x7AF: 84,
+ 0x7B0: 84,
+ 0x7CA: 68,
+ 0x7CB: 68,
+ 0x7CC: 68,
+ 0x7CD: 68,
+ 0x7CE: 68,
+ 0x7CF: 68,
+ 0x7D0: 68,
+ 0x7D1: 68,
+ 0x7D2: 68,
+ 0x7D3: 68,
+ 0x7D4: 68,
+ 0x7D5: 68,
+ 0x7D6: 68,
+ 0x7D7: 68,
+ 0x7D8: 68,
+ 0x7D9: 68,
+ 0x7DA: 68,
+ 0x7DB: 68,
+ 0x7DC: 68,
+ 0x7DD: 68,
+ 0x7DE: 68,
+ 0x7DF: 68,
+ 0x7E0: 68,
+ 0x7E1: 68,
+ 0x7E2: 68,
+ 0x7E3: 68,
+ 0x7E4: 68,
+ 0x7E5: 68,
+ 0x7E6: 68,
+ 0x7E7: 68,
+ 0x7E8: 68,
+ 0x7E9: 68,
+ 0x7EA: 68,
+ 0x7EB: 84,
+ 0x7EC: 84,
+ 0x7ED: 84,
+ 0x7EE: 84,
+ 0x7EF: 84,
+ 0x7F0: 84,
+ 0x7F1: 84,
+ 0x7F2: 84,
+ 0x7F3: 84,
+ 0x7FA: 67,
+ 0x7FD: 84,
+ 0x816: 84,
+ 0x817: 84,
+ 0x818: 84,
+ 0x819: 84,
+ 0x81B: 84,
+ 0x81C: 84,
+ 0x81D: 84,
+ 0x81E: 84,
+ 0x81F: 84,
+ 0x820: 84,
+ 0x821: 84,
+ 0x822: 84,
+ 0x823: 84,
+ 0x825: 84,
+ 0x826: 84,
+ 0x827: 84,
+ 0x829: 84,
+ 0x82A: 84,
+ 0x82B: 84,
+ 0x82C: 84,
+ 0x82D: 84,
+ 0x840: 82,
+ 0x841: 68,
+ 0x842: 68,
+ 0x843: 68,
+ 0x844: 68,
+ 0x845: 68,
+ 0x846: 82,
+ 0x847: 82,
+ 0x848: 68,
+ 0x849: 82,
+ 0x84A: 68,
+ 0x84B: 68,
+ 0x84C: 68,
+ 0x84D: 68,
+ 0x84E: 68,
+ 0x84F: 68,
+ 0x850: 68,
+ 0x851: 68,
+ 0x852: 68,
+ 0x853: 68,
+ 0x854: 82,
+ 0x855: 68,
+ 0x856: 82,
+ 0x857: 82,
+ 0x858: 82,
+ 0x859: 84,
+ 0x85A: 84,
+ 0x85B: 84,
+ 0x860: 68,
+ 0x862: 68,
+ 0x863: 68,
+ 0x864: 68,
+ 0x865: 68,
+ 0x867: 82,
+ 0x868: 68,
+ 0x869: 82,
+ 0x86A: 82,
+ 0x870: 82,
+ 0x871: 82,
+ 0x872: 82,
+ 0x873: 82,
+ 0x874: 82,
+ 0x875: 82,
+ 0x876: 82,
+ 0x877: 82,
+ 0x878: 82,
+ 0x879: 82,
+ 0x87A: 82,
+ 0x87B: 82,
+ 0x87C: 82,
+ 0x87D: 82,
+ 0x87E: 82,
+ 0x87F: 82,
+ 0x880: 82,
+ 0x881: 82,
+ 0x882: 82,
+ 0x883: 67,
+ 0x884: 67,
+ 0x885: 67,
+ 0x886: 68,
+ 0x889: 68,
+ 0x88A: 68,
+ 0x88B: 68,
+ 0x88C: 68,
+ 0x88D: 68,
+ 0x88E: 82,
+ 0x898: 84,
+ 0x899: 84,
+ 0x89A: 84,
+ 0x89B: 84,
+ 0x89C: 84,
+ 0x89D: 84,
+ 0x89E: 84,
+ 0x89F: 84,
+ 0x8A0: 68,
+ 0x8A1: 68,
+ 0x8A2: 68,
+ 0x8A3: 68,
+ 0x8A4: 68,
+ 0x8A5: 68,
+ 0x8A6: 68,
+ 0x8A7: 68,
+ 0x8A8: 68,
+ 0x8A9: 68,
+ 0x8AA: 82,
+ 0x8AB: 82,
+ 0x8AC: 82,
+ 0x8AE: 82,
+ 0x8AF: 68,
+ 0x8B0: 68,
+ 0x8B1: 82,
+ 0x8B2: 82,
+ 0x8B3: 68,
+ 0x8B4: 68,
+ 0x8B5: 68,
+ 0x8B6: 68,
+ 0x8B7: 68,
+ 0x8B8: 68,
+ 0x8B9: 82,
+ 0x8BA: 68,
+ 0x8BB: 68,
+ 0x8BC: 68,
+ 0x8BD: 68,
+ 0x8BE: 68,
+ 0x8BF: 68,
+ 0x8C0: 68,
+ 0x8C1: 68,
+ 0x8C2: 68,
+ 0x8C3: 68,
+ 0x8C4: 68,
+ 0x8C5: 68,
+ 0x8C6: 68,
+ 0x8C7: 68,
+ 0x8C8: 68,
+ 0x8CA: 84,
+ 0x8CB: 84,
+ 0x8CC: 84,
+ 0x8CD: 84,
+ 0x8CE: 84,
+ 0x8CF: 84,
+ 0x8D0: 84,
+ 0x8D1: 84,
+ 0x8D2: 84,
+ 0x8D3: 84,
+ 0x8D4: 84,
+ 0x8D5: 84,
+ 0x8D6: 84,
+ 0x8D7: 84,
+ 0x8D8: 84,
+ 0x8D9: 84,
+ 0x8DA: 84,
+ 0x8DB: 84,
+ 0x8DC: 84,
+ 0x8DD: 84,
+ 0x8DE: 84,
+ 0x8DF: 84,
+ 0x8E0: 84,
+ 0x8E1: 84,
+ 0x8E3: 84,
+ 0x8E4: 84,
+ 0x8E5: 84,
+ 0x8E6: 84,
+ 0x8E7: 84,
+ 0x8E8: 84,
+ 0x8E9: 84,
+ 0x8EA: 84,
+ 0x8EB: 84,
+ 0x8EC: 84,
+ 0x8ED: 84,
+ 0x8EE: 84,
+ 0x8EF: 84,
+ 0x8F0: 84,
+ 0x8F1: 84,
+ 0x8F2: 84,
+ 0x8F3: 84,
+ 0x8F4: 84,
+ 0x8F5: 84,
+ 0x8F6: 84,
+ 0x8F7: 84,
+ 0x8F8: 84,
+ 0x8F9: 84,
+ 0x8FA: 84,
+ 0x8FB: 84,
+ 0x8FC: 84,
+ 0x8FD: 84,
+ 0x8FE: 84,
+ 0x8FF: 84,
+ 0x900: 84,
+ 0x901: 84,
+ 0x902: 84,
+ 0x93A: 84,
+ 0x93C: 84,
+ 0x941: 84,
+ 0x942: 84,
+ 0x943: 84,
+ 0x944: 84,
+ 0x945: 84,
+ 0x946: 84,
+ 0x947: 84,
+ 0x948: 84,
+ 0x94D: 84,
+ 0x951: 84,
+ 0x952: 84,
+ 0x953: 84,
+ 0x954: 84,
+ 0x955: 84,
+ 0x956: 84,
+ 0x957: 84,
+ 0x962: 84,
+ 0x963: 84,
+ 0x981: 84,
+ 0x9BC: 84,
+ 0x9C1: 84,
+ 0x9C2: 84,
+ 0x9C3: 84,
+ 0x9C4: 84,
+ 0x9CD: 84,
+ 0x9E2: 84,
+ 0x9E3: 84,
+ 0x9FE: 84,
+ 0xA01: 84,
+ 0xA02: 84,
+ 0xA3C: 84,
+ 0xA41: 84,
+ 0xA42: 84,
+ 0xA47: 84,
+ 0xA48: 84,
+ 0xA4B: 84,
+ 0xA4C: 84,
+ 0xA4D: 84,
+ 0xA51: 84,
+ 0xA70: 84,
+ 0xA71: 84,
+ 0xA75: 84,
+ 0xA81: 84,
+ 0xA82: 84,
+ 0xABC: 84,
+ 0xAC1: 84,
+ 0xAC2: 84,
+ 0xAC3: 84,
+ 0xAC4: 84,
+ 0xAC5: 84,
+ 0xAC7: 84,
+ 0xAC8: 84,
+ 0xACD: 84,
+ 0xAE2: 84,
+ 0xAE3: 84,
+ 0xAFA: 84,
+ 0xAFB: 84,
+ 0xAFC: 84,
+ 0xAFD: 84,
+ 0xAFE: 84,
+ 0xAFF: 84,
+ 0xB01: 84,
+ 0xB3C: 84,
+ 0xB3F: 84,
+ 0xB41: 84,
+ 0xB42: 84,
+ 0xB43: 84,
+ 0xB44: 84,
+ 0xB4D: 84,
+ 0xB55: 84,
+ 0xB56: 84,
+ 0xB62: 84,
+ 0xB63: 84,
+ 0xB82: 84,
+ 0xBC0: 84,
+ 0xBCD: 84,
+ 0xC00: 84,
+ 0xC04: 84,
+ 0xC3C: 84,
+ 0xC3E: 84,
+ 0xC3F: 84,
+ 0xC40: 84,
+ 0xC46: 84,
+ 0xC47: 84,
+ 0xC48: 84,
+ 0xC4A: 84,
+ 0xC4B: 84,
+ 0xC4C: 84,
+ 0xC4D: 84,
+ 0xC55: 84,
+ 0xC56: 84,
+ 0xC62: 84,
+ 0xC63: 84,
+ 0xC81: 84,
+ 0xCBC: 84,
+ 0xCBF: 84,
+ 0xCC6: 84,
+ 0xCCC: 84,
+ 0xCCD: 84,
+ 0xCE2: 84,
+ 0xCE3: 84,
+ 0xD00: 84,
+ 0xD01: 84,
+ 0xD3B: 84,
+ 0xD3C: 84,
+ 0xD41: 84,
+ 0xD42: 84,
+ 0xD43: 84,
+ 0xD44: 84,
+ 0xD4D: 84,
+ 0xD62: 84,
+ 0xD63: 84,
+ 0xD81: 84,
+ 0xDCA: 84,
+ 0xDD2: 84,
+ 0xDD3: 84,
+ 0xDD4: 84,
+ 0xDD6: 84,
+ 0xE31: 84,
+ 0xE34: 84,
+ 0xE35: 84,
+ 0xE36: 84,
+ 0xE37: 84,
+ 0xE38: 84,
+ 0xE39: 84,
+ 0xE3A: 84,
+ 0xE47: 84,
+ 0xE48: 84,
+ 0xE49: 84,
+ 0xE4A: 84,
+ 0xE4B: 84,
+ 0xE4C: 84,
+ 0xE4D: 84,
+ 0xE4E: 84,
+ 0xEB1: 84,
+ 0xEB4: 84,
+ 0xEB5: 84,
+ 0xEB6: 84,
+ 0xEB7: 84,
+ 0xEB8: 84,
+ 0xEB9: 84,
+ 0xEBA: 84,
+ 0xEBB: 84,
+ 0xEBC: 84,
+ 0xEC8: 84,
+ 0xEC9: 84,
+ 0xECA: 84,
+ 0xECB: 84,
+ 0xECC: 84,
+ 0xECD: 84,
+ 0xECE: 84,
+ 0xF18: 84,
+ 0xF19: 84,
+ 0xF35: 84,
+ 0xF37: 84,
+ 0xF39: 84,
+ 0xF71: 84,
+ 0xF72: 84,
+ 0xF73: 84,
+ 0xF74: 84,
+ 0xF75: 84,
+ 0xF76: 84,
+ 0xF77: 84,
+ 0xF78: 84,
+ 0xF79: 84,
+ 0xF7A: 84,
+ 0xF7B: 84,
+ 0xF7C: 84,
+ 0xF7D: 84,
+ 0xF7E: 84,
+ 0xF80: 84,
+ 0xF81: 84,
+ 0xF82: 84,
+ 0xF83: 84,
+ 0xF84: 84,
+ 0xF86: 84,
+ 0xF87: 84,
+ 0xF8D: 84,
+ 0xF8E: 84,
+ 0xF8F: 84,
+ 0xF90: 84,
+ 0xF91: 84,
+ 0xF92: 84,
+ 0xF93: 84,
+ 0xF94: 84,
+ 0xF95: 84,
+ 0xF96: 84,
+ 0xF97: 84,
+ 0xF99: 84,
+ 0xF9A: 84,
+ 0xF9B: 84,
+ 0xF9C: 84,
+ 0xF9D: 84,
+ 0xF9E: 84,
+ 0xF9F: 84,
+ 0xFA0: 84,
+ 0xFA1: 84,
+ 0xFA2: 84,
+ 0xFA3: 84,
+ 0xFA4: 84,
+ 0xFA5: 84,
+ 0xFA6: 84,
+ 0xFA7: 84,
+ 0xFA8: 84,
+ 0xFA9: 84,
+ 0xFAA: 84,
+ 0xFAB: 84,
+ 0xFAC: 84,
+ 0xFAD: 84,
+ 0xFAE: 84,
+ 0xFAF: 84,
+ 0xFB0: 84,
+ 0xFB1: 84,
+ 0xFB2: 84,
+ 0xFB3: 84,
+ 0xFB4: 84,
+ 0xFB5: 84,
+ 0xFB6: 84,
+ 0xFB7: 84,
+ 0xFB8: 84,
+ 0xFB9: 84,
+ 0xFBA: 84,
+ 0xFBB: 84,
+ 0xFBC: 84,
+ 0xFC6: 84,
+ 0x102D: 84,
+ 0x102E: 84,
+ 0x102F: 84,
+ 0x1030: 84,
+ 0x1032: 84,
+ 0x1033: 84,
+ 0x1034: 84,
+ 0x1035: 84,
+ 0x1036: 84,
+ 0x1037: 84,
+ 0x1039: 84,
+ 0x103A: 84,
+ 0x103D: 84,
+ 0x103E: 84,
+ 0x1058: 84,
+ 0x1059: 84,
+ 0x105E: 84,
+ 0x105F: 84,
+ 0x1060: 84,
+ 0x1071: 84,
+ 0x1072: 84,
+ 0x1073: 84,
+ 0x1074: 84,
+ 0x1082: 84,
+ 0x1085: 84,
+ 0x1086: 84,
+ 0x108D: 84,
+ 0x109D: 84,
+ 0x135D: 84,
+ 0x135E: 84,
+ 0x135F: 84,
+ 0x1712: 84,
+ 0x1713: 84,
+ 0x1714: 84,
+ 0x1732: 84,
+ 0x1733: 84,
+ 0x1752: 84,
+ 0x1753: 84,
+ 0x1772: 84,
+ 0x1773: 84,
+ 0x17B4: 84,
+ 0x17B5: 84,
+ 0x17B7: 84,
+ 0x17B8: 84,
+ 0x17B9: 84,
+ 0x17BA: 84,
+ 0x17BB: 84,
+ 0x17BC: 84,
+ 0x17BD: 84,
+ 0x17C6: 84,
+ 0x17C9: 84,
+ 0x17CA: 84,
+ 0x17CB: 84,
+ 0x17CC: 84,
+ 0x17CD: 84,
+ 0x17CE: 84,
+ 0x17CF: 84,
+ 0x17D0: 84,
+ 0x17D1: 84,
+ 0x17D2: 84,
+ 0x17D3: 84,
+ 0x17DD: 84,
+ 0x1807: 68,
+ 0x180A: 67,
+ 0x180B: 84,
+ 0x180C: 84,
+ 0x180D: 84,
+ 0x180F: 84,
+ 0x1820: 68,
+ 0x1821: 68,
+ 0x1822: 68,
+ 0x1823: 68,
+ 0x1824: 68,
+ 0x1825: 68,
+ 0x1826: 68,
+ 0x1827: 68,
+ 0x1828: 68,
+ 0x1829: 68,
+ 0x182A: 68,
+ 0x182B: 68,
+ 0x182C: 68,
+ 0x182D: 68,
+ 0x182E: 68,
+ 0x182F: 68,
+ 0x1830: 68,
+ 0x1831: 68,
+ 0x1832: 68,
+ 0x1833: 68,
+ 0x1834: 68,
+ 0x1835: 68,
+ 0x1836: 68,
+ 0x1837: 68,
+ 0x1838: 68,
+ 0x1839: 68,
+ 0x183A: 68,
+ 0x183B: 68,
+ 0x183C: 68,
+ 0x183D: 68,
+ 0x183E: 68,
+ 0x183F: 68,
+ 0x1840: 68,
+ 0x1841: 68,
+ 0x1842: 68,
+ 0x1843: 68,
+ 0x1844: 68,
+ 0x1845: 68,
+ 0x1846: 68,
+ 0x1847: 68,
+ 0x1848: 68,
+ 0x1849: 68,
+ 0x184A: 68,
+ 0x184B: 68,
+ 0x184C: 68,
+ 0x184D: 68,
+ 0x184E: 68,
+ 0x184F: 68,
+ 0x1850: 68,
+ 0x1851: 68,
+ 0x1852: 68,
+ 0x1853: 68,
+ 0x1854: 68,
+ 0x1855: 68,
+ 0x1856: 68,
+ 0x1857: 68,
+ 0x1858: 68,
+ 0x1859: 68,
+ 0x185A: 68,
+ 0x185B: 68,
+ 0x185C: 68,
+ 0x185D: 68,
+ 0x185E: 68,
+ 0x185F: 68,
+ 0x1860: 68,
+ 0x1861: 68,
+ 0x1862: 68,
+ 0x1863: 68,
+ 0x1864: 68,
+ 0x1865: 68,
+ 0x1866: 68,
+ 0x1867: 68,
+ 0x1868: 68,
+ 0x1869: 68,
+ 0x186A: 68,
+ 0x186B: 68,
+ 0x186C: 68,
+ 0x186D: 68,
+ 0x186E: 68,
+ 0x186F: 68,
+ 0x1870: 68,
+ 0x1871: 68,
+ 0x1872: 68,
+ 0x1873: 68,
+ 0x1874: 68,
+ 0x1875: 68,
+ 0x1876: 68,
+ 0x1877: 68,
+ 0x1878: 68,
+ 0x1885: 84,
+ 0x1886: 84,
+ 0x1887: 68,
+ 0x1888: 68,
+ 0x1889: 68,
+ 0x188A: 68,
+ 0x188B: 68,
+ 0x188C: 68,
+ 0x188D: 68,
+ 0x188E: 68,
+ 0x188F: 68,
+ 0x1890: 68,
+ 0x1891: 68,
+ 0x1892: 68,
+ 0x1893: 68,
+ 0x1894: 68,
+ 0x1895: 68,
+ 0x1896: 68,
+ 0x1897: 68,
+ 0x1898: 68,
+ 0x1899: 68,
+ 0x189A: 68,
+ 0x189B: 68,
+ 0x189C: 68,
+ 0x189D: 68,
+ 0x189E: 68,
+ 0x189F: 68,
+ 0x18A0: 68,
+ 0x18A1: 68,
+ 0x18A2: 68,
+ 0x18A3: 68,
+ 0x18A4: 68,
+ 0x18A5: 68,
+ 0x18A6: 68,
+ 0x18A7: 68,
+ 0x18A8: 68,
+ 0x18A9: 84,
+ 0x18AA: 68,
+ 0x1920: 84,
+ 0x1921: 84,
+ 0x1922: 84,
+ 0x1927: 84,
+ 0x1928: 84,
+ 0x1932: 84,
+ 0x1939: 84,
+ 0x193A: 84,
+ 0x193B: 84,
+ 0x1A17: 84,
+ 0x1A18: 84,
+ 0x1A1B: 84,
+ 0x1A56: 84,
+ 0x1A58: 84,
+ 0x1A59: 84,
+ 0x1A5A: 84,
+ 0x1A5B: 84,
+ 0x1A5C: 84,
+ 0x1A5D: 84,
+ 0x1A5E: 84,
+ 0x1A60: 84,
+ 0x1A62: 84,
+ 0x1A65: 84,
+ 0x1A66: 84,
+ 0x1A67: 84,
+ 0x1A68: 84,
+ 0x1A69: 84,
+ 0x1A6A: 84,
+ 0x1A6B: 84,
+ 0x1A6C: 84,
+ 0x1A73: 84,
+ 0x1A74: 84,
+ 0x1A75: 84,
+ 0x1A76: 84,
+ 0x1A77: 84,
+ 0x1A78: 84,
+ 0x1A79: 84,
+ 0x1A7A: 84,
+ 0x1A7B: 84,
+ 0x1A7C: 84,
+ 0x1A7F: 84,
+ 0x1AB0: 84,
+ 0x1AB1: 84,
+ 0x1AB2: 84,
+ 0x1AB3: 84,
+ 0x1AB4: 84,
+ 0x1AB5: 84,
+ 0x1AB6: 84,
+ 0x1AB7: 84,
+ 0x1AB8: 84,
+ 0x1AB9: 84,
+ 0x1ABA: 84,
+ 0x1ABB: 84,
+ 0x1ABC: 84,
+ 0x1ABD: 84,
+ 0x1ABE: 84,
+ 0x1ABF: 84,
+ 0x1AC0: 84,
+ 0x1AC1: 84,
+ 0x1AC2: 84,
+ 0x1AC3: 84,
+ 0x1AC4: 84,
+ 0x1AC5: 84,
+ 0x1AC6: 84,
+ 0x1AC7: 84,
+ 0x1AC8: 84,
+ 0x1AC9: 84,
+ 0x1ACA: 84,
+ 0x1ACB: 84,
+ 0x1ACC: 84,
+ 0x1ACD: 84,
+ 0x1ACE: 84,
+ 0x1B00: 84,
+ 0x1B01: 84,
+ 0x1B02: 84,
+ 0x1B03: 84,
+ 0x1B34: 84,
+ 0x1B36: 84,
+ 0x1B37: 84,
+ 0x1B38: 84,
+ 0x1B39: 84,
+ 0x1B3A: 84,
+ 0x1B3C: 84,
+ 0x1B42: 84,
+ 0x1B6B: 84,
+ 0x1B6C: 84,
+ 0x1B6D: 84,
+ 0x1B6E: 84,
+ 0x1B6F: 84,
+ 0x1B70: 84,
+ 0x1B71: 84,
+ 0x1B72: 84,
+ 0x1B73: 84,
+ 0x1B80: 84,
+ 0x1B81: 84,
+ 0x1BA2: 84,
+ 0x1BA3: 84,
+ 0x1BA4: 84,
+ 0x1BA5: 84,
+ 0x1BA8: 84,
+ 0x1BA9: 84,
+ 0x1BAB: 84,
+ 0x1BAC: 84,
+ 0x1BAD: 84,
+ 0x1BE6: 84,
+ 0x1BE8: 84,
+ 0x1BE9: 84,
+ 0x1BED: 84,
+ 0x1BEF: 84,
+ 0x1BF0: 84,
+ 0x1BF1: 84,
+ 0x1C2C: 84,
+ 0x1C2D: 84,
+ 0x1C2E: 84,
+ 0x1C2F: 84,
+ 0x1C30: 84,
+ 0x1C31: 84,
+ 0x1C32: 84,
+ 0x1C33: 84,
+ 0x1C36: 84,
+ 0x1C37: 84,
+ 0x1CD0: 84,
+ 0x1CD1: 84,
+ 0x1CD2: 84,
+ 0x1CD4: 84,
+ 0x1CD5: 84,
+ 0x1CD6: 84,
+ 0x1CD7: 84,
+ 0x1CD8: 84,
+ 0x1CD9: 84,
+ 0x1CDA: 84,
+ 0x1CDB: 84,
+ 0x1CDC: 84,
+ 0x1CDD: 84,
+ 0x1CDE: 84,
+ 0x1CDF: 84,
+ 0x1CE0: 84,
+ 0x1CE2: 84,
+ 0x1CE3: 84,
+ 0x1CE4: 84,
+ 0x1CE5: 84,
+ 0x1CE6: 84,
+ 0x1CE7: 84,
+ 0x1CE8: 84,
+ 0x1CED: 84,
+ 0x1CF4: 84,
+ 0x1CF8: 84,
+ 0x1CF9: 84,
+ 0x1DC0: 84,
+ 0x1DC1: 84,
+ 0x1DC2: 84,
+ 0x1DC3: 84,
+ 0x1DC4: 84,
+ 0x1DC5: 84,
+ 0x1DC6: 84,
+ 0x1DC7: 84,
+ 0x1DC8: 84,
+ 0x1DC9: 84,
+ 0x1DCA: 84,
+ 0x1DCB: 84,
+ 0x1DCC: 84,
+ 0x1DCD: 84,
+ 0x1DCE: 84,
+ 0x1DCF: 84,
+ 0x1DD0: 84,
+ 0x1DD1: 84,
+ 0x1DD2: 84,
+ 0x1DD3: 84,
+ 0x1DD4: 84,
+ 0x1DD5: 84,
+ 0x1DD6: 84,
+ 0x1DD7: 84,
+ 0x1DD8: 84,
+ 0x1DD9: 84,
+ 0x1DDA: 84,
+ 0x1DDB: 84,
+ 0x1DDC: 84,
+ 0x1DDD: 84,
+ 0x1DDE: 84,
+ 0x1DDF: 84,
+ 0x1DE0: 84,
+ 0x1DE1: 84,
+ 0x1DE2: 84,
+ 0x1DE3: 84,
+ 0x1DE4: 84,
+ 0x1DE5: 84,
+ 0x1DE6: 84,
+ 0x1DE7: 84,
+ 0x1DE8: 84,
+ 0x1DE9: 84,
+ 0x1DEA: 84,
+ 0x1DEB: 84,
+ 0x1DEC: 84,
+ 0x1DED: 84,
+ 0x1DEE: 84,
+ 0x1DEF: 84,
+ 0x1DF0: 84,
+ 0x1DF1: 84,
+ 0x1DF2: 84,
+ 0x1DF3: 84,
+ 0x1DF4: 84,
+ 0x1DF5: 84,
+ 0x1DF6: 84,
+ 0x1DF7: 84,
+ 0x1DF8: 84,
+ 0x1DF9: 84,
+ 0x1DFA: 84,
+ 0x1DFB: 84,
+ 0x1DFC: 84,
+ 0x1DFD: 84,
+ 0x1DFE: 84,
+ 0x1DFF: 84,
+ 0x200B: 84,
+ 0x200D: 67,
+ 0x200E: 84,
+ 0x200F: 84,
+ 0x202A: 84,
+ 0x202B: 84,
+ 0x202C: 84,
+ 0x202D: 84,
+ 0x202E: 84,
+ 0x2060: 84,
+ 0x2061: 84,
+ 0x2062: 84,
+ 0x2063: 84,
+ 0x2064: 84,
+ 0x206A: 84,
+ 0x206B: 84,
+ 0x206C: 84,
+ 0x206D: 84,
+ 0x206E: 84,
+ 0x206F: 84,
+ 0x20D0: 84,
+ 0x20D1: 84,
+ 0x20D2: 84,
+ 0x20D3: 84,
+ 0x20D4: 84,
+ 0x20D5: 84,
+ 0x20D6: 84,
+ 0x20D7: 84,
+ 0x20D8: 84,
+ 0x20D9: 84,
+ 0x20DA: 84,
+ 0x20DB: 84,
+ 0x20DC: 84,
+ 0x20DD: 84,
+ 0x20DE: 84,
+ 0x20DF: 84,
+ 0x20E0: 84,
+ 0x20E1: 84,
+ 0x20E2: 84,
+ 0x20E3: 84,
+ 0x20E4: 84,
+ 0x20E5: 84,
+ 0x20E6: 84,
+ 0x20E7: 84,
+ 0x20E8: 84,
+ 0x20E9: 84,
+ 0x20EA: 84,
+ 0x20EB: 84,
+ 0x20EC: 84,
+ 0x20ED: 84,
+ 0x20EE: 84,
+ 0x20EF: 84,
+ 0x20F0: 84,
+ 0x2CEF: 84,
+ 0x2CF0: 84,
+ 0x2CF1: 84,
+ 0x2D7F: 84,
+ 0x2DE0: 84,
+ 0x2DE1: 84,
+ 0x2DE2: 84,
+ 0x2DE3: 84,
+ 0x2DE4: 84,
+ 0x2DE5: 84,
+ 0x2DE6: 84,
+ 0x2DE7: 84,
+ 0x2DE8: 84,
+ 0x2DE9: 84,
+ 0x2DEA: 84,
+ 0x2DEB: 84,
+ 0x2DEC: 84,
+ 0x2DED: 84,
+ 0x2DEE: 84,
+ 0x2DEF: 84,
+ 0x2DF0: 84,
+ 0x2DF1: 84,
+ 0x2DF2: 84,
+ 0x2DF3: 84,
+ 0x2DF4: 84,
+ 0x2DF5: 84,
+ 0x2DF6: 84,
+ 0x2DF7: 84,
+ 0x2DF8: 84,
+ 0x2DF9: 84,
+ 0x2DFA: 84,
+ 0x2DFB: 84,
+ 0x2DFC: 84,
+ 0x2DFD: 84,
+ 0x2DFE: 84,
+ 0x2DFF: 84,
+ 0x302A: 84,
+ 0x302B: 84,
+ 0x302C: 84,
+ 0x302D: 84,
+ 0x3099: 84,
+ 0x309A: 84,
+ 0xA66F: 84,
+ 0xA670: 84,
+ 0xA671: 84,
+ 0xA672: 84,
+ 0xA674: 84,
+ 0xA675: 84,
+ 0xA676: 84,
+ 0xA677: 84,
+ 0xA678: 84,
+ 0xA679: 84,
+ 0xA67A: 84,
+ 0xA67B: 84,
+ 0xA67C: 84,
+ 0xA67D: 84,
+ 0xA69E: 84,
+ 0xA69F: 84,
+ 0xA6F0: 84,
+ 0xA6F1: 84,
+ 0xA802: 84,
+ 0xA806: 84,
+ 0xA80B: 84,
+ 0xA825: 84,
+ 0xA826: 84,
+ 0xA82C: 84,
+ 0xA840: 68,
+ 0xA841: 68,
+ 0xA842: 68,
+ 0xA843: 68,
+ 0xA844: 68,
+ 0xA845: 68,
+ 0xA846: 68,
+ 0xA847: 68,
+ 0xA848: 68,
+ 0xA849: 68,
+ 0xA84A: 68,
+ 0xA84B: 68,
+ 0xA84C: 68,
+ 0xA84D: 68,
+ 0xA84E: 68,
+ 0xA84F: 68,
+ 0xA850: 68,
+ 0xA851: 68,
+ 0xA852: 68,
+ 0xA853: 68,
+ 0xA854: 68,
+ 0xA855: 68,
+ 0xA856: 68,
+ 0xA857: 68,
+ 0xA858: 68,
+ 0xA859: 68,
+ 0xA85A: 68,
+ 0xA85B: 68,
+ 0xA85C: 68,
+ 0xA85D: 68,
+ 0xA85E: 68,
+ 0xA85F: 68,
+ 0xA860: 68,
+ 0xA861: 68,
+ 0xA862: 68,
+ 0xA863: 68,
+ 0xA864: 68,
+ 0xA865: 68,
+ 0xA866: 68,
+ 0xA867: 68,
+ 0xA868: 68,
+ 0xA869: 68,
+ 0xA86A: 68,
+ 0xA86B: 68,
+ 0xA86C: 68,
+ 0xA86D: 68,
+ 0xA86E: 68,
+ 0xA86F: 68,
+ 0xA870: 68,
+ 0xA871: 68,
+ 0xA872: 76,
+ 0xA8C4: 84,
+ 0xA8C5: 84,
+ 0xA8E0: 84,
+ 0xA8E1: 84,
+ 0xA8E2: 84,
+ 0xA8E3: 84,
+ 0xA8E4: 84,
+ 0xA8E5: 84,
+ 0xA8E6: 84,
+ 0xA8E7: 84,
+ 0xA8E8: 84,
+ 0xA8E9: 84,
+ 0xA8EA: 84,
+ 0xA8EB: 84,
+ 0xA8EC: 84,
+ 0xA8ED: 84,
+ 0xA8EE: 84,
+ 0xA8EF: 84,
+ 0xA8F0: 84,
+ 0xA8F1: 84,
+ 0xA8FF: 84,
+ 0xA926: 84,
+ 0xA927: 84,
+ 0xA928: 84,
+ 0xA929: 84,
+ 0xA92A: 84,
+ 0xA92B: 84,
+ 0xA92C: 84,
+ 0xA92D: 84,
+ 0xA947: 84,
+ 0xA948: 84,
+ 0xA949: 84,
+ 0xA94A: 84,
+ 0xA94B: 84,
+ 0xA94C: 84,
+ 0xA94D: 84,
+ 0xA94E: 84,
+ 0xA94F: 84,
+ 0xA950: 84,
+ 0xA951: 84,
+ 0xA980: 84,
+ 0xA981: 84,
+ 0xA982: 84,
+ 0xA9B3: 84,
+ 0xA9B6: 84,
+ 0xA9B7: 84,
+ 0xA9B8: 84,
+ 0xA9B9: 84,
+ 0xA9BC: 84,
+ 0xA9BD: 84,
+ 0xA9E5: 84,
+ 0xAA29: 84,
+ 0xAA2A: 84,
+ 0xAA2B: 84,
+ 0xAA2C: 84,
+ 0xAA2D: 84,
+ 0xAA2E: 84,
+ 0xAA31: 84,
+ 0xAA32: 84,
+ 0xAA35: 84,
+ 0xAA36: 84,
+ 0xAA43: 84,
+ 0xAA4C: 84,
+ 0xAA7C: 84,
+ 0xAAB0: 84,
+ 0xAAB2: 84,
+ 0xAAB3: 84,
+ 0xAAB4: 84,
+ 0xAAB7: 84,
+ 0xAAB8: 84,
+ 0xAABE: 84,
+ 0xAABF: 84,
+ 0xAAC1: 84,
+ 0xAAEC: 84,
+ 0xAAED: 84,
+ 0xAAF6: 84,
+ 0xABE5: 84,
+ 0xABE8: 84,
+ 0xABED: 84,
+ 0xFB1E: 84,
+ 0xFE00: 84,
+ 0xFE01: 84,
+ 0xFE02: 84,
+ 0xFE03: 84,
+ 0xFE04: 84,
+ 0xFE05: 84,
+ 0xFE06: 84,
+ 0xFE07: 84,
+ 0xFE08: 84,
+ 0xFE09: 84,
+ 0xFE0A: 84,
+ 0xFE0B: 84,
+ 0xFE0C: 84,
+ 0xFE0D: 84,
+ 0xFE0E: 84,
+ 0xFE0F: 84,
+ 0xFE20: 84,
+ 0xFE21: 84,
+ 0xFE22: 84,
+ 0xFE23: 84,
+ 0xFE24: 84,
+ 0xFE25: 84,
+ 0xFE26: 84,
+ 0xFE27: 84,
+ 0xFE28: 84,
+ 0xFE29: 84,
+ 0xFE2A: 84,
+ 0xFE2B: 84,
+ 0xFE2C: 84,
+ 0xFE2D: 84,
+ 0xFE2E: 84,
+ 0xFE2F: 84,
+ 0xFEFF: 84,
+ 0xFFF9: 84,
+ 0xFFFA: 84,
+ 0xFFFB: 84,
+ 0x101FD: 84,
+ 0x102E0: 84,
+ 0x10376: 84,
+ 0x10377: 84,
+ 0x10378: 84,
+ 0x10379: 84,
+ 0x1037A: 84,
+ 0x10A01: 84,
+ 0x10A02: 84,
+ 0x10A03: 84,
+ 0x10A05: 84,
+ 0x10A06: 84,
+ 0x10A0C: 84,
+ 0x10A0D: 84,
+ 0x10A0E: 84,
+ 0x10A0F: 84,
+ 0x10A38: 84,
+ 0x10A39: 84,
+ 0x10A3A: 84,
+ 0x10A3F: 84,
+ 0x10AC0: 68,
+ 0x10AC1: 68,
+ 0x10AC2: 68,
+ 0x10AC3: 68,
+ 0x10AC4: 68,
+ 0x10AC5: 82,
+ 0x10AC7: 82,
+ 0x10AC9: 82,
+ 0x10ACA: 82,
+ 0x10ACD: 76,
+ 0x10ACE: 82,
+ 0x10ACF: 82,
+ 0x10AD0: 82,
+ 0x10AD1: 82,
+ 0x10AD2: 82,
+ 0x10AD3: 68,
+ 0x10AD4: 68,
+ 0x10AD5: 68,
+ 0x10AD6: 68,
+ 0x10AD7: 76,
+ 0x10AD8: 68,
+ 0x10AD9: 68,
+ 0x10ADA: 68,
+ 0x10ADB: 68,
+ 0x10ADC: 68,
+ 0x10ADD: 82,
+ 0x10ADE: 68,
+ 0x10ADF: 68,
+ 0x10AE0: 68,
+ 0x10AE1: 82,
+ 0x10AE4: 82,
+ 0x10AE5: 84,
+ 0x10AE6: 84,
+ 0x10AEB: 68,
+ 0x10AEC: 68,
+ 0x10AED: 68,
+ 0x10AEE: 68,
+ 0x10AEF: 82,
+ 0x10B80: 68,
+ 0x10B81: 82,
+ 0x10B82: 68,
+ 0x10B83: 82,
+ 0x10B84: 82,
+ 0x10B85: 82,
+ 0x10B86: 68,
+ 0x10B87: 68,
+ 0x10B88: 68,
+ 0x10B89: 82,
+ 0x10B8A: 68,
+ 0x10B8B: 68,
+ 0x10B8C: 82,
+ 0x10B8D: 68,
+ 0x10B8E: 82,
+ 0x10B8F: 82,
+ 0x10B90: 68,
+ 0x10B91: 82,
+ 0x10BA9: 82,
+ 0x10BAA: 82,
+ 0x10BAB: 82,
+ 0x10BAC: 82,
+ 0x10BAD: 68,
+ 0x10BAE: 68,
+ 0x10D00: 76,
+ 0x10D01: 68,
+ 0x10D02: 68,
+ 0x10D03: 68,
+ 0x10D04: 68,
+ 0x10D05: 68,
+ 0x10D06: 68,
+ 0x10D07: 68,
+ 0x10D08: 68,
+ 0x10D09: 68,
+ 0x10D0A: 68,
+ 0x10D0B: 68,
+ 0x10D0C: 68,
+ 0x10D0D: 68,
+ 0x10D0E: 68,
+ 0x10D0F: 68,
+ 0x10D10: 68,
+ 0x10D11: 68,
+ 0x10D12: 68,
+ 0x10D13: 68,
+ 0x10D14: 68,
+ 0x10D15: 68,
+ 0x10D16: 68,
+ 0x10D17: 68,
+ 0x10D18: 68,
+ 0x10D19: 68,
+ 0x10D1A: 68,
+ 0x10D1B: 68,
+ 0x10D1C: 68,
+ 0x10D1D: 68,
+ 0x10D1E: 68,
+ 0x10D1F: 68,
+ 0x10D20: 68,
+ 0x10D21: 68,
+ 0x10D22: 82,
+ 0x10D23: 68,
+ 0x10D24: 84,
+ 0x10D25: 84,
+ 0x10D26: 84,
+ 0x10D27: 84,
+ 0x10EAB: 84,
+ 0x10EAC: 84,
+ 0x10EFD: 84,
+ 0x10EFE: 84,
+ 0x10EFF: 84,
+ 0x10F30: 68,
+ 0x10F31: 68,
+ 0x10F32: 68,
+ 0x10F33: 82,
+ 0x10F34: 68,
+ 0x10F35: 68,
+ 0x10F36: 68,
+ 0x10F37: 68,
+ 0x10F38: 68,
+ 0x10F39: 68,
+ 0x10F3A: 68,
+ 0x10F3B: 68,
+ 0x10F3C: 68,
+ 0x10F3D: 68,
+ 0x10F3E: 68,
+ 0x10F3F: 68,
+ 0x10F40: 68,
+ 0x10F41: 68,
+ 0x10F42: 68,
+ 0x10F43: 68,
+ 0x10F44: 68,
+ 0x10F46: 84,
+ 0x10F47: 84,
+ 0x10F48: 84,
+ 0x10F49: 84,
+ 0x10F4A: 84,
+ 0x10F4B: 84,
+ 0x10F4C: 84,
+ 0x10F4D: 84,
+ 0x10F4E: 84,
+ 0x10F4F: 84,
+ 0x10F50: 84,
+ 0x10F51: 68,
+ 0x10F52: 68,
+ 0x10F53: 68,
+ 0x10F54: 82,
+ 0x10F70: 68,
+ 0x10F71: 68,
+ 0x10F72: 68,
+ 0x10F73: 68,
+ 0x10F74: 82,
+ 0x10F75: 82,
+ 0x10F76: 68,
+ 0x10F77: 68,
+ 0x10F78: 68,
+ 0x10F79: 68,
+ 0x10F7A: 68,
+ 0x10F7B: 68,
+ 0x10F7C: 68,
+ 0x10F7D: 68,
+ 0x10F7E: 68,
+ 0x10F7F: 68,
+ 0x10F80: 68,
+ 0x10F81: 68,
+ 0x10F82: 84,
+ 0x10F83: 84,
+ 0x10F84: 84,
+ 0x10F85: 84,
+ 0x10FB0: 68,
+ 0x10FB2: 68,
+ 0x10FB3: 68,
+ 0x10FB4: 82,
+ 0x10FB5: 82,
+ 0x10FB6: 82,
+ 0x10FB8: 68,
+ 0x10FB9: 82,
+ 0x10FBA: 82,
+ 0x10FBB: 68,
+ 0x10FBC: 68,
+ 0x10FBD: 82,
+ 0x10FBE: 68,
+ 0x10FBF: 68,
+ 0x10FC1: 68,
+ 0x10FC2: 82,
+ 0x10FC3: 82,
+ 0x10FC4: 68,
+ 0x10FC9: 82,
+ 0x10FCA: 68,
+ 0x10FCB: 76,
+ 0x11001: 84,
+ 0x11038: 84,
+ 0x11039: 84,
+ 0x1103A: 84,
+ 0x1103B: 84,
+ 0x1103C: 84,
+ 0x1103D: 84,
+ 0x1103E: 84,
+ 0x1103F: 84,
+ 0x11040: 84,
+ 0x11041: 84,
+ 0x11042: 84,
+ 0x11043: 84,
+ 0x11044: 84,
+ 0x11045: 84,
+ 0x11046: 84,
+ 0x11070: 84,
+ 0x11073: 84,
+ 0x11074: 84,
+ 0x1107F: 84,
+ 0x11080: 84,
+ 0x11081: 84,
+ 0x110B3: 84,
+ 0x110B4: 84,
+ 0x110B5: 84,
+ 0x110B6: 84,
+ 0x110B9: 84,
+ 0x110BA: 84,
+ 0x110C2: 84,
+ 0x11100: 84,
+ 0x11101: 84,
+ 0x11102: 84,
+ 0x11127: 84,
+ 0x11128: 84,
+ 0x11129: 84,
+ 0x1112A: 84,
+ 0x1112B: 84,
+ 0x1112D: 84,
+ 0x1112E: 84,
+ 0x1112F: 84,
+ 0x11130: 84,
+ 0x11131: 84,
+ 0x11132: 84,
+ 0x11133: 84,
+ 0x11134: 84,
+ 0x11173: 84,
+ 0x11180: 84,
+ 0x11181: 84,
+ 0x111B6: 84,
+ 0x111B7: 84,
+ 0x111B8: 84,
+ 0x111B9: 84,
+ 0x111BA: 84,
+ 0x111BB: 84,
+ 0x111BC: 84,
+ 0x111BD: 84,
+ 0x111BE: 84,
+ 0x111C9: 84,
+ 0x111CA: 84,
+ 0x111CB: 84,
+ 0x111CC: 84,
+ 0x111CF: 84,
+ 0x1122F: 84,
+ 0x11230: 84,
+ 0x11231: 84,
+ 0x11234: 84,
+ 0x11236: 84,
+ 0x11237: 84,
+ 0x1123E: 84,
+ 0x11241: 84,
+ 0x112DF: 84,
+ 0x112E3: 84,
+ 0x112E4: 84,
+ 0x112E5: 84,
+ 0x112E6: 84,
+ 0x112E7: 84,
+ 0x112E8: 84,
+ 0x112E9: 84,
+ 0x112EA: 84,
+ 0x11300: 84,
+ 0x11301: 84,
+ 0x1133B: 84,
+ 0x1133C: 84,
+ 0x11340: 84,
+ 0x11366: 84,
+ 0x11367: 84,
+ 0x11368: 84,
+ 0x11369: 84,
+ 0x1136A: 84,
+ 0x1136B: 84,
+ 0x1136C: 84,
+ 0x11370: 84,
+ 0x11371: 84,
+ 0x11372: 84,
+ 0x11373: 84,
+ 0x11374: 84,
+ 0x11438: 84,
+ 0x11439: 84,
+ 0x1143A: 84,
+ 0x1143B: 84,
+ 0x1143C: 84,
+ 0x1143D: 84,
+ 0x1143E: 84,
+ 0x1143F: 84,
+ 0x11442: 84,
+ 0x11443: 84,
+ 0x11444: 84,
+ 0x11446: 84,
+ 0x1145E: 84,
+ 0x114B3: 84,
+ 0x114B4: 84,
+ 0x114B5: 84,
+ 0x114B6: 84,
+ 0x114B7: 84,
+ 0x114B8: 84,
+ 0x114BA: 84,
+ 0x114BF: 84,
+ 0x114C0: 84,
+ 0x114C2: 84,
+ 0x114C3: 84,
+ 0x115B2: 84,
+ 0x115B3: 84,
+ 0x115B4: 84,
+ 0x115B5: 84,
+ 0x115BC: 84,
+ 0x115BD: 84,
+ 0x115BF: 84,
+ 0x115C0: 84,
+ 0x115DC: 84,
+ 0x115DD: 84,
+ 0x11633: 84,
+ 0x11634: 84,
+ 0x11635: 84,
+ 0x11636: 84,
+ 0x11637: 84,
+ 0x11638: 84,
+ 0x11639: 84,
+ 0x1163A: 84,
+ 0x1163D: 84,
+ 0x1163F: 84,
+ 0x11640: 84,
+ 0x116AB: 84,
+ 0x116AD: 84,
+ 0x116B0: 84,
+ 0x116B1: 84,
+ 0x116B2: 84,
+ 0x116B3: 84,
+ 0x116B4: 84,
+ 0x116B5: 84,
+ 0x116B7: 84,
+ 0x1171D: 84,
+ 0x1171E: 84,
+ 0x1171F: 84,
+ 0x11722: 84,
+ 0x11723: 84,
+ 0x11724: 84,
+ 0x11725: 84,
+ 0x11727: 84,
+ 0x11728: 84,
+ 0x11729: 84,
+ 0x1172A: 84,
+ 0x1172B: 84,
+ 0x1182F: 84,
+ 0x11830: 84,
+ 0x11831: 84,
+ 0x11832: 84,
+ 0x11833: 84,
+ 0x11834: 84,
+ 0x11835: 84,
+ 0x11836: 84,
+ 0x11837: 84,
+ 0x11839: 84,
+ 0x1183A: 84,
+ 0x1193B: 84,
+ 0x1193C: 84,
+ 0x1193E: 84,
+ 0x11943: 84,
+ 0x119D4: 84,
+ 0x119D5: 84,
+ 0x119D6: 84,
+ 0x119D7: 84,
+ 0x119DA: 84,
+ 0x119DB: 84,
+ 0x119E0: 84,
+ 0x11A01: 84,
+ 0x11A02: 84,
+ 0x11A03: 84,
+ 0x11A04: 84,
+ 0x11A05: 84,
+ 0x11A06: 84,
+ 0x11A07: 84,
+ 0x11A08: 84,
+ 0x11A09: 84,
+ 0x11A0A: 84,
+ 0x11A33: 84,
+ 0x11A34: 84,
+ 0x11A35: 84,
+ 0x11A36: 84,
+ 0x11A37: 84,
+ 0x11A38: 84,
+ 0x11A3B: 84,
+ 0x11A3C: 84,
+ 0x11A3D: 84,
+ 0x11A3E: 84,
+ 0x11A47: 84,
+ 0x11A51: 84,
+ 0x11A52: 84,
+ 0x11A53: 84,
+ 0x11A54: 84,
+ 0x11A55: 84,
+ 0x11A56: 84,
+ 0x11A59: 84,
+ 0x11A5A: 84,
+ 0x11A5B: 84,
+ 0x11A8A: 84,
+ 0x11A8B: 84,
+ 0x11A8C: 84,
+ 0x11A8D: 84,
+ 0x11A8E: 84,
+ 0x11A8F: 84,
+ 0x11A90: 84,
+ 0x11A91: 84,
+ 0x11A92: 84,
+ 0x11A93: 84,
+ 0x11A94: 84,
+ 0x11A95: 84,
+ 0x11A96: 84,
+ 0x11A98: 84,
+ 0x11A99: 84,
+ 0x11C30: 84,
+ 0x11C31: 84,
+ 0x11C32: 84,
+ 0x11C33: 84,
+ 0x11C34: 84,
+ 0x11C35: 84,
+ 0x11C36: 84,
+ 0x11C38: 84,
+ 0x11C39: 84,
+ 0x11C3A: 84,
+ 0x11C3B: 84,
+ 0x11C3C: 84,
+ 0x11C3D: 84,
+ 0x11C3F: 84,
+ 0x11C92: 84,
+ 0x11C93: 84,
+ 0x11C94: 84,
+ 0x11C95: 84,
+ 0x11C96: 84,
+ 0x11C97: 84,
+ 0x11C98: 84,
+ 0x11C99: 84,
+ 0x11C9A: 84,
+ 0x11C9B: 84,
+ 0x11C9C: 84,
+ 0x11C9D: 84,
+ 0x11C9E: 84,
+ 0x11C9F: 84,
+ 0x11CA0: 84,
+ 0x11CA1: 84,
+ 0x11CA2: 84,
+ 0x11CA3: 84,
+ 0x11CA4: 84,
+ 0x11CA5: 84,
+ 0x11CA6: 84,
+ 0x11CA7: 84,
+ 0x11CAA: 84,
+ 0x11CAB: 84,
+ 0x11CAC: 84,
+ 0x11CAD: 84,
+ 0x11CAE: 84,
+ 0x11CAF: 84,
+ 0x11CB0: 84,
+ 0x11CB2: 84,
+ 0x11CB3: 84,
+ 0x11CB5: 84,
+ 0x11CB6: 84,
+ 0x11D31: 84,
+ 0x11D32: 84,
+ 0x11D33: 84,
+ 0x11D34: 84,
+ 0x11D35: 84,
+ 0x11D36: 84,
+ 0x11D3A: 84,
+ 0x11D3C: 84,
+ 0x11D3D: 84,
+ 0x11D3F: 84,
+ 0x11D40: 84,
+ 0x11D41: 84,
+ 0x11D42: 84,
+ 0x11D43: 84,
+ 0x11D44: 84,
+ 0x11D45: 84,
+ 0x11D47: 84,
+ 0x11D90: 84,
+ 0x11D91: 84,
+ 0x11D95: 84,
+ 0x11D97: 84,
+ 0x11EF3: 84,
+ 0x11EF4: 84,
+ 0x11F00: 84,
+ 0x11F01: 84,
+ 0x11F36: 84,
+ 0x11F37: 84,
+ 0x11F38: 84,
+ 0x11F39: 84,
+ 0x11F3A: 84,
+ 0x11F40: 84,
+ 0x11F42: 84,
+ 0x13430: 84,
+ 0x13431: 84,
+ 0x13432: 84,
+ 0x13433: 84,
+ 0x13434: 84,
+ 0x13435: 84,
+ 0x13436: 84,
+ 0x13437: 84,
+ 0x13438: 84,
+ 0x13439: 84,
+ 0x1343A: 84,
+ 0x1343B: 84,
+ 0x1343C: 84,
+ 0x1343D: 84,
+ 0x1343E: 84,
+ 0x1343F: 84,
+ 0x13440: 84,
+ 0x13447: 84,
+ 0x13448: 84,
+ 0x13449: 84,
+ 0x1344A: 84,
+ 0x1344B: 84,
+ 0x1344C: 84,
+ 0x1344D: 84,
+ 0x1344E: 84,
+ 0x1344F: 84,
+ 0x13450: 84,
+ 0x13451: 84,
+ 0x13452: 84,
+ 0x13453: 84,
+ 0x13454: 84,
+ 0x13455: 84,
+ 0x16AF0: 84,
+ 0x16AF1: 84,
+ 0x16AF2: 84,
+ 0x16AF3: 84,
+ 0x16AF4: 84,
+ 0x16B30: 84,
+ 0x16B31: 84,
+ 0x16B32: 84,
+ 0x16B33: 84,
+ 0x16B34: 84,
+ 0x16B35: 84,
+ 0x16B36: 84,
+ 0x16F4F: 84,
+ 0x16F8F: 84,
+ 0x16F90: 84,
+ 0x16F91: 84,
+ 0x16F92: 84,
+ 0x16FE4: 84,
+ 0x1BC9D: 84,
+ 0x1BC9E: 84,
+ 0x1BCA0: 84,
+ 0x1BCA1: 84,
+ 0x1BCA2: 84,
+ 0x1BCA3: 84,
+ 0x1CF00: 84,
+ 0x1CF01: 84,
+ 0x1CF02: 84,
+ 0x1CF03: 84,
+ 0x1CF04: 84,
+ 0x1CF05: 84,
+ 0x1CF06: 84,
+ 0x1CF07: 84,
+ 0x1CF08: 84,
+ 0x1CF09: 84,
+ 0x1CF0A: 84,
+ 0x1CF0B: 84,
+ 0x1CF0C: 84,
+ 0x1CF0D: 84,
+ 0x1CF0E: 84,
+ 0x1CF0F: 84,
+ 0x1CF10: 84,
+ 0x1CF11: 84,
+ 0x1CF12: 84,
+ 0x1CF13: 84,
+ 0x1CF14: 84,
+ 0x1CF15: 84,
+ 0x1CF16: 84,
+ 0x1CF17: 84,
+ 0x1CF18: 84,
+ 0x1CF19: 84,
+ 0x1CF1A: 84,
+ 0x1CF1B: 84,
+ 0x1CF1C: 84,
+ 0x1CF1D: 84,
+ 0x1CF1E: 84,
+ 0x1CF1F: 84,
+ 0x1CF20: 84,
+ 0x1CF21: 84,
+ 0x1CF22: 84,
+ 0x1CF23: 84,
+ 0x1CF24: 84,
+ 0x1CF25: 84,
+ 0x1CF26: 84,
+ 0x1CF27: 84,
+ 0x1CF28: 84,
+ 0x1CF29: 84,
+ 0x1CF2A: 84,
+ 0x1CF2B: 84,
+ 0x1CF2C: 84,
+ 0x1CF2D: 84,
+ 0x1CF30: 84,
+ 0x1CF31: 84,
+ 0x1CF32: 84,
+ 0x1CF33: 84,
+ 0x1CF34: 84,
+ 0x1CF35: 84,
+ 0x1CF36: 84,
+ 0x1CF37: 84,
+ 0x1CF38: 84,
+ 0x1CF39: 84,
+ 0x1CF3A: 84,
+ 0x1CF3B: 84,
+ 0x1CF3C: 84,
+ 0x1CF3D: 84,
+ 0x1CF3E: 84,
+ 0x1CF3F: 84,
+ 0x1CF40: 84,
+ 0x1CF41: 84,
+ 0x1CF42: 84,
+ 0x1CF43: 84,
+ 0x1CF44: 84,
+ 0x1CF45: 84,
+ 0x1CF46: 84,
+ 0x1D167: 84,
+ 0x1D168: 84,
+ 0x1D169: 84,
+ 0x1D173: 84,
+ 0x1D174: 84,
+ 0x1D175: 84,
+ 0x1D176: 84,
+ 0x1D177: 84,
+ 0x1D178: 84,
+ 0x1D179: 84,
+ 0x1D17A: 84,
+ 0x1D17B: 84,
+ 0x1D17C: 84,
+ 0x1D17D: 84,
+ 0x1D17E: 84,
+ 0x1D17F: 84,
+ 0x1D180: 84,
+ 0x1D181: 84,
+ 0x1D182: 84,
+ 0x1D185: 84,
+ 0x1D186: 84,
+ 0x1D187: 84,
+ 0x1D188: 84,
+ 0x1D189: 84,
+ 0x1D18A: 84,
+ 0x1D18B: 84,
+ 0x1D1AA: 84,
+ 0x1D1AB: 84,
+ 0x1D1AC: 84,
+ 0x1D1AD: 84,
+ 0x1D242: 84,
+ 0x1D243: 84,
+ 0x1D244: 84,
+ 0x1DA00: 84,
+ 0x1DA01: 84,
+ 0x1DA02: 84,
+ 0x1DA03: 84,
+ 0x1DA04: 84,
+ 0x1DA05: 84,
+ 0x1DA06: 84,
+ 0x1DA07: 84,
+ 0x1DA08: 84,
+ 0x1DA09: 84,
+ 0x1DA0A: 84,
+ 0x1DA0B: 84,
+ 0x1DA0C: 84,
+ 0x1DA0D: 84,
+ 0x1DA0E: 84,
+ 0x1DA0F: 84,
+ 0x1DA10: 84,
+ 0x1DA11: 84,
+ 0x1DA12: 84,
+ 0x1DA13: 84,
+ 0x1DA14: 84,
+ 0x1DA15: 84,
+ 0x1DA16: 84,
+ 0x1DA17: 84,
+ 0x1DA18: 84,
+ 0x1DA19: 84,
+ 0x1DA1A: 84,
+ 0x1DA1B: 84,
+ 0x1DA1C: 84,
+ 0x1DA1D: 84,
+ 0x1DA1E: 84,
+ 0x1DA1F: 84,
+ 0x1DA20: 84,
+ 0x1DA21: 84,
+ 0x1DA22: 84,
+ 0x1DA23: 84,
+ 0x1DA24: 84,
+ 0x1DA25: 84,
+ 0x1DA26: 84,
+ 0x1DA27: 84,
+ 0x1DA28: 84,
+ 0x1DA29: 84,
+ 0x1DA2A: 84,
+ 0x1DA2B: 84,
+ 0x1DA2C: 84,
+ 0x1DA2D: 84,
+ 0x1DA2E: 84,
+ 0x1DA2F: 84,
+ 0x1DA30: 84,
+ 0x1DA31: 84,
+ 0x1DA32: 84,
+ 0x1DA33: 84,
+ 0x1DA34: 84,
+ 0x1DA35: 84,
+ 0x1DA36: 84,
+ 0x1DA3B: 84,
+ 0x1DA3C: 84,
+ 0x1DA3D: 84,
+ 0x1DA3E: 84,
+ 0x1DA3F: 84,
+ 0x1DA40: 84,
+ 0x1DA41: 84,
+ 0x1DA42: 84,
+ 0x1DA43: 84,
+ 0x1DA44: 84,
+ 0x1DA45: 84,
+ 0x1DA46: 84,
+ 0x1DA47: 84,
+ 0x1DA48: 84,
+ 0x1DA49: 84,
+ 0x1DA4A: 84,
+ 0x1DA4B: 84,
+ 0x1DA4C: 84,
+ 0x1DA4D: 84,
+ 0x1DA4E: 84,
+ 0x1DA4F: 84,
+ 0x1DA50: 84,
+ 0x1DA51: 84,
+ 0x1DA52: 84,
+ 0x1DA53: 84,
+ 0x1DA54: 84,
+ 0x1DA55: 84,
+ 0x1DA56: 84,
+ 0x1DA57: 84,
+ 0x1DA58: 84,
+ 0x1DA59: 84,
+ 0x1DA5A: 84,
+ 0x1DA5B: 84,
+ 0x1DA5C: 84,
+ 0x1DA5D: 84,
+ 0x1DA5E: 84,
+ 0x1DA5F: 84,
+ 0x1DA60: 84,
+ 0x1DA61: 84,
+ 0x1DA62: 84,
+ 0x1DA63: 84,
+ 0x1DA64: 84,
+ 0x1DA65: 84,
+ 0x1DA66: 84,
+ 0x1DA67: 84,
+ 0x1DA68: 84,
+ 0x1DA69: 84,
+ 0x1DA6A: 84,
+ 0x1DA6B: 84,
+ 0x1DA6C: 84,
+ 0x1DA75: 84,
+ 0x1DA84: 84,
+ 0x1DA9B: 84,
+ 0x1DA9C: 84,
+ 0x1DA9D: 84,
+ 0x1DA9E: 84,
+ 0x1DA9F: 84,
+ 0x1DAA1: 84,
+ 0x1DAA2: 84,
+ 0x1DAA3: 84,
+ 0x1DAA4: 84,
+ 0x1DAA5: 84,
+ 0x1DAA6: 84,
+ 0x1DAA7: 84,
+ 0x1DAA8: 84,
+ 0x1DAA9: 84,
+ 0x1DAAA: 84,
+ 0x1DAAB: 84,
+ 0x1DAAC: 84,
+ 0x1DAAD: 84,
+ 0x1DAAE: 84,
+ 0x1DAAF: 84,
+ 0x1E000: 84,
+ 0x1E001: 84,
+ 0x1E002: 84,
+ 0x1E003: 84,
+ 0x1E004: 84,
+ 0x1E005: 84,
+ 0x1E006: 84,
+ 0x1E008: 84,
+ 0x1E009: 84,
+ 0x1E00A: 84,
+ 0x1E00B: 84,
+ 0x1E00C: 84,
+ 0x1E00D: 84,
+ 0x1E00E: 84,
+ 0x1E00F: 84,
+ 0x1E010: 84,
+ 0x1E011: 84,
+ 0x1E012: 84,
+ 0x1E013: 84,
+ 0x1E014: 84,
+ 0x1E015: 84,
+ 0x1E016: 84,
+ 0x1E017: 84,
+ 0x1E018: 84,
+ 0x1E01B: 84,
+ 0x1E01C: 84,
+ 0x1E01D: 84,
+ 0x1E01E: 84,
+ 0x1E01F: 84,
+ 0x1E020: 84,
+ 0x1E021: 84,
+ 0x1E023: 84,
+ 0x1E024: 84,
+ 0x1E026: 84,
+ 0x1E027: 84,
+ 0x1E028: 84,
+ 0x1E029: 84,
+ 0x1E02A: 84,
+ 0x1E08F: 84,
+ 0x1E130: 84,
+ 0x1E131: 84,
+ 0x1E132: 84,
+ 0x1E133: 84,
+ 0x1E134: 84,
+ 0x1E135: 84,
+ 0x1E136: 84,
+ 0x1E2AE: 84,
+ 0x1E2EC: 84,
+ 0x1E2ED: 84,
+ 0x1E2EE: 84,
+ 0x1E2EF: 84,
+ 0x1E4EC: 84,
+ 0x1E4ED: 84,
+ 0x1E4EE: 84,
+ 0x1E4EF: 84,
+ 0x1E8D0: 84,
+ 0x1E8D1: 84,
+ 0x1E8D2: 84,
+ 0x1E8D3: 84,
+ 0x1E8D4: 84,
+ 0x1E8D5: 84,
+ 0x1E8D6: 84,
+ 0x1E900: 68,
+ 0x1E901: 68,
+ 0x1E902: 68,
+ 0x1E903: 68,
+ 0x1E904: 68,
+ 0x1E905: 68,
+ 0x1E906: 68,
+ 0x1E907: 68,
+ 0x1E908: 68,
+ 0x1E909: 68,
+ 0x1E90A: 68,
+ 0x1E90B: 68,
+ 0x1E90C: 68,
+ 0x1E90D: 68,
+ 0x1E90E: 68,
+ 0x1E90F: 68,
+ 0x1E910: 68,
+ 0x1E911: 68,
+ 0x1E912: 68,
+ 0x1E913: 68,
+ 0x1E914: 68,
+ 0x1E915: 68,
+ 0x1E916: 68,
+ 0x1E917: 68,
+ 0x1E918: 68,
+ 0x1E919: 68,
+ 0x1E91A: 68,
+ 0x1E91B: 68,
+ 0x1E91C: 68,
+ 0x1E91D: 68,
+ 0x1E91E: 68,
+ 0x1E91F: 68,
+ 0x1E920: 68,
+ 0x1E921: 68,
+ 0x1E922: 68,
+ 0x1E923: 68,
+ 0x1E924: 68,
+ 0x1E925: 68,
+ 0x1E926: 68,
+ 0x1E927: 68,
+ 0x1E928: 68,
+ 0x1E929: 68,
+ 0x1E92A: 68,
+ 0x1E92B: 68,
+ 0x1E92C: 68,
+ 0x1E92D: 68,
+ 0x1E92E: 68,
+ 0x1E92F: 68,
+ 0x1E930: 68,
+ 0x1E931: 68,
+ 0x1E932: 68,
+ 0x1E933: 68,
+ 0x1E934: 68,
+ 0x1E935: 68,
+ 0x1E936: 68,
+ 0x1E937: 68,
+ 0x1E938: 68,
+ 0x1E939: 68,
+ 0x1E93A: 68,
+ 0x1E93B: 68,
+ 0x1E93C: 68,
+ 0x1E93D: 68,
+ 0x1E93E: 68,
+ 0x1E93F: 68,
+ 0x1E940: 68,
+ 0x1E941: 68,
+ 0x1E942: 68,
+ 0x1E943: 68,
+ 0x1E944: 84,
+ 0x1E945: 84,
+ 0x1E946: 84,
+ 0x1E947: 84,
+ 0x1E948: 84,
+ 0x1E949: 84,
+ 0x1E94A: 84,
+ 0x1E94B: 84,
+ 0xE0001: 84,
+ 0xE0020: 84,
+ 0xE0021: 84,
+ 0xE0022: 84,
+ 0xE0023: 84,
+ 0xE0024: 84,
+ 0xE0025: 84,
+ 0xE0026: 84,
+ 0xE0027: 84,
+ 0xE0028: 84,
+ 0xE0029: 84,
+ 0xE002A: 84,
+ 0xE002B: 84,
+ 0xE002C: 84,
+ 0xE002D: 84,
+ 0xE002E: 84,
+ 0xE002F: 84,
+ 0xE0030: 84,
+ 0xE0031: 84,
+ 0xE0032: 84,
+ 0xE0033: 84,
+ 0xE0034: 84,
+ 0xE0035: 84,
+ 0xE0036: 84,
+ 0xE0037: 84,
+ 0xE0038: 84,
+ 0xE0039: 84,
+ 0xE003A: 84,
+ 0xE003B: 84,
+ 0xE003C: 84,
+ 0xE003D: 84,
+ 0xE003E: 84,
+ 0xE003F: 84,
+ 0xE0040: 84,
+ 0xE0041: 84,
+ 0xE0042: 84,
+ 0xE0043: 84,
+ 0xE0044: 84,
+ 0xE0045: 84,
+ 0xE0046: 84,
+ 0xE0047: 84,
+ 0xE0048: 84,
+ 0xE0049: 84,
+ 0xE004A: 84,
+ 0xE004B: 84,
+ 0xE004C: 84,
+ 0xE004D: 84,
+ 0xE004E: 84,
+ 0xE004F: 84,
+ 0xE0050: 84,
+ 0xE0051: 84,
+ 0xE0052: 84,
+ 0xE0053: 84,
+ 0xE0054: 84,
+ 0xE0055: 84,
+ 0xE0056: 84,
+ 0xE0057: 84,
+ 0xE0058: 84,
+ 0xE0059: 84,
+ 0xE005A: 84,
+ 0xE005B: 84,
+ 0xE005C: 84,
+ 0xE005D: 84,
+ 0xE005E: 84,
+ 0xE005F: 84,
+ 0xE0060: 84,
+ 0xE0061: 84,
+ 0xE0062: 84,
+ 0xE0063: 84,
+ 0xE0064: 84,
+ 0xE0065: 84,
+ 0xE0066: 84,
+ 0xE0067: 84,
+ 0xE0068: 84,
+ 0xE0069: 84,
+ 0xE006A: 84,
+ 0xE006B: 84,
+ 0xE006C: 84,
+ 0xE006D: 84,
+ 0xE006E: 84,
+ 0xE006F: 84,
+ 0xE0070: 84,
+ 0xE0071: 84,
+ 0xE0072: 84,
+ 0xE0073: 84,
+ 0xE0074: 84,
+ 0xE0075: 84,
+ 0xE0076: 84,
+ 0xE0077: 84,
+ 0xE0078: 84,
+ 0xE0079: 84,
+ 0xE007A: 84,
+ 0xE007B: 84,
+ 0xE007C: 84,
+ 0xE007D: 84,
+ 0xE007E: 84,
+ 0xE007F: 84,
+ 0xE0100: 84,
+ 0xE0101: 84,
+ 0xE0102: 84,
+ 0xE0103: 84,
+ 0xE0104: 84,
+ 0xE0105: 84,
+ 0xE0106: 84,
+ 0xE0107: 84,
+ 0xE0108: 84,
+ 0xE0109: 84,
+ 0xE010A: 84,
+ 0xE010B: 84,
+ 0xE010C: 84,
+ 0xE010D: 84,
+ 0xE010E: 84,
+ 0xE010F: 84,
+ 0xE0110: 84,
+ 0xE0111: 84,
+ 0xE0112: 84,
+ 0xE0113: 84,
+ 0xE0114: 84,
+ 0xE0115: 84,
+ 0xE0116: 84,
+ 0xE0117: 84,
+ 0xE0118: 84,
+ 0xE0119: 84,
+ 0xE011A: 84,
+ 0xE011B: 84,
+ 0xE011C: 84,
+ 0xE011D: 84,
+ 0xE011E: 84,
+ 0xE011F: 84,
+ 0xE0120: 84,
+ 0xE0121: 84,
+ 0xE0122: 84,
+ 0xE0123: 84,
+ 0xE0124: 84,
+ 0xE0125: 84,
+ 0xE0126: 84,
+ 0xE0127: 84,
+ 0xE0128: 84,
+ 0xE0129: 84,
+ 0xE012A: 84,
+ 0xE012B: 84,
+ 0xE012C: 84,
+ 0xE012D: 84,
+ 0xE012E: 84,
+ 0xE012F: 84,
+ 0xE0130: 84,
+ 0xE0131: 84,
+ 0xE0132: 84,
+ 0xE0133: 84,
+ 0xE0134: 84,
+ 0xE0135: 84,
+ 0xE0136: 84,
+ 0xE0137: 84,
+ 0xE0138: 84,
+ 0xE0139: 84,
+ 0xE013A: 84,
+ 0xE013B: 84,
+ 0xE013C: 84,
+ 0xE013D: 84,
+ 0xE013E: 84,
+ 0xE013F: 84,
+ 0xE0140: 84,
+ 0xE0141: 84,
+ 0xE0142: 84,
+ 0xE0143: 84,
+ 0xE0144: 84,
+ 0xE0145: 84,
+ 0xE0146: 84,
+ 0xE0147: 84,
+ 0xE0148: 84,
+ 0xE0149: 84,
+ 0xE014A: 84,
+ 0xE014B: 84,
+ 0xE014C: 84,
+ 0xE014D: 84,
+ 0xE014E: 84,
+ 0xE014F: 84,
+ 0xE0150: 84,
+ 0xE0151: 84,
+ 0xE0152: 84,
+ 0xE0153: 84,
+ 0xE0154: 84,
+ 0xE0155: 84,
+ 0xE0156: 84,
+ 0xE0157: 84,
+ 0xE0158: 84,
+ 0xE0159: 84,
+ 0xE015A: 84,
+ 0xE015B: 84,
+ 0xE015C: 84,
+ 0xE015D: 84,
+ 0xE015E: 84,
+ 0xE015F: 84,
+ 0xE0160: 84,
+ 0xE0161: 84,
+ 0xE0162: 84,
+ 0xE0163: 84,
+ 0xE0164: 84,
+ 0xE0165: 84,
+ 0xE0166: 84,
+ 0xE0167: 84,
+ 0xE0168: 84,
+ 0xE0169: 84,
+ 0xE016A: 84,
+ 0xE016B: 84,
+ 0xE016C: 84,
+ 0xE016D: 84,
+ 0xE016E: 84,
+ 0xE016F: 84,
+ 0xE0170: 84,
+ 0xE0171: 84,
+ 0xE0172: 84,
+ 0xE0173: 84,
+ 0xE0174: 84,
+ 0xE0175: 84,
+ 0xE0176: 84,
+ 0xE0177: 84,
+ 0xE0178: 84,
+ 0xE0179: 84,
+ 0xE017A: 84,
+ 0xE017B: 84,
+ 0xE017C: 84,
+ 0xE017D: 84,
+ 0xE017E: 84,
+ 0xE017F: 84,
+ 0xE0180: 84,
+ 0xE0181: 84,
+ 0xE0182: 84,
+ 0xE0183: 84,
+ 0xE0184: 84,
+ 0xE0185: 84,
+ 0xE0186: 84,
+ 0xE0187: 84,
+ 0xE0188: 84,
+ 0xE0189: 84,
+ 0xE018A: 84,
+ 0xE018B: 84,
+ 0xE018C: 84,
+ 0xE018D: 84,
+ 0xE018E: 84,
+ 0xE018F: 84,
+ 0xE0190: 84,
+ 0xE0191: 84,
+ 0xE0192: 84,
+ 0xE0193: 84,
+ 0xE0194: 84,
+ 0xE0195: 84,
+ 0xE0196: 84,
+ 0xE0197: 84,
+ 0xE0198: 84,
+ 0xE0199: 84,
+ 0xE019A: 84,
+ 0xE019B: 84,
+ 0xE019C: 84,
+ 0xE019D: 84,
+ 0xE019E: 84,
+ 0xE019F: 84,
+ 0xE01A0: 84,
+ 0xE01A1: 84,
+ 0xE01A2: 84,
+ 0xE01A3: 84,
+ 0xE01A4: 84,
+ 0xE01A5: 84,
+ 0xE01A6: 84,
+ 0xE01A7: 84,
+ 0xE01A8: 84,
+ 0xE01A9: 84,
+ 0xE01AA: 84,
+ 0xE01AB: 84,
+ 0xE01AC: 84,
+ 0xE01AD: 84,
+ 0xE01AE: 84,
+ 0xE01AF: 84,
+ 0xE01B0: 84,
+ 0xE01B1: 84,
+ 0xE01B2: 84,
+ 0xE01B3: 84,
+ 0xE01B4: 84,
+ 0xE01B5: 84,
+ 0xE01B6: 84,
+ 0xE01B7: 84,
+ 0xE01B8: 84,
+ 0xE01B9: 84,
+ 0xE01BA: 84,
+ 0xE01BB: 84,
+ 0xE01BC: 84,
+ 0xE01BD: 84,
+ 0xE01BE: 84,
+ 0xE01BF: 84,
+ 0xE01C0: 84,
+ 0xE01C1: 84,
+ 0xE01C2: 84,
+ 0xE01C3: 84,
+ 0xE01C4: 84,
+ 0xE01C5: 84,
+ 0xE01C6: 84,
+ 0xE01C7: 84,
+ 0xE01C8: 84,
+ 0xE01C9: 84,
+ 0xE01CA: 84,
+ 0xE01CB: 84,
+ 0xE01CC: 84,
+ 0xE01CD: 84,
+ 0xE01CE: 84,
+ 0xE01CF: 84,
+ 0xE01D0: 84,
+ 0xE01D1: 84,
+ 0xE01D2: 84,
+ 0xE01D3: 84,
+ 0xE01D4: 84,
+ 0xE01D5: 84,
+ 0xE01D6: 84,
+ 0xE01D7: 84,
+ 0xE01D8: 84,
+ 0xE01D9: 84,
+ 0xE01DA: 84,
+ 0xE01DB: 84,
+ 0xE01DC: 84,
+ 0xE01DD: 84,
+ 0xE01DE: 84,
+ 0xE01DF: 84,
+ 0xE01E0: 84,
+ 0xE01E1: 84,
+ 0xE01E2: 84,
+ 0xE01E3: 84,
+ 0xE01E4: 84,
+ 0xE01E5: 84,
+ 0xE01E6: 84,
+ 0xE01E7: 84,
+ 0xE01E8: 84,
+ 0xE01E9: 84,
+ 0xE01EA: 84,
+ 0xE01EB: 84,
+ 0xE01EC: 84,
+ 0xE01ED: 84,
+ 0xE01EE: 84,
+ 0xE01EF: 84,
+}
+codepoint_classes = {
+ "PVALID": (
+ 0x2D0000002E,
+ 0x300000003A,
+ 0x610000007B,
+ 0xDF000000F7,
+ 0xF800000100,
+ 0x10100000102,
+ 0x10300000104,
+ 0x10500000106,
+ 0x10700000108,
+ 0x1090000010A,
+ 0x10B0000010C,
+ 0x10D0000010E,
+ 0x10F00000110,
+ 0x11100000112,
+ 0x11300000114,
+ 0x11500000116,
+ 0x11700000118,
+ 0x1190000011A,
+ 0x11B0000011C,
+ 0x11D0000011E,
+ 0x11F00000120,
+ 0x12100000122,
+ 0x12300000124,
+ 0x12500000126,
+ 0x12700000128,
+ 0x1290000012A,
+ 0x12B0000012C,
+ 0x12D0000012E,
+ 0x12F00000130,
+ 0x13100000132,
+ 0x13500000136,
+ 0x13700000139,
+ 0x13A0000013B,
+ 0x13C0000013D,
+ 0x13E0000013F,
+ 0x14200000143,
+ 0x14400000145,
+ 0x14600000147,
+ 0x14800000149,
+ 0x14B0000014C,
+ 0x14D0000014E,
+ 0x14F00000150,
+ 0x15100000152,
+ 0x15300000154,
+ 0x15500000156,
+ 0x15700000158,
+ 0x1590000015A,
+ 0x15B0000015C,
+ 0x15D0000015E,
+ 0x15F00000160,
+ 0x16100000162,
+ 0x16300000164,
+ 0x16500000166,
+ 0x16700000168,
+ 0x1690000016A,
+ 0x16B0000016C,
+ 0x16D0000016E,
+ 0x16F00000170,
+ 0x17100000172,
+ 0x17300000174,
+ 0x17500000176,
+ 0x17700000178,
+ 0x17A0000017B,
+ 0x17C0000017D,
+ 0x17E0000017F,
+ 0x18000000181,
+ 0x18300000184,
+ 0x18500000186,
+ 0x18800000189,
+ 0x18C0000018E,
+ 0x19200000193,
+ 0x19500000196,
+ 0x1990000019C,
+ 0x19E0000019F,
+ 0x1A1000001A2,
+ 0x1A3000001A4,
+ 0x1A5000001A6,
+ 0x1A8000001A9,
+ 0x1AA000001AC,
+ 0x1AD000001AE,
+ 0x1B0000001B1,
+ 0x1B4000001B5,
+ 0x1B6000001B7,
+ 0x1B9000001BC,
+ 0x1BD000001C4,
+ 0x1CE000001CF,
+ 0x1D0000001D1,
+ 0x1D2000001D3,
+ 0x1D4000001D5,
+ 0x1D6000001D7,
+ 0x1D8000001D9,
+ 0x1DA000001DB,
+ 0x1DC000001DE,
+ 0x1DF000001E0,
+ 0x1E1000001E2,
+ 0x1E3000001E4,
+ 0x1E5000001E6,
+ 0x1E7000001E8,
+ 0x1E9000001EA,
+ 0x1EB000001EC,
+ 0x1ED000001EE,
+ 0x1EF000001F1,
+ 0x1F5000001F6,
+ 0x1F9000001FA,
+ 0x1FB000001FC,
+ 0x1FD000001FE,
+ 0x1FF00000200,
+ 0x20100000202,
+ 0x20300000204,
+ 0x20500000206,
+ 0x20700000208,
+ 0x2090000020A,
+ 0x20B0000020C,
+ 0x20D0000020E,
+ 0x20F00000210,
+ 0x21100000212,
+ 0x21300000214,
+ 0x21500000216,
+ 0x21700000218,
+ 0x2190000021A,
+ 0x21B0000021C,
+ 0x21D0000021E,
+ 0x21F00000220,
+ 0x22100000222,
+ 0x22300000224,
+ 0x22500000226,
+ 0x22700000228,
+ 0x2290000022A,
+ 0x22B0000022C,
+ 0x22D0000022E,
+ 0x22F00000230,
+ 0x23100000232,
+ 0x2330000023A,
+ 0x23C0000023D,
+ 0x23F00000241,
+ 0x24200000243,
+ 0x24700000248,
+ 0x2490000024A,
+ 0x24B0000024C,
+ 0x24D0000024E,
+ 0x24F000002B0,
+ 0x2B9000002C2,
+ 0x2C6000002D2,
+ 0x2EC000002ED,
+ 0x2EE000002EF,
+ 0x30000000340,
+ 0x34200000343,
+ 0x3460000034F,
+ 0x35000000370,
+ 0x37100000372,
+ 0x37300000374,
+ 0x37700000378,
+ 0x37B0000037E,
+ 0x39000000391,
+ 0x3AC000003CF,
+ 0x3D7000003D8,
+ 0x3D9000003DA,
+ 0x3DB000003DC,
+ 0x3DD000003DE,
+ 0x3DF000003E0,
+ 0x3E1000003E2,
+ 0x3E3000003E4,
+ 0x3E5000003E6,
+ 0x3E7000003E8,
+ 0x3E9000003EA,
+ 0x3EB000003EC,
+ 0x3ED000003EE,
+ 0x3EF000003F0,
+ 0x3F3000003F4,
+ 0x3F8000003F9,
+ 0x3FB000003FD,
+ 0x43000000460,
+ 0x46100000462,
+ 0x46300000464,
+ 0x46500000466,
+ 0x46700000468,
+ 0x4690000046A,
+ 0x46B0000046C,
+ 0x46D0000046E,
+ 0x46F00000470,
+ 0x47100000472,
+ 0x47300000474,
+ 0x47500000476,
+ 0x47700000478,
+ 0x4790000047A,
+ 0x47B0000047C,
+ 0x47D0000047E,
+ 0x47F00000480,
+ 0x48100000482,
+ 0x48300000488,
+ 0x48B0000048C,
+ 0x48D0000048E,
+ 0x48F00000490,
+ 0x49100000492,
+ 0x49300000494,
+ 0x49500000496,
+ 0x49700000498,
+ 0x4990000049A,
+ 0x49B0000049C,
+ 0x49D0000049E,
+ 0x49F000004A0,
+ 0x4A1000004A2,
+ 0x4A3000004A4,
+ 0x4A5000004A6,
+ 0x4A7000004A8,
+ 0x4A9000004AA,
+ 0x4AB000004AC,
+ 0x4AD000004AE,
+ 0x4AF000004B0,
+ 0x4B1000004B2,
+ 0x4B3000004B4,
+ 0x4B5000004B6,
+ 0x4B7000004B8,
+ 0x4B9000004BA,
+ 0x4BB000004BC,
+ 0x4BD000004BE,
+ 0x4BF000004C0,
+ 0x4C2000004C3,
+ 0x4C4000004C5,
+ 0x4C6000004C7,
+ 0x4C8000004C9,
+ 0x4CA000004CB,
+ 0x4CC000004CD,
+ 0x4CE000004D0,
+ 0x4D1000004D2,
+ 0x4D3000004D4,
+ 0x4D5000004D6,
+ 0x4D7000004D8,
+ 0x4D9000004DA,
+ 0x4DB000004DC,
+ 0x4DD000004DE,
+ 0x4DF000004E0,
+ 0x4E1000004E2,
+ 0x4E3000004E4,
+ 0x4E5000004E6,
+ 0x4E7000004E8,
+ 0x4E9000004EA,
+ 0x4EB000004EC,
+ 0x4ED000004EE,
+ 0x4EF000004F0,
+ 0x4F1000004F2,
+ 0x4F3000004F4,
+ 0x4F5000004F6,
+ 0x4F7000004F8,
+ 0x4F9000004FA,
+ 0x4FB000004FC,
+ 0x4FD000004FE,
+ 0x4FF00000500,
+ 0x50100000502,
+ 0x50300000504,
+ 0x50500000506,
+ 0x50700000508,
+ 0x5090000050A,
+ 0x50B0000050C,
+ 0x50D0000050E,
+ 0x50F00000510,
+ 0x51100000512,
+ 0x51300000514,
+ 0x51500000516,
+ 0x51700000518,
+ 0x5190000051A,
+ 0x51B0000051C,
+ 0x51D0000051E,
+ 0x51F00000520,
+ 0x52100000522,
+ 0x52300000524,
+ 0x52500000526,
+ 0x52700000528,
+ 0x5290000052A,
+ 0x52B0000052C,
+ 0x52D0000052E,
+ 0x52F00000530,
+ 0x5590000055A,
+ 0x56000000587,
+ 0x58800000589,
+ 0x591000005BE,
+ 0x5BF000005C0,
+ 0x5C1000005C3,
+ 0x5C4000005C6,
+ 0x5C7000005C8,
+ 0x5D0000005EB,
+ 0x5EF000005F3,
+ 0x6100000061B,
+ 0x62000000640,
+ 0x64100000660,
+ 0x66E00000675,
+ 0x679000006D4,
+ 0x6D5000006DD,
+ 0x6DF000006E9,
+ 0x6EA000006F0,
+ 0x6FA00000700,
+ 0x7100000074B,
+ 0x74D000007B2,
+ 0x7C0000007F6,
+ 0x7FD000007FE,
+ 0x8000000082E,
+ 0x8400000085C,
+ 0x8600000086B,
+ 0x87000000888,
+ 0x8890000088F,
+ 0x898000008E2,
+ 0x8E300000958,
+ 0x96000000964,
+ 0x96600000970,
+ 0x97100000984,
+ 0x9850000098D,
+ 0x98F00000991,
+ 0x993000009A9,
+ 0x9AA000009B1,
+ 0x9B2000009B3,
+ 0x9B6000009BA,
+ 0x9BC000009C5,
+ 0x9C7000009C9,
+ 0x9CB000009CF,
+ 0x9D7000009D8,
+ 0x9E0000009E4,
+ 0x9E6000009F2,
+ 0x9FC000009FD,
+ 0x9FE000009FF,
+ 0xA0100000A04,
+ 0xA0500000A0B,
+ 0xA0F00000A11,
+ 0xA1300000A29,
+ 0xA2A00000A31,
+ 0xA3200000A33,
+ 0xA3500000A36,
+ 0xA3800000A3A,
+ 0xA3C00000A3D,
+ 0xA3E00000A43,
+ 0xA4700000A49,
+ 0xA4B00000A4E,
+ 0xA5100000A52,
+ 0xA5C00000A5D,
+ 0xA6600000A76,
+ 0xA8100000A84,
+ 0xA8500000A8E,
+ 0xA8F00000A92,
+ 0xA9300000AA9,
+ 0xAAA00000AB1,
+ 0xAB200000AB4,
+ 0xAB500000ABA,
+ 0xABC00000AC6,
+ 0xAC700000ACA,
+ 0xACB00000ACE,
+ 0xAD000000AD1,
+ 0xAE000000AE4,
+ 0xAE600000AF0,
+ 0xAF900000B00,
+ 0xB0100000B04,
+ 0xB0500000B0D,
+ 0xB0F00000B11,
+ 0xB1300000B29,
+ 0xB2A00000B31,
+ 0xB3200000B34,
+ 0xB3500000B3A,
+ 0xB3C00000B45,
+ 0xB4700000B49,
+ 0xB4B00000B4E,
+ 0xB5500000B58,
+ 0xB5F00000B64,
+ 0xB6600000B70,
+ 0xB7100000B72,
+ 0xB8200000B84,
+ 0xB8500000B8B,
+ 0xB8E00000B91,
+ 0xB9200000B96,
+ 0xB9900000B9B,
+ 0xB9C00000B9D,
+ 0xB9E00000BA0,
+ 0xBA300000BA5,
+ 0xBA800000BAB,
+ 0xBAE00000BBA,
+ 0xBBE00000BC3,
+ 0xBC600000BC9,
+ 0xBCA00000BCE,
+ 0xBD000000BD1,
+ 0xBD700000BD8,
+ 0xBE600000BF0,
+ 0xC0000000C0D,
+ 0xC0E00000C11,
+ 0xC1200000C29,
+ 0xC2A00000C3A,
+ 0xC3C00000C45,
+ 0xC4600000C49,
+ 0xC4A00000C4E,
+ 0xC5500000C57,
+ 0xC5800000C5B,
+ 0xC5D00000C5E,
+ 0xC6000000C64,
+ 0xC6600000C70,
+ 0xC8000000C84,
+ 0xC8500000C8D,
+ 0xC8E00000C91,
+ 0xC9200000CA9,
+ 0xCAA00000CB4,
+ 0xCB500000CBA,
+ 0xCBC00000CC5,
+ 0xCC600000CC9,
+ 0xCCA00000CCE,
+ 0xCD500000CD7,
+ 0xCDD00000CDF,
+ 0xCE000000CE4,
+ 0xCE600000CF0,
+ 0xCF100000CF4,
+ 0xD0000000D0D,
+ 0xD0E00000D11,
+ 0xD1200000D45,
+ 0xD4600000D49,
+ 0xD4A00000D4F,
+ 0xD5400000D58,
+ 0xD5F00000D64,
+ 0xD6600000D70,
+ 0xD7A00000D80,
+ 0xD8100000D84,
+ 0xD8500000D97,
+ 0xD9A00000DB2,
+ 0xDB300000DBC,
+ 0xDBD00000DBE,
+ 0xDC000000DC7,
+ 0xDCA00000DCB,
+ 0xDCF00000DD5,
+ 0xDD600000DD7,
+ 0xDD800000DE0,
+ 0xDE600000DF0,
+ 0xDF200000DF4,
+ 0xE0100000E33,
+ 0xE3400000E3B,
+ 0xE4000000E4F,
+ 0xE5000000E5A,
+ 0xE8100000E83,
+ 0xE8400000E85,
+ 0xE8600000E8B,
+ 0xE8C00000EA4,
+ 0xEA500000EA6,
+ 0xEA700000EB3,
+ 0xEB400000EBE,
+ 0xEC000000EC5,
+ 0xEC600000EC7,
+ 0xEC800000ECF,
+ 0xED000000EDA,
+ 0xEDE00000EE0,
+ 0xF0000000F01,
+ 0xF0B00000F0C,
+ 0xF1800000F1A,
+ 0xF2000000F2A,
+ 0xF3500000F36,
+ 0xF3700000F38,
+ 0xF3900000F3A,
+ 0xF3E00000F43,
+ 0xF4400000F48,
+ 0xF4900000F4D,
+ 0xF4E00000F52,
+ 0xF5300000F57,
+ 0xF5800000F5C,
+ 0xF5D00000F69,
+ 0xF6A00000F6D,
+ 0xF7100000F73,
+ 0xF7400000F75,
+ 0xF7A00000F81,
+ 0xF8200000F85,
+ 0xF8600000F93,
+ 0xF9400000F98,
+ 0xF9900000F9D,
+ 0xF9E00000FA2,
+ 0xFA300000FA7,
+ 0xFA800000FAC,
+ 0xFAD00000FB9,
+ 0xFBA00000FBD,
+ 0xFC600000FC7,
+ 0x10000000104A,
+ 0x10500000109E,
+ 0x10D0000010FB,
+ 0x10FD00001100,
+ 0x120000001249,
+ 0x124A0000124E,
+ 0x125000001257,
+ 0x125800001259,
+ 0x125A0000125E,
+ 0x126000001289,
+ 0x128A0000128E,
+ 0x1290000012B1,
+ 0x12B2000012B6,
+ 0x12B8000012BF,
+ 0x12C0000012C1,
+ 0x12C2000012C6,
+ 0x12C8000012D7,
+ 0x12D800001311,
+ 0x131200001316,
+ 0x13180000135B,
+ 0x135D00001360,
+ 0x138000001390,
+ 0x13A0000013F6,
+ 0x14010000166D,
+ 0x166F00001680,
+ 0x16810000169B,
+ 0x16A0000016EB,
+ 0x16F1000016F9,
+ 0x170000001716,
+ 0x171F00001735,
+ 0x174000001754,
+ 0x17600000176D,
+ 0x176E00001771,
+ 0x177200001774,
+ 0x1780000017B4,
+ 0x17B6000017D4,
+ 0x17D7000017D8,
+ 0x17DC000017DE,
+ 0x17E0000017EA,
+ 0x18100000181A,
+ 0x182000001879,
+ 0x1880000018AB,
+ 0x18B0000018F6,
+ 0x19000000191F,
+ 0x19200000192C,
+ 0x19300000193C,
+ 0x19460000196E,
+ 0x197000001975,
+ 0x1980000019AC,
+ 0x19B0000019CA,
+ 0x19D0000019DA,
+ 0x1A0000001A1C,
+ 0x1A2000001A5F,
+ 0x1A6000001A7D,
+ 0x1A7F00001A8A,
+ 0x1A9000001A9A,
+ 0x1AA700001AA8,
+ 0x1AB000001ABE,
+ 0x1ABF00001ACF,
+ 0x1B0000001B4D,
+ 0x1B5000001B5A,
+ 0x1B6B00001B74,
+ 0x1B8000001BF4,
+ 0x1C0000001C38,
+ 0x1C4000001C4A,
+ 0x1C4D00001C7E,
+ 0x1CD000001CD3,
+ 0x1CD400001CFB,
+ 0x1D0000001D2C,
+ 0x1D2F00001D30,
+ 0x1D3B00001D3C,
+ 0x1D4E00001D4F,
+ 0x1D6B00001D78,
+ 0x1D7900001D9B,
+ 0x1DC000001E00,
+ 0x1E0100001E02,
+ 0x1E0300001E04,
+ 0x1E0500001E06,
+ 0x1E0700001E08,
+ 0x1E0900001E0A,
+ 0x1E0B00001E0C,
+ 0x1E0D00001E0E,
+ 0x1E0F00001E10,
+ 0x1E1100001E12,
+ 0x1E1300001E14,
+ 0x1E1500001E16,
+ 0x1E1700001E18,
+ 0x1E1900001E1A,
+ 0x1E1B00001E1C,
+ 0x1E1D00001E1E,
+ 0x1E1F00001E20,
+ 0x1E2100001E22,
+ 0x1E2300001E24,
+ 0x1E2500001E26,
+ 0x1E2700001E28,
+ 0x1E2900001E2A,
+ 0x1E2B00001E2C,
+ 0x1E2D00001E2E,
+ 0x1E2F00001E30,
+ 0x1E3100001E32,
+ 0x1E3300001E34,
+ 0x1E3500001E36,
+ 0x1E3700001E38,
+ 0x1E3900001E3A,
+ 0x1E3B00001E3C,
+ 0x1E3D00001E3E,
+ 0x1E3F00001E40,
+ 0x1E4100001E42,
+ 0x1E4300001E44,
+ 0x1E4500001E46,
+ 0x1E4700001E48,
+ 0x1E4900001E4A,
+ 0x1E4B00001E4C,
+ 0x1E4D00001E4E,
+ 0x1E4F00001E50,
+ 0x1E5100001E52,
+ 0x1E5300001E54,
+ 0x1E5500001E56,
+ 0x1E5700001E58,
+ 0x1E5900001E5A,
+ 0x1E5B00001E5C,
+ 0x1E5D00001E5E,
+ 0x1E5F00001E60,
+ 0x1E6100001E62,
+ 0x1E6300001E64,
+ 0x1E6500001E66,
+ 0x1E6700001E68,
+ 0x1E6900001E6A,
+ 0x1E6B00001E6C,
+ 0x1E6D00001E6E,
+ 0x1E6F00001E70,
+ 0x1E7100001E72,
+ 0x1E7300001E74,
+ 0x1E7500001E76,
+ 0x1E7700001E78,
+ 0x1E7900001E7A,
+ 0x1E7B00001E7C,
+ 0x1E7D00001E7E,
+ 0x1E7F00001E80,
+ 0x1E8100001E82,
+ 0x1E8300001E84,
+ 0x1E8500001E86,
+ 0x1E8700001E88,
+ 0x1E8900001E8A,
+ 0x1E8B00001E8C,
+ 0x1E8D00001E8E,
+ 0x1E8F00001E90,
+ 0x1E9100001E92,
+ 0x1E9300001E94,
+ 0x1E9500001E9A,
+ 0x1E9C00001E9E,
+ 0x1E9F00001EA0,
+ 0x1EA100001EA2,
+ 0x1EA300001EA4,
+ 0x1EA500001EA6,
+ 0x1EA700001EA8,
+ 0x1EA900001EAA,
+ 0x1EAB00001EAC,
+ 0x1EAD00001EAE,
+ 0x1EAF00001EB0,
+ 0x1EB100001EB2,
+ 0x1EB300001EB4,
+ 0x1EB500001EB6,
+ 0x1EB700001EB8,
+ 0x1EB900001EBA,
+ 0x1EBB00001EBC,
+ 0x1EBD00001EBE,
+ 0x1EBF00001EC0,
+ 0x1EC100001EC2,
+ 0x1EC300001EC4,
+ 0x1EC500001EC6,
+ 0x1EC700001EC8,
+ 0x1EC900001ECA,
+ 0x1ECB00001ECC,
+ 0x1ECD00001ECE,
+ 0x1ECF00001ED0,
+ 0x1ED100001ED2,
+ 0x1ED300001ED4,
+ 0x1ED500001ED6,
+ 0x1ED700001ED8,
+ 0x1ED900001EDA,
+ 0x1EDB00001EDC,
+ 0x1EDD00001EDE,
+ 0x1EDF00001EE0,
+ 0x1EE100001EE2,
+ 0x1EE300001EE4,
+ 0x1EE500001EE6,
+ 0x1EE700001EE8,
+ 0x1EE900001EEA,
+ 0x1EEB00001EEC,
+ 0x1EED00001EEE,
+ 0x1EEF00001EF0,
+ 0x1EF100001EF2,
+ 0x1EF300001EF4,
+ 0x1EF500001EF6,
+ 0x1EF700001EF8,
+ 0x1EF900001EFA,
+ 0x1EFB00001EFC,
+ 0x1EFD00001EFE,
+ 0x1EFF00001F08,
+ 0x1F1000001F16,
+ 0x1F2000001F28,
+ 0x1F3000001F38,
+ 0x1F4000001F46,
+ 0x1F5000001F58,
+ 0x1F6000001F68,
+ 0x1F7000001F71,
+ 0x1F7200001F73,
+ 0x1F7400001F75,
+ 0x1F7600001F77,
+ 0x1F7800001F79,
+ 0x1F7A00001F7B,
+ 0x1F7C00001F7D,
+ 0x1FB000001FB2,
+ 0x1FB600001FB7,
+ 0x1FC600001FC7,
+ 0x1FD000001FD3,
+ 0x1FD600001FD8,
+ 0x1FE000001FE3,
+ 0x1FE400001FE8,
+ 0x1FF600001FF7,
+ 0x214E0000214F,
+ 0x218400002185,
+ 0x2C3000002C60,
+ 0x2C6100002C62,
+ 0x2C6500002C67,
+ 0x2C6800002C69,
+ 0x2C6A00002C6B,
+ 0x2C6C00002C6D,
+ 0x2C7100002C72,
+ 0x2C7300002C75,
+ 0x2C7600002C7C,
+ 0x2C8100002C82,
+ 0x2C8300002C84,
+ 0x2C8500002C86,
+ 0x2C8700002C88,
+ 0x2C8900002C8A,
+ 0x2C8B00002C8C,
+ 0x2C8D00002C8E,
+ 0x2C8F00002C90,
+ 0x2C9100002C92,
+ 0x2C9300002C94,
+ 0x2C9500002C96,
+ 0x2C9700002C98,
+ 0x2C9900002C9A,
+ 0x2C9B00002C9C,
+ 0x2C9D00002C9E,
+ 0x2C9F00002CA0,
+ 0x2CA100002CA2,
+ 0x2CA300002CA4,
+ 0x2CA500002CA6,
+ 0x2CA700002CA8,
+ 0x2CA900002CAA,
+ 0x2CAB00002CAC,
+ 0x2CAD00002CAE,
+ 0x2CAF00002CB0,
+ 0x2CB100002CB2,
+ 0x2CB300002CB4,
+ 0x2CB500002CB6,
+ 0x2CB700002CB8,
+ 0x2CB900002CBA,
+ 0x2CBB00002CBC,
+ 0x2CBD00002CBE,
+ 0x2CBF00002CC0,
+ 0x2CC100002CC2,
+ 0x2CC300002CC4,
+ 0x2CC500002CC6,
+ 0x2CC700002CC8,
+ 0x2CC900002CCA,
+ 0x2CCB00002CCC,
+ 0x2CCD00002CCE,
+ 0x2CCF00002CD0,
+ 0x2CD100002CD2,
+ 0x2CD300002CD4,
+ 0x2CD500002CD6,
+ 0x2CD700002CD8,
+ 0x2CD900002CDA,
+ 0x2CDB00002CDC,
+ 0x2CDD00002CDE,
+ 0x2CDF00002CE0,
+ 0x2CE100002CE2,
+ 0x2CE300002CE5,
+ 0x2CEC00002CED,
+ 0x2CEE00002CF2,
+ 0x2CF300002CF4,
+ 0x2D0000002D26,
+ 0x2D2700002D28,
+ 0x2D2D00002D2E,
+ 0x2D3000002D68,
+ 0x2D7F00002D97,
+ 0x2DA000002DA7,
+ 0x2DA800002DAF,
+ 0x2DB000002DB7,
+ 0x2DB800002DBF,
+ 0x2DC000002DC7,
+ 0x2DC800002DCF,
+ 0x2DD000002DD7,
+ 0x2DD800002DDF,
+ 0x2DE000002E00,
+ 0x2E2F00002E30,
+ 0x300500003008,
+ 0x302A0000302E,
+ 0x303C0000303D,
+ 0x304100003097,
+ 0x30990000309B,
+ 0x309D0000309F,
+ 0x30A1000030FB,
+ 0x30FC000030FF,
+ 0x310500003130,
+ 0x31A0000031C0,
+ 0x31F000003200,
+ 0x340000004DC0,
+ 0x4E000000A48D,
+ 0xA4D00000A4FE,
+ 0xA5000000A60D,
+ 0xA6100000A62C,
+ 0xA6410000A642,
+ 0xA6430000A644,
+ 0xA6450000A646,
+ 0xA6470000A648,
+ 0xA6490000A64A,
+ 0xA64B0000A64C,
+ 0xA64D0000A64E,
+ 0xA64F0000A650,
+ 0xA6510000A652,
+ 0xA6530000A654,
+ 0xA6550000A656,
+ 0xA6570000A658,
+ 0xA6590000A65A,
+ 0xA65B0000A65C,
+ 0xA65D0000A65E,
+ 0xA65F0000A660,
+ 0xA6610000A662,
+ 0xA6630000A664,
+ 0xA6650000A666,
+ 0xA6670000A668,
+ 0xA6690000A66A,
+ 0xA66B0000A66C,
+ 0xA66D0000A670,
+ 0xA6740000A67E,
+ 0xA67F0000A680,
+ 0xA6810000A682,
+ 0xA6830000A684,
+ 0xA6850000A686,
+ 0xA6870000A688,
+ 0xA6890000A68A,
+ 0xA68B0000A68C,
+ 0xA68D0000A68E,
+ 0xA68F0000A690,
+ 0xA6910000A692,
+ 0xA6930000A694,
+ 0xA6950000A696,
+ 0xA6970000A698,
+ 0xA6990000A69A,
+ 0xA69B0000A69C,
+ 0xA69E0000A6E6,
+ 0xA6F00000A6F2,
+ 0xA7170000A720,
+ 0xA7230000A724,
+ 0xA7250000A726,
+ 0xA7270000A728,
+ 0xA7290000A72A,
+ 0xA72B0000A72C,
+ 0xA72D0000A72E,
+ 0xA72F0000A732,
+ 0xA7330000A734,
+ 0xA7350000A736,
+ 0xA7370000A738,
+ 0xA7390000A73A,
+ 0xA73B0000A73C,
+ 0xA73D0000A73E,
+ 0xA73F0000A740,
+ 0xA7410000A742,
+ 0xA7430000A744,
+ 0xA7450000A746,
+ 0xA7470000A748,
+ 0xA7490000A74A,
+ 0xA74B0000A74C,
+ 0xA74D0000A74E,
+ 0xA74F0000A750,
+ 0xA7510000A752,
+ 0xA7530000A754,
+ 0xA7550000A756,
+ 0xA7570000A758,
+ 0xA7590000A75A,
+ 0xA75B0000A75C,
+ 0xA75D0000A75E,
+ 0xA75F0000A760,
+ 0xA7610000A762,
+ 0xA7630000A764,
+ 0xA7650000A766,
+ 0xA7670000A768,
+ 0xA7690000A76A,
+ 0xA76B0000A76C,
+ 0xA76D0000A76E,
+ 0xA76F0000A770,
+ 0xA7710000A779,
+ 0xA77A0000A77B,
+ 0xA77C0000A77D,
+ 0xA77F0000A780,
+ 0xA7810000A782,
+ 0xA7830000A784,
+ 0xA7850000A786,
+ 0xA7870000A789,
+ 0xA78C0000A78D,
+ 0xA78E0000A790,
+ 0xA7910000A792,
+ 0xA7930000A796,
+ 0xA7970000A798,
+ 0xA7990000A79A,
+ 0xA79B0000A79C,
+ 0xA79D0000A79E,
+ 0xA79F0000A7A0,
+ 0xA7A10000A7A2,
+ 0xA7A30000A7A4,
+ 0xA7A50000A7A6,
+ 0xA7A70000A7A8,
+ 0xA7A90000A7AA,
+ 0xA7AF0000A7B0,
+ 0xA7B50000A7B6,
+ 0xA7B70000A7B8,
+ 0xA7B90000A7BA,
+ 0xA7BB0000A7BC,
+ 0xA7BD0000A7BE,
+ 0xA7BF0000A7C0,
+ 0xA7C10000A7C2,
+ 0xA7C30000A7C4,
+ 0xA7C80000A7C9,
+ 0xA7CA0000A7CB,
+ 0xA7D10000A7D2,
+ 0xA7D30000A7D4,
+ 0xA7D50000A7D6,
+ 0xA7D70000A7D8,
+ 0xA7D90000A7DA,
+ 0xA7F60000A7F8,
+ 0xA7FA0000A828,
+ 0xA82C0000A82D,
+ 0xA8400000A874,
+ 0xA8800000A8C6,
+ 0xA8D00000A8DA,
+ 0xA8E00000A8F8,
+ 0xA8FB0000A8FC,
+ 0xA8FD0000A92E,
+ 0xA9300000A954,
+ 0xA9800000A9C1,
+ 0xA9CF0000A9DA,
+ 0xA9E00000A9FF,
+ 0xAA000000AA37,
+ 0xAA400000AA4E,
+ 0xAA500000AA5A,
+ 0xAA600000AA77,
+ 0xAA7A0000AAC3,
+ 0xAADB0000AADE,
+ 0xAAE00000AAF0,
+ 0xAAF20000AAF7,
+ 0xAB010000AB07,
+ 0xAB090000AB0F,
+ 0xAB110000AB17,
+ 0xAB200000AB27,
+ 0xAB280000AB2F,
+ 0xAB300000AB5B,
+ 0xAB600000AB69,
+ 0xABC00000ABEB,
+ 0xABEC0000ABEE,
+ 0xABF00000ABFA,
+ 0xAC000000D7A4,
+ 0xFA0E0000FA10,
+ 0xFA110000FA12,
+ 0xFA130000FA15,
+ 0xFA1F0000FA20,
+ 0xFA210000FA22,
+ 0xFA230000FA25,
+ 0xFA270000FA2A,
+ 0xFB1E0000FB1F,
+ 0xFE200000FE30,
+ 0xFE730000FE74,
+ 0x100000001000C,
+ 0x1000D00010027,
+ 0x100280001003B,
+ 0x1003C0001003E,
+ 0x1003F0001004E,
+ 0x100500001005E,
+ 0x10080000100FB,
+ 0x101FD000101FE,
+ 0x102800001029D,
+ 0x102A0000102D1,
+ 0x102E0000102E1,
+ 0x1030000010320,
+ 0x1032D00010341,
+ 0x103420001034A,
+ 0x103500001037B,
+ 0x103800001039E,
+ 0x103A0000103C4,
+ 0x103C8000103D0,
+ 0x104280001049E,
+ 0x104A0000104AA,
+ 0x104D8000104FC,
+ 0x1050000010528,
+ 0x1053000010564,
+ 0x10597000105A2,
+ 0x105A3000105B2,
+ 0x105B3000105BA,
+ 0x105BB000105BD,
+ 0x1060000010737,
+ 0x1074000010756,
+ 0x1076000010768,
+ 0x1078000010781,
+ 0x1080000010806,
+ 0x1080800010809,
+ 0x1080A00010836,
+ 0x1083700010839,
+ 0x1083C0001083D,
+ 0x1083F00010856,
+ 0x1086000010877,
+ 0x108800001089F,
+ 0x108E0000108F3,
+ 0x108F4000108F6,
+ 0x1090000010916,
+ 0x109200001093A,
+ 0x10980000109B8,
+ 0x109BE000109C0,
+ 0x10A0000010A04,
+ 0x10A0500010A07,
+ 0x10A0C00010A14,
+ 0x10A1500010A18,
+ 0x10A1900010A36,
+ 0x10A3800010A3B,
+ 0x10A3F00010A40,
+ 0x10A6000010A7D,
+ 0x10A8000010A9D,
+ 0x10AC000010AC8,
+ 0x10AC900010AE7,
+ 0x10B0000010B36,
+ 0x10B4000010B56,
+ 0x10B6000010B73,
+ 0x10B8000010B92,
+ 0x10C0000010C49,
+ 0x10CC000010CF3,
+ 0x10D0000010D28,
+ 0x10D3000010D3A,
+ 0x10E8000010EAA,
+ 0x10EAB00010EAD,
+ 0x10EB000010EB2,
+ 0x10EFD00010F1D,
+ 0x10F2700010F28,
+ 0x10F3000010F51,
+ 0x10F7000010F86,
+ 0x10FB000010FC5,
+ 0x10FE000010FF7,
+ 0x1100000011047,
+ 0x1106600011076,
+ 0x1107F000110BB,
+ 0x110C2000110C3,
+ 0x110D0000110E9,
+ 0x110F0000110FA,
+ 0x1110000011135,
+ 0x1113600011140,
+ 0x1114400011148,
+ 0x1115000011174,
+ 0x1117600011177,
+ 0x11180000111C5,
+ 0x111C9000111CD,
+ 0x111CE000111DB,
+ 0x111DC000111DD,
+ 0x1120000011212,
+ 0x1121300011238,
+ 0x1123E00011242,
+ 0x1128000011287,
+ 0x1128800011289,
+ 0x1128A0001128E,
+ 0x1128F0001129E,
+ 0x1129F000112A9,
+ 0x112B0000112EB,
+ 0x112F0000112FA,
+ 0x1130000011304,
+ 0x113050001130D,
+ 0x1130F00011311,
+ 0x1131300011329,
+ 0x1132A00011331,
+ 0x1133200011334,
+ 0x113350001133A,
+ 0x1133B00011345,
+ 0x1134700011349,
+ 0x1134B0001134E,
+ 0x1135000011351,
+ 0x1135700011358,
+ 0x1135D00011364,
+ 0x113660001136D,
+ 0x1137000011375,
+ 0x114000001144B,
+ 0x114500001145A,
+ 0x1145E00011462,
+ 0x11480000114C6,
+ 0x114C7000114C8,
+ 0x114D0000114DA,
+ 0x11580000115B6,
+ 0x115B8000115C1,
+ 0x115D8000115DE,
+ 0x1160000011641,
+ 0x1164400011645,
+ 0x116500001165A,
+ 0x11680000116B9,
+ 0x116C0000116CA,
+ 0x117000001171B,
+ 0x1171D0001172C,
+ 0x117300001173A,
+ 0x1174000011747,
+ 0x118000001183B,
+ 0x118C0000118EA,
+ 0x118FF00011907,
+ 0x119090001190A,
+ 0x1190C00011914,
+ 0x1191500011917,
+ 0x1191800011936,
+ 0x1193700011939,
+ 0x1193B00011944,
+ 0x119500001195A,
+ 0x119A0000119A8,
+ 0x119AA000119D8,
+ 0x119DA000119E2,
+ 0x119E3000119E5,
+ 0x11A0000011A3F,
+ 0x11A4700011A48,
+ 0x11A5000011A9A,
+ 0x11A9D00011A9E,
+ 0x11AB000011AF9,
+ 0x11C0000011C09,
+ 0x11C0A00011C37,
+ 0x11C3800011C41,
+ 0x11C5000011C5A,
+ 0x11C7200011C90,
+ 0x11C9200011CA8,
+ 0x11CA900011CB7,
+ 0x11D0000011D07,
+ 0x11D0800011D0A,
+ 0x11D0B00011D37,
+ 0x11D3A00011D3B,
+ 0x11D3C00011D3E,
+ 0x11D3F00011D48,
+ 0x11D5000011D5A,
+ 0x11D6000011D66,
+ 0x11D6700011D69,
+ 0x11D6A00011D8F,
+ 0x11D9000011D92,
+ 0x11D9300011D99,
+ 0x11DA000011DAA,
+ 0x11EE000011EF7,
+ 0x11F0000011F11,
+ 0x11F1200011F3B,
+ 0x11F3E00011F43,
+ 0x11F5000011F5A,
+ 0x11FB000011FB1,
+ 0x120000001239A,
+ 0x1248000012544,
+ 0x12F9000012FF1,
+ 0x1300000013430,
+ 0x1344000013456,
+ 0x1440000014647,
+ 0x1680000016A39,
+ 0x16A4000016A5F,
+ 0x16A6000016A6A,
+ 0x16A7000016ABF,
+ 0x16AC000016ACA,
+ 0x16AD000016AEE,
+ 0x16AF000016AF5,
+ 0x16B0000016B37,
+ 0x16B4000016B44,
+ 0x16B5000016B5A,
+ 0x16B6300016B78,
+ 0x16B7D00016B90,
+ 0x16E6000016E80,
+ 0x16F0000016F4B,
+ 0x16F4F00016F88,
+ 0x16F8F00016FA0,
+ 0x16FE000016FE2,
+ 0x16FE300016FE5,
+ 0x16FF000016FF2,
+ 0x17000000187F8,
+ 0x1880000018CD6,
+ 0x18D0000018D09,
+ 0x1AFF00001AFF4,
+ 0x1AFF50001AFFC,
+ 0x1AFFD0001AFFF,
+ 0x1B0000001B123,
+ 0x1B1320001B133,
+ 0x1B1500001B153,
+ 0x1B1550001B156,
+ 0x1B1640001B168,
+ 0x1B1700001B2FC,
+ 0x1BC000001BC6B,
+ 0x1BC700001BC7D,
+ 0x1BC800001BC89,
+ 0x1BC900001BC9A,
+ 0x1BC9D0001BC9F,
+ 0x1CF000001CF2E,
+ 0x1CF300001CF47,
+ 0x1DA000001DA37,
+ 0x1DA3B0001DA6D,
+ 0x1DA750001DA76,
+ 0x1DA840001DA85,
+ 0x1DA9B0001DAA0,
+ 0x1DAA10001DAB0,
+ 0x1DF000001DF1F,
+ 0x1DF250001DF2B,
+ 0x1E0000001E007,
+ 0x1E0080001E019,
+ 0x1E01B0001E022,
+ 0x1E0230001E025,
+ 0x1E0260001E02B,
+ 0x1E08F0001E090,
+ 0x1E1000001E12D,
+ 0x1E1300001E13E,
+ 0x1E1400001E14A,
+ 0x1E14E0001E14F,
+ 0x1E2900001E2AF,
+ 0x1E2C00001E2FA,
+ 0x1E4D00001E4FA,
+ 0x1E7E00001E7E7,
+ 0x1E7E80001E7EC,
+ 0x1E7ED0001E7EF,
+ 0x1E7F00001E7FF,
+ 0x1E8000001E8C5,
+ 0x1E8D00001E8D7,
+ 0x1E9220001E94C,
+ 0x1E9500001E95A,
+ 0x200000002A6E0,
+ 0x2A7000002B73A,
+ 0x2B7400002B81E,
+ 0x2B8200002CEA2,
+ 0x2CEB00002EBE1,
+ 0x2EBF00002EE5E,
+ 0x300000003134B,
+ 0x31350000323B0,
+ ),
+ "CONTEXTJ": (0x200C0000200E,),
+ "CONTEXTO": (
+ 0xB7000000B8,
+ 0x37500000376,
+ 0x5F3000005F5,
+ 0x6600000066A,
+ 0x6F0000006FA,
+ 0x30FB000030FC,
+ ),
+}
diff --git a/env/Lib/site-packages/idna/intranges.py b/env/Lib/site-packages/idna/intranges.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bfaa8d80d7dc471d572db0f949460901126e8bd
--- /dev/null
+++ b/env/Lib/site-packages/idna/intranges.py
@@ -0,0 +1,57 @@
+"""
+Given a list of integers, made up of (hopefully) a small number of long runs
+of consecutive integers, compute a representation of the form
+((start1, end1), (start2, end2) ...). Then answer the question "was x present
+in the original list?" in time O(log(# runs)).
+"""
+
+import bisect
+from typing import List, Tuple
+
+
+def intranges_from_list(list_: List[int]) -> Tuple[int, ...]:
+ """Represent a list of integers as a sequence of ranges:
+ ((start_0, end_0), (start_1, end_1), ...), such that the original
+ integers are exactly those x such that start_i <= x < end_i for some i.
+
+ Ranges are encoded as single integers (start << 32 | end), not as tuples.
+ """
+
+ sorted_list = sorted(list_)
+ ranges = []
+ last_write = -1
+ for i in range(len(sorted_list)):
+ if i + 1 < len(sorted_list):
+ if sorted_list[i] == sorted_list[i + 1] - 1:
+ continue
+ current_range = sorted_list[last_write + 1 : i + 1]
+ ranges.append(_encode_range(current_range[0], current_range[-1] + 1))
+ last_write = i
+
+ return tuple(ranges)
+
+
+def _encode_range(start: int, end: int) -> int:
+ return (start << 32) | end
+
+
+def _decode_range(r: int) -> Tuple[int, int]:
+ return (r >> 32), (r & ((1 << 32) - 1))
+
+
+def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool:
+ """Determine if `int_` falls into one of the ranges in `ranges`."""
+ tuple_ = _encode_range(int_, 0)
+ pos = bisect.bisect_left(ranges, tuple_)
+ # we could be immediately ahead of a tuple (start, end)
+ # with start < int_ <= end
+ if pos > 0:
+ left, right = _decode_range(ranges[pos - 1])
+ if left <= int_ < right:
+ return True
+ # or we could be immediately behind a tuple (int_, end)
+ if pos < len(ranges):
+ left, _ = _decode_range(ranges[pos])
+ if left == int_:
+ return True
+ return False
diff --git a/env/Lib/site-packages/idna/package_data.py b/env/Lib/site-packages/idna/package_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..514ff7e2e68b65f309d30a0b06e6b290d2c353a8
--- /dev/null
+++ b/env/Lib/site-packages/idna/package_data.py
@@ -0,0 +1 @@
+__version__ = "3.10"
diff --git a/env/Lib/site-packages/idna/py.typed b/env/Lib/site-packages/idna/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/idna/uts46data.py b/env/Lib/site-packages/idna/uts46data.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb894327410debecb64ddf40eddc3131cf8344de
--- /dev/null
+++ b/env/Lib/site-packages/idna/uts46data.py
@@ -0,0 +1,8681 @@
+# This file is automatically generated by tools/idna-data
+# vim: set fileencoding=utf-8 :
+
+from typing import List, Tuple, Union
+
+"""IDNA Mapping Table from UTS46."""
+
+
+__version__ = "15.1.0"
+
+
+def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x0, "3"),
+ (0x1, "3"),
+ (0x2, "3"),
+ (0x3, "3"),
+ (0x4, "3"),
+ (0x5, "3"),
+ (0x6, "3"),
+ (0x7, "3"),
+ (0x8, "3"),
+ (0x9, "3"),
+ (0xA, "3"),
+ (0xB, "3"),
+ (0xC, "3"),
+ (0xD, "3"),
+ (0xE, "3"),
+ (0xF, "3"),
+ (0x10, "3"),
+ (0x11, "3"),
+ (0x12, "3"),
+ (0x13, "3"),
+ (0x14, "3"),
+ (0x15, "3"),
+ (0x16, "3"),
+ (0x17, "3"),
+ (0x18, "3"),
+ (0x19, "3"),
+ (0x1A, "3"),
+ (0x1B, "3"),
+ (0x1C, "3"),
+ (0x1D, "3"),
+ (0x1E, "3"),
+ (0x1F, "3"),
+ (0x20, "3"),
+ (0x21, "3"),
+ (0x22, "3"),
+ (0x23, "3"),
+ (0x24, "3"),
+ (0x25, "3"),
+ (0x26, "3"),
+ (0x27, "3"),
+ (0x28, "3"),
+ (0x29, "3"),
+ (0x2A, "3"),
+ (0x2B, "3"),
+ (0x2C, "3"),
+ (0x2D, "V"),
+ (0x2E, "V"),
+ (0x2F, "3"),
+ (0x30, "V"),
+ (0x31, "V"),
+ (0x32, "V"),
+ (0x33, "V"),
+ (0x34, "V"),
+ (0x35, "V"),
+ (0x36, "V"),
+ (0x37, "V"),
+ (0x38, "V"),
+ (0x39, "V"),
+ (0x3A, "3"),
+ (0x3B, "3"),
+ (0x3C, "3"),
+ (0x3D, "3"),
+ (0x3E, "3"),
+ (0x3F, "3"),
+ (0x40, "3"),
+ (0x41, "M", "a"),
+ (0x42, "M", "b"),
+ (0x43, "M", "c"),
+ (0x44, "M", "d"),
+ (0x45, "M", "e"),
+ (0x46, "M", "f"),
+ (0x47, "M", "g"),
+ (0x48, "M", "h"),
+ (0x49, "M", "i"),
+ (0x4A, "M", "j"),
+ (0x4B, "M", "k"),
+ (0x4C, "M", "l"),
+ (0x4D, "M", "m"),
+ (0x4E, "M", "n"),
+ (0x4F, "M", "o"),
+ (0x50, "M", "p"),
+ (0x51, "M", "q"),
+ (0x52, "M", "r"),
+ (0x53, "M", "s"),
+ (0x54, "M", "t"),
+ (0x55, "M", "u"),
+ (0x56, "M", "v"),
+ (0x57, "M", "w"),
+ (0x58, "M", "x"),
+ (0x59, "M", "y"),
+ (0x5A, "M", "z"),
+ (0x5B, "3"),
+ (0x5C, "3"),
+ (0x5D, "3"),
+ (0x5E, "3"),
+ (0x5F, "3"),
+ (0x60, "3"),
+ (0x61, "V"),
+ (0x62, "V"),
+ (0x63, "V"),
+ ]
+
+
+def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x64, "V"),
+ (0x65, "V"),
+ (0x66, "V"),
+ (0x67, "V"),
+ (0x68, "V"),
+ (0x69, "V"),
+ (0x6A, "V"),
+ (0x6B, "V"),
+ (0x6C, "V"),
+ (0x6D, "V"),
+ (0x6E, "V"),
+ (0x6F, "V"),
+ (0x70, "V"),
+ (0x71, "V"),
+ (0x72, "V"),
+ (0x73, "V"),
+ (0x74, "V"),
+ (0x75, "V"),
+ (0x76, "V"),
+ (0x77, "V"),
+ (0x78, "V"),
+ (0x79, "V"),
+ (0x7A, "V"),
+ (0x7B, "3"),
+ (0x7C, "3"),
+ (0x7D, "3"),
+ (0x7E, "3"),
+ (0x7F, "3"),
+ (0x80, "X"),
+ (0x81, "X"),
+ (0x82, "X"),
+ (0x83, "X"),
+ (0x84, "X"),
+ (0x85, "X"),
+ (0x86, "X"),
+ (0x87, "X"),
+ (0x88, "X"),
+ (0x89, "X"),
+ (0x8A, "X"),
+ (0x8B, "X"),
+ (0x8C, "X"),
+ (0x8D, "X"),
+ (0x8E, "X"),
+ (0x8F, "X"),
+ (0x90, "X"),
+ (0x91, "X"),
+ (0x92, "X"),
+ (0x93, "X"),
+ (0x94, "X"),
+ (0x95, "X"),
+ (0x96, "X"),
+ (0x97, "X"),
+ (0x98, "X"),
+ (0x99, "X"),
+ (0x9A, "X"),
+ (0x9B, "X"),
+ (0x9C, "X"),
+ (0x9D, "X"),
+ (0x9E, "X"),
+ (0x9F, "X"),
+ (0xA0, "3", " "),
+ (0xA1, "V"),
+ (0xA2, "V"),
+ (0xA3, "V"),
+ (0xA4, "V"),
+ (0xA5, "V"),
+ (0xA6, "V"),
+ (0xA7, "V"),
+ (0xA8, "3", " ̈"),
+ (0xA9, "V"),
+ (0xAA, "M", "a"),
+ (0xAB, "V"),
+ (0xAC, "V"),
+ (0xAD, "I"),
+ (0xAE, "V"),
+ (0xAF, "3", " ̄"),
+ (0xB0, "V"),
+ (0xB1, "V"),
+ (0xB2, "M", "2"),
+ (0xB3, "M", "3"),
+ (0xB4, "3", " ́"),
+ (0xB5, "M", "μ"),
+ (0xB6, "V"),
+ (0xB7, "V"),
+ (0xB8, "3", " ̧"),
+ (0xB9, "M", "1"),
+ (0xBA, "M", "o"),
+ (0xBB, "V"),
+ (0xBC, "M", "1⁄4"),
+ (0xBD, "M", "1⁄2"),
+ (0xBE, "M", "3⁄4"),
+ (0xBF, "V"),
+ (0xC0, "M", "à"),
+ (0xC1, "M", "á"),
+ (0xC2, "M", "â"),
+ (0xC3, "M", "ã"),
+ (0xC4, "M", "ä"),
+ (0xC5, "M", "å"),
+ (0xC6, "M", "æ"),
+ (0xC7, "M", "ç"),
+ ]
+
+
+def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xC8, "M", "è"),
+ (0xC9, "M", "é"),
+ (0xCA, "M", "ê"),
+ (0xCB, "M", "ë"),
+ (0xCC, "M", "ì"),
+ (0xCD, "M", "í"),
+ (0xCE, "M", "î"),
+ (0xCF, "M", "ï"),
+ (0xD0, "M", "ð"),
+ (0xD1, "M", "ñ"),
+ (0xD2, "M", "ò"),
+ (0xD3, "M", "ó"),
+ (0xD4, "M", "ô"),
+ (0xD5, "M", "õ"),
+ (0xD6, "M", "ö"),
+ (0xD7, "V"),
+ (0xD8, "M", "ø"),
+ (0xD9, "M", "ù"),
+ (0xDA, "M", "ú"),
+ (0xDB, "M", "û"),
+ (0xDC, "M", "ü"),
+ (0xDD, "M", "ý"),
+ (0xDE, "M", "þ"),
+ (0xDF, "D", "ss"),
+ (0xE0, "V"),
+ (0xE1, "V"),
+ (0xE2, "V"),
+ (0xE3, "V"),
+ (0xE4, "V"),
+ (0xE5, "V"),
+ (0xE6, "V"),
+ (0xE7, "V"),
+ (0xE8, "V"),
+ (0xE9, "V"),
+ (0xEA, "V"),
+ (0xEB, "V"),
+ (0xEC, "V"),
+ (0xED, "V"),
+ (0xEE, "V"),
+ (0xEF, "V"),
+ (0xF0, "V"),
+ (0xF1, "V"),
+ (0xF2, "V"),
+ (0xF3, "V"),
+ (0xF4, "V"),
+ (0xF5, "V"),
+ (0xF6, "V"),
+ (0xF7, "V"),
+ (0xF8, "V"),
+ (0xF9, "V"),
+ (0xFA, "V"),
+ (0xFB, "V"),
+ (0xFC, "V"),
+ (0xFD, "V"),
+ (0xFE, "V"),
+ (0xFF, "V"),
+ (0x100, "M", "ā"),
+ (0x101, "V"),
+ (0x102, "M", "ă"),
+ (0x103, "V"),
+ (0x104, "M", "ą"),
+ (0x105, "V"),
+ (0x106, "M", "ć"),
+ (0x107, "V"),
+ (0x108, "M", "ĉ"),
+ (0x109, "V"),
+ (0x10A, "M", "ċ"),
+ (0x10B, "V"),
+ (0x10C, "M", "č"),
+ (0x10D, "V"),
+ (0x10E, "M", "ď"),
+ (0x10F, "V"),
+ (0x110, "M", "đ"),
+ (0x111, "V"),
+ (0x112, "M", "ē"),
+ (0x113, "V"),
+ (0x114, "M", "ĕ"),
+ (0x115, "V"),
+ (0x116, "M", "ė"),
+ (0x117, "V"),
+ (0x118, "M", "ę"),
+ (0x119, "V"),
+ (0x11A, "M", "ě"),
+ (0x11B, "V"),
+ (0x11C, "M", "ĝ"),
+ (0x11D, "V"),
+ (0x11E, "M", "ğ"),
+ (0x11F, "V"),
+ (0x120, "M", "ġ"),
+ (0x121, "V"),
+ (0x122, "M", "ģ"),
+ (0x123, "V"),
+ (0x124, "M", "ĥ"),
+ (0x125, "V"),
+ (0x126, "M", "ħ"),
+ (0x127, "V"),
+ (0x128, "M", "ĩ"),
+ (0x129, "V"),
+ (0x12A, "M", "ī"),
+ (0x12B, "V"),
+ ]
+
+
+def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x12C, "M", "ĭ"),
+ (0x12D, "V"),
+ (0x12E, "M", "į"),
+ (0x12F, "V"),
+ (0x130, "M", "i̇"),
+ (0x131, "V"),
+ (0x132, "M", "ij"),
+ (0x134, "M", "ĵ"),
+ (0x135, "V"),
+ (0x136, "M", "ķ"),
+ (0x137, "V"),
+ (0x139, "M", "ĺ"),
+ (0x13A, "V"),
+ (0x13B, "M", "ļ"),
+ (0x13C, "V"),
+ (0x13D, "M", "ľ"),
+ (0x13E, "V"),
+ (0x13F, "M", "l·"),
+ (0x141, "M", "ł"),
+ (0x142, "V"),
+ (0x143, "M", "ń"),
+ (0x144, "V"),
+ (0x145, "M", "ņ"),
+ (0x146, "V"),
+ (0x147, "M", "ň"),
+ (0x148, "V"),
+ (0x149, "M", "ʼn"),
+ (0x14A, "M", "ŋ"),
+ (0x14B, "V"),
+ (0x14C, "M", "ō"),
+ (0x14D, "V"),
+ (0x14E, "M", "ŏ"),
+ (0x14F, "V"),
+ (0x150, "M", "ő"),
+ (0x151, "V"),
+ (0x152, "M", "œ"),
+ (0x153, "V"),
+ (0x154, "M", "ŕ"),
+ (0x155, "V"),
+ (0x156, "M", "ŗ"),
+ (0x157, "V"),
+ (0x158, "M", "ř"),
+ (0x159, "V"),
+ (0x15A, "M", "ś"),
+ (0x15B, "V"),
+ (0x15C, "M", "ŝ"),
+ (0x15D, "V"),
+ (0x15E, "M", "ş"),
+ (0x15F, "V"),
+ (0x160, "M", "š"),
+ (0x161, "V"),
+ (0x162, "M", "ţ"),
+ (0x163, "V"),
+ (0x164, "M", "ť"),
+ (0x165, "V"),
+ (0x166, "M", "ŧ"),
+ (0x167, "V"),
+ (0x168, "M", "ũ"),
+ (0x169, "V"),
+ (0x16A, "M", "ū"),
+ (0x16B, "V"),
+ (0x16C, "M", "ŭ"),
+ (0x16D, "V"),
+ (0x16E, "M", "ů"),
+ (0x16F, "V"),
+ (0x170, "M", "ű"),
+ (0x171, "V"),
+ (0x172, "M", "ų"),
+ (0x173, "V"),
+ (0x174, "M", "ŵ"),
+ (0x175, "V"),
+ (0x176, "M", "ŷ"),
+ (0x177, "V"),
+ (0x178, "M", "ÿ"),
+ (0x179, "M", "ź"),
+ (0x17A, "V"),
+ (0x17B, "M", "ż"),
+ (0x17C, "V"),
+ (0x17D, "M", "ž"),
+ (0x17E, "V"),
+ (0x17F, "M", "s"),
+ (0x180, "V"),
+ (0x181, "M", "ɓ"),
+ (0x182, "M", "ƃ"),
+ (0x183, "V"),
+ (0x184, "M", "ƅ"),
+ (0x185, "V"),
+ (0x186, "M", "ɔ"),
+ (0x187, "M", "ƈ"),
+ (0x188, "V"),
+ (0x189, "M", "ɖ"),
+ (0x18A, "M", "ɗ"),
+ (0x18B, "M", "ƌ"),
+ (0x18C, "V"),
+ (0x18E, "M", "ǝ"),
+ (0x18F, "M", "ə"),
+ (0x190, "M", "ɛ"),
+ (0x191, "M", "ƒ"),
+ (0x192, "V"),
+ (0x193, "M", "ɠ"),
+ ]
+
+
+def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x194, "M", "ɣ"),
+ (0x195, "V"),
+ (0x196, "M", "ɩ"),
+ (0x197, "M", "ɨ"),
+ (0x198, "M", "ƙ"),
+ (0x199, "V"),
+ (0x19C, "M", "ɯ"),
+ (0x19D, "M", "ɲ"),
+ (0x19E, "V"),
+ (0x19F, "M", "ɵ"),
+ (0x1A0, "M", "ơ"),
+ (0x1A1, "V"),
+ (0x1A2, "M", "ƣ"),
+ (0x1A3, "V"),
+ (0x1A4, "M", "ƥ"),
+ (0x1A5, "V"),
+ (0x1A6, "M", "ʀ"),
+ (0x1A7, "M", "ƨ"),
+ (0x1A8, "V"),
+ (0x1A9, "M", "ʃ"),
+ (0x1AA, "V"),
+ (0x1AC, "M", "ƭ"),
+ (0x1AD, "V"),
+ (0x1AE, "M", "ʈ"),
+ (0x1AF, "M", "ư"),
+ (0x1B0, "V"),
+ (0x1B1, "M", "ʊ"),
+ (0x1B2, "M", "ʋ"),
+ (0x1B3, "M", "ƴ"),
+ (0x1B4, "V"),
+ (0x1B5, "M", "ƶ"),
+ (0x1B6, "V"),
+ (0x1B7, "M", "ʒ"),
+ (0x1B8, "M", "ƹ"),
+ (0x1B9, "V"),
+ (0x1BC, "M", "ƽ"),
+ (0x1BD, "V"),
+ (0x1C4, "M", "dž"),
+ (0x1C7, "M", "lj"),
+ (0x1CA, "M", "nj"),
+ (0x1CD, "M", "ǎ"),
+ (0x1CE, "V"),
+ (0x1CF, "M", "ǐ"),
+ (0x1D0, "V"),
+ (0x1D1, "M", "ǒ"),
+ (0x1D2, "V"),
+ (0x1D3, "M", "ǔ"),
+ (0x1D4, "V"),
+ (0x1D5, "M", "ǖ"),
+ (0x1D6, "V"),
+ (0x1D7, "M", "ǘ"),
+ (0x1D8, "V"),
+ (0x1D9, "M", "ǚ"),
+ (0x1DA, "V"),
+ (0x1DB, "M", "ǜ"),
+ (0x1DC, "V"),
+ (0x1DE, "M", "ǟ"),
+ (0x1DF, "V"),
+ (0x1E0, "M", "ǡ"),
+ (0x1E1, "V"),
+ (0x1E2, "M", "ǣ"),
+ (0x1E3, "V"),
+ (0x1E4, "M", "ǥ"),
+ (0x1E5, "V"),
+ (0x1E6, "M", "ǧ"),
+ (0x1E7, "V"),
+ (0x1E8, "M", "ǩ"),
+ (0x1E9, "V"),
+ (0x1EA, "M", "ǫ"),
+ (0x1EB, "V"),
+ (0x1EC, "M", "ǭ"),
+ (0x1ED, "V"),
+ (0x1EE, "M", "ǯ"),
+ (0x1EF, "V"),
+ (0x1F1, "M", "dz"),
+ (0x1F4, "M", "ǵ"),
+ (0x1F5, "V"),
+ (0x1F6, "M", "ƕ"),
+ (0x1F7, "M", "ƿ"),
+ (0x1F8, "M", "ǹ"),
+ (0x1F9, "V"),
+ (0x1FA, "M", "ǻ"),
+ (0x1FB, "V"),
+ (0x1FC, "M", "ǽ"),
+ (0x1FD, "V"),
+ (0x1FE, "M", "ǿ"),
+ (0x1FF, "V"),
+ (0x200, "M", "ȁ"),
+ (0x201, "V"),
+ (0x202, "M", "ȃ"),
+ (0x203, "V"),
+ (0x204, "M", "ȅ"),
+ (0x205, "V"),
+ (0x206, "M", "ȇ"),
+ (0x207, "V"),
+ (0x208, "M", "ȉ"),
+ (0x209, "V"),
+ (0x20A, "M", "ȋ"),
+ (0x20B, "V"),
+ (0x20C, "M", "ȍ"),
+ ]
+
+
+def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x20D, "V"),
+ (0x20E, "M", "ȏ"),
+ (0x20F, "V"),
+ (0x210, "M", "ȑ"),
+ (0x211, "V"),
+ (0x212, "M", "ȓ"),
+ (0x213, "V"),
+ (0x214, "M", "ȕ"),
+ (0x215, "V"),
+ (0x216, "M", "ȗ"),
+ (0x217, "V"),
+ (0x218, "M", "ș"),
+ (0x219, "V"),
+ (0x21A, "M", "ț"),
+ (0x21B, "V"),
+ (0x21C, "M", "ȝ"),
+ (0x21D, "V"),
+ (0x21E, "M", "ȟ"),
+ (0x21F, "V"),
+ (0x220, "M", "ƞ"),
+ (0x221, "V"),
+ (0x222, "M", "ȣ"),
+ (0x223, "V"),
+ (0x224, "M", "ȥ"),
+ (0x225, "V"),
+ (0x226, "M", "ȧ"),
+ (0x227, "V"),
+ (0x228, "M", "ȩ"),
+ (0x229, "V"),
+ (0x22A, "M", "ȫ"),
+ (0x22B, "V"),
+ (0x22C, "M", "ȭ"),
+ (0x22D, "V"),
+ (0x22E, "M", "ȯ"),
+ (0x22F, "V"),
+ (0x230, "M", "ȱ"),
+ (0x231, "V"),
+ (0x232, "M", "ȳ"),
+ (0x233, "V"),
+ (0x23A, "M", "ⱥ"),
+ (0x23B, "M", "ȼ"),
+ (0x23C, "V"),
+ (0x23D, "M", "ƚ"),
+ (0x23E, "M", "ⱦ"),
+ (0x23F, "V"),
+ (0x241, "M", "ɂ"),
+ (0x242, "V"),
+ (0x243, "M", "ƀ"),
+ (0x244, "M", "ʉ"),
+ (0x245, "M", "ʌ"),
+ (0x246, "M", "ɇ"),
+ (0x247, "V"),
+ (0x248, "M", "ɉ"),
+ (0x249, "V"),
+ (0x24A, "M", "ɋ"),
+ (0x24B, "V"),
+ (0x24C, "M", "ɍ"),
+ (0x24D, "V"),
+ (0x24E, "M", "ɏ"),
+ (0x24F, "V"),
+ (0x2B0, "M", "h"),
+ (0x2B1, "M", "ɦ"),
+ (0x2B2, "M", "j"),
+ (0x2B3, "M", "r"),
+ (0x2B4, "M", "ɹ"),
+ (0x2B5, "M", "ɻ"),
+ (0x2B6, "M", "ʁ"),
+ (0x2B7, "M", "w"),
+ (0x2B8, "M", "y"),
+ (0x2B9, "V"),
+ (0x2D8, "3", " ̆"),
+ (0x2D9, "3", " ̇"),
+ (0x2DA, "3", " ̊"),
+ (0x2DB, "3", " ̨"),
+ (0x2DC, "3", " ̃"),
+ (0x2DD, "3", " ̋"),
+ (0x2DE, "V"),
+ (0x2E0, "M", "ɣ"),
+ (0x2E1, "M", "l"),
+ (0x2E2, "M", "s"),
+ (0x2E3, "M", "x"),
+ (0x2E4, "M", "ʕ"),
+ (0x2E5, "V"),
+ (0x340, "M", "̀"),
+ (0x341, "M", "́"),
+ (0x342, "V"),
+ (0x343, "M", "̓"),
+ (0x344, "M", "̈́"),
+ (0x345, "M", "ι"),
+ (0x346, "V"),
+ (0x34F, "I"),
+ (0x350, "V"),
+ (0x370, "M", "ͱ"),
+ (0x371, "V"),
+ (0x372, "M", "ͳ"),
+ (0x373, "V"),
+ (0x374, "M", "ʹ"),
+ (0x375, "V"),
+ (0x376, "M", "ͷ"),
+ (0x377, "V"),
+ ]
+
+
+def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x378, "X"),
+ (0x37A, "3", " ι"),
+ (0x37B, "V"),
+ (0x37E, "3", ";"),
+ (0x37F, "M", "ϳ"),
+ (0x380, "X"),
+ (0x384, "3", " ́"),
+ (0x385, "3", " ̈́"),
+ (0x386, "M", "ά"),
+ (0x387, "M", "·"),
+ (0x388, "M", "έ"),
+ (0x389, "M", "ή"),
+ (0x38A, "M", "ί"),
+ (0x38B, "X"),
+ (0x38C, "M", "ό"),
+ (0x38D, "X"),
+ (0x38E, "M", "ύ"),
+ (0x38F, "M", "ώ"),
+ (0x390, "V"),
+ (0x391, "M", "α"),
+ (0x392, "M", "β"),
+ (0x393, "M", "γ"),
+ (0x394, "M", "δ"),
+ (0x395, "M", "ε"),
+ (0x396, "M", "ζ"),
+ (0x397, "M", "η"),
+ (0x398, "M", "θ"),
+ (0x399, "M", "ι"),
+ (0x39A, "M", "κ"),
+ (0x39B, "M", "λ"),
+ (0x39C, "M", "μ"),
+ (0x39D, "M", "ν"),
+ (0x39E, "M", "ξ"),
+ (0x39F, "M", "ο"),
+ (0x3A0, "M", "π"),
+ (0x3A1, "M", "ρ"),
+ (0x3A2, "X"),
+ (0x3A3, "M", "σ"),
+ (0x3A4, "M", "τ"),
+ (0x3A5, "M", "υ"),
+ (0x3A6, "M", "φ"),
+ (0x3A7, "M", "χ"),
+ (0x3A8, "M", "ψ"),
+ (0x3A9, "M", "ω"),
+ (0x3AA, "M", "ϊ"),
+ (0x3AB, "M", "ϋ"),
+ (0x3AC, "V"),
+ (0x3C2, "D", "σ"),
+ (0x3C3, "V"),
+ (0x3CF, "M", "ϗ"),
+ (0x3D0, "M", "β"),
+ (0x3D1, "M", "θ"),
+ (0x3D2, "M", "υ"),
+ (0x3D3, "M", "ύ"),
+ (0x3D4, "M", "ϋ"),
+ (0x3D5, "M", "φ"),
+ (0x3D6, "M", "π"),
+ (0x3D7, "V"),
+ (0x3D8, "M", "ϙ"),
+ (0x3D9, "V"),
+ (0x3DA, "M", "ϛ"),
+ (0x3DB, "V"),
+ (0x3DC, "M", "ϝ"),
+ (0x3DD, "V"),
+ (0x3DE, "M", "ϟ"),
+ (0x3DF, "V"),
+ (0x3E0, "M", "ϡ"),
+ (0x3E1, "V"),
+ (0x3E2, "M", "ϣ"),
+ (0x3E3, "V"),
+ (0x3E4, "M", "ϥ"),
+ (0x3E5, "V"),
+ (0x3E6, "M", "ϧ"),
+ (0x3E7, "V"),
+ (0x3E8, "M", "ϩ"),
+ (0x3E9, "V"),
+ (0x3EA, "M", "ϫ"),
+ (0x3EB, "V"),
+ (0x3EC, "M", "ϭ"),
+ (0x3ED, "V"),
+ (0x3EE, "M", "ϯ"),
+ (0x3EF, "V"),
+ (0x3F0, "M", "κ"),
+ (0x3F1, "M", "ρ"),
+ (0x3F2, "M", "σ"),
+ (0x3F3, "V"),
+ (0x3F4, "M", "θ"),
+ (0x3F5, "M", "ε"),
+ (0x3F6, "V"),
+ (0x3F7, "M", "ϸ"),
+ (0x3F8, "V"),
+ (0x3F9, "M", "σ"),
+ (0x3FA, "M", "ϻ"),
+ (0x3FB, "V"),
+ (0x3FD, "M", "ͻ"),
+ (0x3FE, "M", "ͼ"),
+ (0x3FF, "M", "ͽ"),
+ (0x400, "M", "ѐ"),
+ (0x401, "M", "ё"),
+ (0x402, "M", "ђ"),
+ ]
+
+
+def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x403, "M", "ѓ"),
+ (0x404, "M", "є"),
+ (0x405, "M", "ѕ"),
+ (0x406, "M", "і"),
+ (0x407, "M", "ї"),
+ (0x408, "M", "ј"),
+ (0x409, "M", "љ"),
+ (0x40A, "M", "њ"),
+ (0x40B, "M", "ћ"),
+ (0x40C, "M", "ќ"),
+ (0x40D, "M", "ѝ"),
+ (0x40E, "M", "ў"),
+ (0x40F, "M", "џ"),
+ (0x410, "M", "а"),
+ (0x411, "M", "б"),
+ (0x412, "M", "в"),
+ (0x413, "M", "г"),
+ (0x414, "M", "д"),
+ (0x415, "M", "е"),
+ (0x416, "M", "ж"),
+ (0x417, "M", "з"),
+ (0x418, "M", "и"),
+ (0x419, "M", "й"),
+ (0x41A, "M", "к"),
+ (0x41B, "M", "л"),
+ (0x41C, "M", "м"),
+ (0x41D, "M", "н"),
+ (0x41E, "M", "о"),
+ (0x41F, "M", "п"),
+ (0x420, "M", "р"),
+ (0x421, "M", "с"),
+ (0x422, "M", "т"),
+ (0x423, "M", "у"),
+ (0x424, "M", "ф"),
+ (0x425, "M", "х"),
+ (0x426, "M", "ц"),
+ (0x427, "M", "ч"),
+ (0x428, "M", "ш"),
+ (0x429, "M", "щ"),
+ (0x42A, "M", "ъ"),
+ (0x42B, "M", "ы"),
+ (0x42C, "M", "ь"),
+ (0x42D, "M", "э"),
+ (0x42E, "M", "ю"),
+ (0x42F, "M", "я"),
+ (0x430, "V"),
+ (0x460, "M", "ѡ"),
+ (0x461, "V"),
+ (0x462, "M", "ѣ"),
+ (0x463, "V"),
+ (0x464, "M", "ѥ"),
+ (0x465, "V"),
+ (0x466, "M", "ѧ"),
+ (0x467, "V"),
+ (0x468, "M", "ѩ"),
+ (0x469, "V"),
+ (0x46A, "M", "ѫ"),
+ (0x46B, "V"),
+ (0x46C, "M", "ѭ"),
+ (0x46D, "V"),
+ (0x46E, "M", "ѯ"),
+ (0x46F, "V"),
+ (0x470, "M", "ѱ"),
+ (0x471, "V"),
+ (0x472, "M", "ѳ"),
+ (0x473, "V"),
+ (0x474, "M", "ѵ"),
+ (0x475, "V"),
+ (0x476, "M", "ѷ"),
+ (0x477, "V"),
+ (0x478, "M", "ѹ"),
+ (0x479, "V"),
+ (0x47A, "M", "ѻ"),
+ (0x47B, "V"),
+ (0x47C, "M", "ѽ"),
+ (0x47D, "V"),
+ (0x47E, "M", "ѿ"),
+ (0x47F, "V"),
+ (0x480, "M", "ҁ"),
+ (0x481, "V"),
+ (0x48A, "M", "ҋ"),
+ (0x48B, "V"),
+ (0x48C, "M", "ҍ"),
+ (0x48D, "V"),
+ (0x48E, "M", "ҏ"),
+ (0x48F, "V"),
+ (0x490, "M", "ґ"),
+ (0x491, "V"),
+ (0x492, "M", "ғ"),
+ (0x493, "V"),
+ (0x494, "M", "ҕ"),
+ (0x495, "V"),
+ (0x496, "M", "җ"),
+ (0x497, "V"),
+ (0x498, "M", "ҙ"),
+ (0x499, "V"),
+ (0x49A, "M", "қ"),
+ (0x49B, "V"),
+ (0x49C, "M", "ҝ"),
+ (0x49D, "V"),
+ ]
+
+
+def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x49E, "M", "ҟ"),
+ (0x49F, "V"),
+ (0x4A0, "M", "ҡ"),
+ (0x4A1, "V"),
+ (0x4A2, "M", "ң"),
+ (0x4A3, "V"),
+ (0x4A4, "M", "ҥ"),
+ (0x4A5, "V"),
+ (0x4A6, "M", "ҧ"),
+ (0x4A7, "V"),
+ (0x4A8, "M", "ҩ"),
+ (0x4A9, "V"),
+ (0x4AA, "M", "ҫ"),
+ (0x4AB, "V"),
+ (0x4AC, "M", "ҭ"),
+ (0x4AD, "V"),
+ (0x4AE, "M", "ү"),
+ (0x4AF, "V"),
+ (0x4B0, "M", "ұ"),
+ (0x4B1, "V"),
+ (0x4B2, "M", "ҳ"),
+ (0x4B3, "V"),
+ (0x4B4, "M", "ҵ"),
+ (0x4B5, "V"),
+ (0x4B6, "M", "ҷ"),
+ (0x4B7, "V"),
+ (0x4B8, "M", "ҹ"),
+ (0x4B9, "V"),
+ (0x4BA, "M", "һ"),
+ (0x4BB, "V"),
+ (0x4BC, "M", "ҽ"),
+ (0x4BD, "V"),
+ (0x4BE, "M", "ҿ"),
+ (0x4BF, "V"),
+ (0x4C0, "X"),
+ (0x4C1, "M", "ӂ"),
+ (0x4C2, "V"),
+ (0x4C3, "M", "ӄ"),
+ (0x4C4, "V"),
+ (0x4C5, "M", "ӆ"),
+ (0x4C6, "V"),
+ (0x4C7, "M", "ӈ"),
+ (0x4C8, "V"),
+ (0x4C9, "M", "ӊ"),
+ (0x4CA, "V"),
+ (0x4CB, "M", "ӌ"),
+ (0x4CC, "V"),
+ (0x4CD, "M", "ӎ"),
+ (0x4CE, "V"),
+ (0x4D0, "M", "ӑ"),
+ (0x4D1, "V"),
+ (0x4D2, "M", "ӓ"),
+ (0x4D3, "V"),
+ (0x4D4, "M", "ӕ"),
+ (0x4D5, "V"),
+ (0x4D6, "M", "ӗ"),
+ (0x4D7, "V"),
+ (0x4D8, "M", "ә"),
+ (0x4D9, "V"),
+ (0x4DA, "M", "ӛ"),
+ (0x4DB, "V"),
+ (0x4DC, "M", "ӝ"),
+ (0x4DD, "V"),
+ (0x4DE, "M", "ӟ"),
+ (0x4DF, "V"),
+ (0x4E0, "M", "ӡ"),
+ (0x4E1, "V"),
+ (0x4E2, "M", "ӣ"),
+ (0x4E3, "V"),
+ (0x4E4, "M", "ӥ"),
+ (0x4E5, "V"),
+ (0x4E6, "M", "ӧ"),
+ (0x4E7, "V"),
+ (0x4E8, "M", "ө"),
+ (0x4E9, "V"),
+ (0x4EA, "M", "ӫ"),
+ (0x4EB, "V"),
+ (0x4EC, "M", "ӭ"),
+ (0x4ED, "V"),
+ (0x4EE, "M", "ӯ"),
+ (0x4EF, "V"),
+ (0x4F0, "M", "ӱ"),
+ (0x4F1, "V"),
+ (0x4F2, "M", "ӳ"),
+ (0x4F3, "V"),
+ (0x4F4, "M", "ӵ"),
+ (0x4F5, "V"),
+ (0x4F6, "M", "ӷ"),
+ (0x4F7, "V"),
+ (0x4F8, "M", "ӹ"),
+ (0x4F9, "V"),
+ (0x4FA, "M", "ӻ"),
+ (0x4FB, "V"),
+ (0x4FC, "M", "ӽ"),
+ (0x4FD, "V"),
+ (0x4FE, "M", "ӿ"),
+ (0x4FF, "V"),
+ (0x500, "M", "ԁ"),
+ (0x501, "V"),
+ (0x502, "M", "ԃ"),
+ ]
+
+
+def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x503, "V"),
+ (0x504, "M", "ԅ"),
+ (0x505, "V"),
+ (0x506, "M", "ԇ"),
+ (0x507, "V"),
+ (0x508, "M", "ԉ"),
+ (0x509, "V"),
+ (0x50A, "M", "ԋ"),
+ (0x50B, "V"),
+ (0x50C, "M", "ԍ"),
+ (0x50D, "V"),
+ (0x50E, "M", "ԏ"),
+ (0x50F, "V"),
+ (0x510, "M", "ԑ"),
+ (0x511, "V"),
+ (0x512, "M", "ԓ"),
+ (0x513, "V"),
+ (0x514, "M", "ԕ"),
+ (0x515, "V"),
+ (0x516, "M", "ԗ"),
+ (0x517, "V"),
+ (0x518, "M", "ԙ"),
+ (0x519, "V"),
+ (0x51A, "M", "ԛ"),
+ (0x51B, "V"),
+ (0x51C, "M", "ԝ"),
+ (0x51D, "V"),
+ (0x51E, "M", "ԟ"),
+ (0x51F, "V"),
+ (0x520, "M", "ԡ"),
+ (0x521, "V"),
+ (0x522, "M", "ԣ"),
+ (0x523, "V"),
+ (0x524, "M", "ԥ"),
+ (0x525, "V"),
+ (0x526, "M", "ԧ"),
+ (0x527, "V"),
+ (0x528, "M", "ԩ"),
+ (0x529, "V"),
+ (0x52A, "M", "ԫ"),
+ (0x52B, "V"),
+ (0x52C, "M", "ԭ"),
+ (0x52D, "V"),
+ (0x52E, "M", "ԯ"),
+ (0x52F, "V"),
+ (0x530, "X"),
+ (0x531, "M", "ա"),
+ (0x532, "M", "բ"),
+ (0x533, "M", "գ"),
+ (0x534, "M", "դ"),
+ (0x535, "M", "ե"),
+ (0x536, "M", "զ"),
+ (0x537, "M", "է"),
+ (0x538, "M", "ը"),
+ (0x539, "M", "թ"),
+ (0x53A, "M", "ժ"),
+ (0x53B, "M", "ի"),
+ (0x53C, "M", "լ"),
+ (0x53D, "M", "խ"),
+ (0x53E, "M", "ծ"),
+ (0x53F, "M", "կ"),
+ (0x540, "M", "հ"),
+ (0x541, "M", "ձ"),
+ (0x542, "M", "ղ"),
+ (0x543, "M", "ճ"),
+ (0x544, "M", "մ"),
+ (0x545, "M", "յ"),
+ (0x546, "M", "ն"),
+ (0x547, "M", "շ"),
+ (0x548, "M", "ո"),
+ (0x549, "M", "չ"),
+ (0x54A, "M", "պ"),
+ (0x54B, "M", "ջ"),
+ (0x54C, "M", "ռ"),
+ (0x54D, "M", "ս"),
+ (0x54E, "M", "վ"),
+ (0x54F, "M", "տ"),
+ (0x550, "M", "ր"),
+ (0x551, "M", "ց"),
+ (0x552, "M", "ւ"),
+ (0x553, "M", "փ"),
+ (0x554, "M", "ք"),
+ (0x555, "M", "օ"),
+ (0x556, "M", "ֆ"),
+ (0x557, "X"),
+ (0x559, "V"),
+ (0x587, "M", "եւ"),
+ (0x588, "V"),
+ (0x58B, "X"),
+ (0x58D, "V"),
+ (0x590, "X"),
+ (0x591, "V"),
+ (0x5C8, "X"),
+ (0x5D0, "V"),
+ (0x5EB, "X"),
+ (0x5EF, "V"),
+ (0x5F5, "X"),
+ (0x606, "V"),
+ (0x61C, "X"),
+ (0x61D, "V"),
+ ]
+
+
+def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x675, "M", "اٴ"),
+ (0x676, "M", "وٴ"),
+ (0x677, "M", "ۇٴ"),
+ (0x678, "M", "يٴ"),
+ (0x679, "V"),
+ (0x6DD, "X"),
+ (0x6DE, "V"),
+ (0x70E, "X"),
+ (0x710, "V"),
+ (0x74B, "X"),
+ (0x74D, "V"),
+ (0x7B2, "X"),
+ (0x7C0, "V"),
+ (0x7FB, "X"),
+ (0x7FD, "V"),
+ (0x82E, "X"),
+ (0x830, "V"),
+ (0x83F, "X"),
+ (0x840, "V"),
+ (0x85C, "X"),
+ (0x85E, "V"),
+ (0x85F, "X"),
+ (0x860, "V"),
+ (0x86B, "X"),
+ (0x870, "V"),
+ (0x88F, "X"),
+ (0x898, "V"),
+ (0x8E2, "X"),
+ (0x8E3, "V"),
+ (0x958, "M", "क़"),
+ (0x959, "M", "ख़"),
+ (0x95A, "M", "ग़"),
+ (0x95B, "M", "ज़"),
+ (0x95C, "M", "ड़"),
+ (0x95D, "M", "ढ़"),
+ (0x95E, "M", "फ़"),
+ (0x95F, "M", "य़"),
+ (0x960, "V"),
+ (0x984, "X"),
+ (0x985, "V"),
+ (0x98D, "X"),
+ (0x98F, "V"),
+ (0x991, "X"),
+ (0x993, "V"),
+ (0x9A9, "X"),
+ (0x9AA, "V"),
+ (0x9B1, "X"),
+ (0x9B2, "V"),
+ (0x9B3, "X"),
+ (0x9B6, "V"),
+ (0x9BA, "X"),
+ (0x9BC, "V"),
+ (0x9C5, "X"),
+ (0x9C7, "V"),
+ (0x9C9, "X"),
+ (0x9CB, "V"),
+ (0x9CF, "X"),
+ (0x9D7, "V"),
+ (0x9D8, "X"),
+ (0x9DC, "M", "ড়"),
+ (0x9DD, "M", "ঢ়"),
+ (0x9DE, "X"),
+ (0x9DF, "M", "য়"),
+ (0x9E0, "V"),
+ (0x9E4, "X"),
+ (0x9E6, "V"),
+ (0x9FF, "X"),
+ (0xA01, "V"),
+ (0xA04, "X"),
+ (0xA05, "V"),
+ (0xA0B, "X"),
+ (0xA0F, "V"),
+ (0xA11, "X"),
+ (0xA13, "V"),
+ (0xA29, "X"),
+ (0xA2A, "V"),
+ (0xA31, "X"),
+ (0xA32, "V"),
+ (0xA33, "M", "ਲ਼"),
+ (0xA34, "X"),
+ (0xA35, "V"),
+ (0xA36, "M", "ਸ਼"),
+ (0xA37, "X"),
+ (0xA38, "V"),
+ (0xA3A, "X"),
+ (0xA3C, "V"),
+ (0xA3D, "X"),
+ (0xA3E, "V"),
+ (0xA43, "X"),
+ (0xA47, "V"),
+ (0xA49, "X"),
+ (0xA4B, "V"),
+ (0xA4E, "X"),
+ (0xA51, "V"),
+ (0xA52, "X"),
+ (0xA59, "M", "ਖ਼"),
+ (0xA5A, "M", "ਗ਼"),
+ (0xA5B, "M", "ਜ਼"),
+ (0xA5C, "V"),
+ (0xA5D, "X"),
+ ]
+
+
+def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA5E, "M", "ਫ਼"),
+ (0xA5F, "X"),
+ (0xA66, "V"),
+ (0xA77, "X"),
+ (0xA81, "V"),
+ (0xA84, "X"),
+ (0xA85, "V"),
+ (0xA8E, "X"),
+ (0xA8F, "V"),
+ (0xA92, "X"),
+ (0xA93, "V"),
+ (0xAA9, "X"),
+ (0xAAA, "V"),
+ (0xAB1, "X"),
+ (0xAB2, "V"),
+ (0xAB4, "X"),
+ (0xAB5, "V"),
+ (0xABA, "X"),
+ (0xABC, "V"),
+ (0xAC6, "X"),
+ (0xAC7, "V"),
+ (0xACA, "X"),
+ (0xACB, "V"),
+ (0xACE, "X"),
+ (0xAD0, "V"),
+ (0xAD1, "X"),
+ (0xAE0, "V"),
+ (0xAE4, "X"),
+ (0xAE6, "V"),
+ (0xAF2, "X"),
+ (0xAF9, "V"),
+ (0xB00, "X"),
+ (0xB01, "V"),
+ (0xB04, "X"),
+ (0xB05, "V"),
+ (0xB0D, "X"),
+ (0xB0F, "V"),
+ (0xB11, "X"),
+ (0xB13, "V"),
+ (0xB29, "X"),
+ (0xB2A, "V"),
+ (0xB31, "X"),
+ (0xB32, "V"),
+ (0xB34, "X"),
+ (0xB35, "V"),
+ (0xB3A, "X"),
+ (0xB3C, "V"),
+ (0xB45, "X"),
+ (0xB47, "V"),
+ (0xB49, "X"),
+ (0xB4B, "V"),
+ (0xB4E, "X"),
+ (0xB55, "V"),
+ (0xB58, "X"),
+ (0xB5C, "M", "ଡ଼"),
+ (0xB5D, "M", "ଢ଼"),
+ (0xB5E, "X"),
+ (0xB5F, "V"),
+ (0xB64, "X"),
+ (0xB66, "V"),
+ (0xB78, "X"),
+ (0xB82, "V"),
+ (0xB84, "X"),
+ (0xB85, "V"),
+ (0xB8B, "X"),
+ (0xB8E, "V"),
+ (0xB91, "X"),
+ (0xB92, "V"),
+ (0xB96, "X"),
+ (0xB99, "V"),
+ (0xB9B, "X"),
+ (0xB9C, "V"),
+ (0xB9D, "X"),
+ (0xB9E, "V"),
+ (0xBA0, "X"),
+ (0xBA3, "V"),
+ (0xBA5, "X"),
+ (0xBA8, "V"),
+ (0xBAB, "X"),
+ (0xBAE, "V"),
+ (0xBBA, "X"),
+ (0xBBE, "V"),
+ (0xBC3, "X"),
+ (0xBC6, "V"),
+ (0xBC9, "X"),
+ (0xBCA, "V"),
+ (0xBCE, "X"),
+ (0xBD0, "V"),
+ (0xBD1, "X"),
+ (0xBD7, "V"),
+ (0xBD8, "X"),
+ (0xBE6, "V"),
+ (0xBFB, "X"),
+ (0xC00, "V"),
+ (0xC0D, "X"),
+ (0xC0E, "V"),
+ (0xC11, "X"),
+ (0xC12, "V"),
+ (0xC29, "X"),
+ (0xC2A, "V"),
+ ]
+
+
+def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xC3A, "X"),
+ (0xC3C, "V"),
+ (0xC45, "X"),
+ (0xC46, "V"),
+ (0xC49, "X"),
+ (0xC4A, "V"),
+ (0xC4E, "X"),
+ (0xC55, "V"),
+ (0xC57, "X"),
+ (0xC58, "V"),
+ (0xC5B, "X"),
+ (0xC5D, "V"),
+ (0xC5E, "X"),
+ (0xC60, "V"),
+ (0xC64, "X"),
+ (0xC66, "V"),
+ (0xC70, "X"),
+ (0xC77, "V"),
+ (0xC8D, "X"),
+ (0xC8E, "V"),
+ (0xC91, "X"),
+ (0xC92, "V"),
+ (0xCA9, "X"),
+ (0xCAA, "V"),
+ (0xCB4, "X"),
+ (0xCB5, "V"),
+ (0xCBA, "X"),
+ (0xCBC, "V"),
+ (0xCC5, "X"),
+ (0xCC6, "V"),
+ (0xCC9, "X"),
+ (0xCCA, "V"),
+ (0xCCE, "X"),
+ (0xCD5, "V"),
+ (0xCD7, "X"),
+ (0xCDD, "V"),
+ (0xCDF, "X"),
+ (0xCE0, "V"),
+ (0xCE4, "X"),
+ (0xCE6, "V"),
+ (0xCF0, "X"),
+ (0xCF1, "V"),
+ (0xCF4, "X"),
+ (0xD00, "V"),
+ (0xD0D, "X"),
+ (0xD0E, "V"),
+ (0xD11, "X"),
+ (0xD12, "V"),
+ (0xD45, "X"),
+ (0xD46, "V"),
+ (0xD49, "X"),
+ (0xD4A, "V"),
+ (0xD50, "X"),
+ (0xD54, "V"),
+ (0xD64, "X"),
+ (0xD66, "V"),
+ (0xD80, "X"),
+ (0xD81, "V"),
+ (0xD84, "X"),
+ (0xD85, "V"),
+ (0xD97, "X"),
+ (0xD9A, "V"),
+ (0xDB2, "X"),
+ (0xDB3, "V"),
+ (0xDBC, "X"),
+ (0xDBD, "V"),
+ (0xDBE, "X"),
+ (0xDC0, "V"),
+ (0xDC7, "X"),
+ (0xDCA, "V"),
+ (0xDCB, "X"),
+ (0xDCF, "V"),
+ (0xDD5, "X"),
+ (0xDD6, "V"),
+ (0xDD7, "X"),
+ (0xDD8, "V"),
+ (0xDE0, "X"),
+ (0xDE6, "V"),
+ (0xDF0, "X"),
+ (0xDF2, "V"),
+ (0xDF5, "X"),
+ (0xE01, "V"),
+ (0xE33, "M", "ํา"),
+ (0xE34, "V"),
+ (0xE3B, "X"),
+ (0xE3F, "V"),
+ (0xE5C, "X"),
+ (0xE81, "V"),
+ (0xE83, "X"),
+ (0xE84, "V"),
+ (0xE85, "X"),
+ (0xE86, "V"),
+ (0xE8B, "X"),
+ (0xE8C, "V"),
+ (0xEA4, "X"),
+ (0xEA5, "V"),
+ (0xEA6, "X"),
+ (0xEA7, "V"),
+ (0xEB3, "M", "ໍາ"),
+ (0xEB4, "V"),
+ ]
+
+
+def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xEBE, "X"),
+ (0xEC0, "V"),
+ (0xEC5, "X"),
+ (0xEC6, "V"),
+ (0xEC7, "X"),
+ (0xEC8, "V"),
+ (0xECF, "X"),
+ (0xED0, "V"),
+ (0xEDA, "X"),
+ (0xEDC, "M", "ຫນ"),
+ (0xEDD, "M", "ຫມ"),
+ (0xEDE, "V"),
+ (0xEE0, "X"),
+ (0xF00, "V"),
+ (0xF0C, "M", "་"),
+ (0xF0D, "V"),
+ (0xF43, "M", "གྷ"),
+ (0xF44, "V"),
+ (0xF48, "X"),
+ (0xF49, "V"),
+ (0xF4D, "M", "ཌྷ"),
+ (0xF4E, "V"),
+ (0xF52, "M", "དྷ"),
+ (0xF53, "V"),
+ (0xF57, "M", "བྷ"),
+ (0xF58, "V"),
+ (0xF5C, "M", "ཛྷ"),
+ (0xF5D, "V"),
+ (0xF69, "M", "ཀྵ"),
+ (0xF6A, "V"),
+ (0xF6D, "X"),
+ (0xF71, "V"),
+ (0xF73, "M", "ཱི"),
+ (0xF74, "V"),
+ (0xF75, "M", "ཱུ"),
+ (0xF76, "M", "ྲྀ"),
+ (0xF77, "M", "ྲཱྀ"),
+ (0xF78, "M", "ླྀ"),
+ (0xF79, "M", "ླཱྀ"),
+ (0xF7A, "V"),
+ (0xF81, "M", "ཱྀ"),
+ (0xF82, "V"),
+ (0xF93, "M", "ྒྷ"),
+ (0xF94, "V"),
+ (0xF98, "X"),
+ (0xF99, "V"),
+ (0xF9D, "M", "ྜྷ"),
+ (0xF9E, "V"),
+ (0xFA2, "M", "ྡྷ"),
+ (0xFA3, "V"),
+ (0xFA7, "M", "ྦྷ"),
+ (0xFA8, "V"),
+ (0xFAC, "M", "ྫྷ"),
+ (0xFAD, "V"),
+ (0xFB9, "M", "ྐྵ"),
+ (0xFBA, "V"),
+ (0xFBD, "X"),
+ (0xFBE, "V"),
+ (0xFCD, "X"),
+ (0xFCE, "V"),
+ (0xFDB, "X"),
+ (0x1000, "V"),
+ (0x10A0, "X"),
+ (0x10C7, "M", "ⴧ"),
+ (0x10C8, "X"),
+ (0x10CD, "M", "ⴭ"),
+ (0x10CE, "X"),
+ (0x10D0, "V"),
+ (0x10FC, "M", "ნ"),
+ (0x10FD, "V"),
+ (0x115F, "X"),
+ (0x1161, "V"),
+ (0x1249, "X"),
+ (0x124A, "V"),
+ (0x124E, "X"),
+ (0x1250, "V"),
+ (0x1257, "X"),
+ (0x1258, "V"),
+ (0x1259, "X"),
+ (0x125A, "V"),
+ (0x125E, "X"),
+ (0x1260, "V"),
+ (0x1289, "X"),
+ (0x128A, "V"),
+ (0x128E, "X"),
+ (0x1290, "V"),
+ (0x12B1, "X"),
+ (0x12B2, "V"),
+ (0x12B6, "X"),
+ (0x12B8, "V"),
+ (0x12BF, "X"),
+ (0x12C0, "V"),
+ (0x12C1, "X"),
+ (0x12C2, "V"),
+ (0x12C6, "X"),
+ (0x12C8, "V"),
+ (0x12D7, "X"),
+ (0x12D8, "V"),
+ (0x1311, "X"),
+ (0x1312, "V"),
+ ]
+
+
+def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1316, "X"),
+ (0x1318, "V"),
+ (0x135B, "X"),
+ (0x135D, "V"),
+ (0x137D, "X"),
+ (0x1380, "V"),
+ (0x139A, "X"),
+ (0x13A0, "V"),
+ (0x13F6, "X"),
+ (0x13F8, "M", "Ᏸ"),
+ (0x13F9, "M", "Ᏹ"),
+ (0x13FA, "M", "Ᏺ"),
+ (0x13FB, "M", "Ᏻ"),
+ (0x13FC, "M", "Ᏼ"),
+ (0x13FD, "M", "Ᏽ"),
+ (0x13FE, "X"),
+ (0x1400, "V"),
+ (0x1680, "X"),
+ (0x1681, "V"),
+ (0x169D, "X"),
+ (0x16A0, "V"),
+ (0x16F9, "X"),
+ (0x1700, "V"),
+ (0x1716, "X"),
+ (0x171F, "V"),
+ (0x1737, "X"),
+ (0x1740, "V"),
+ (0x1754, "X"),
+ (0x1760, "V"),
+ (0x176D, "X"),
+ (0x176E, "V"),
+ (0x1771, "X"),
+ (0x1772, "V"),
+ (0x1774, "X"),
+ (0x1780, "V"),
+ (0x17B4, "X"),
+ (0x17B6, "V"),
+ (0x17DE, "X"),
+ (0x17E0, "V"),
+ (0x17EA, "X"),
+ (0x17F0, "V"),
+ (0x17FA, "X"),
+ (0x1800, "V"),
+ (0x1806, "X"),
+ (0x1807, "V"),
+ (0x180B, "I"),
+ (0x180E, "X"),
+ (0x180F, "I"),
+ (0x1810, "V"),
+ (0x181A, "X"),
+ (0x1820, "V"),
+ (0x1879, "X"),
+ (0x1880, "V"),
+ (0x18AB, "X"),
+ (0x18B0, "V"),
+ (0x18F6, "X"),
+ (0x1900, "V"),
+ (0x191F, "X"),
+ (0x1920, "V"),
+ (0x192C, "X"),
+ (0x1930, "V"),
+ (0x193C, "X"),
+ (0x1940, "V"),
+ (0x1941, "X"),
+ (0x1944, "V"),
+ (0x196E, "X"),
+ (0x1970, "V"),
+ (0x1975, "X"),
+ (0x1980, "V"),
+ (0x19AC, "X"),
+ (0x19B0, "V"),
+ (0x19CA, "X"),
+ (0x19D0, "V"),
+ (0x19DB, "X"),
+ (0x19DE, "V"),
+ (0x1A1C, "X"),
+ (0x1A1E, "V"),
+ (0x1A5F, "X"),
+ (0x1A60, "V"),
+ (0x1A7D, "X"),
+ (0x1A7F, "V"),
+ (0x1A8A, "X"),
+ (0x1A90, "V"),
+ (0x1A9A, "X"),
+ (0x1AA0, "V"),
+ (0x1AAE, "X"),
+ (0x1AB0, "V"),
+ (0x1ACF, "X"),
+ (0x1B00, "V"),
+ (0x1B4D, "X"),
+ (0x1B50, "V"),
+ (0x1B7F, "X"),
+ (0x1B80, "V"),
+ (0x1BF4, "X"),
+ (0x1BFC, "V"),
+ (0x1C38, "X"),
+ (0x1C3B, "V"),
+ (0x1C4A, "X"),
+ (0x1C4D, "V"),
+ (0x1C80, "M", "в"),
+ ]
+
+
+def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1C81, "M", "д"),
+ (0x1C82, "M", "о"),
+ (0x1C83, "M", "с"),
+ (0x1C84, "M", "т"),
+ (0x1C86, "M", "ъ"),
+ (0x1C87, "M", "ѣ"),
+ (0x1C88, "M", "ꙋ"),
+ (0x1C89, "X"),
+ (0x1C90, "M", "ა"),
+ (0x1C91, "M", "ბ"),
+ (0x1C92, "M", "გ"),
+ (0x1C93, "M", "დ"),
+ (0x1C94, "M", "ე"),
+ (0x1C95, "M", "ვ"),
+ (0x1C96, "M", "ზ"),
+ (0x1C97, "M", "თ"),
+ (0x1C98, "M", "ი"),
+ (0x1C99, "M", "კ"),
+ (0x1C9A, "M", "ლ"),
+ (0x1C9B, "M", "მ"),
+ (0x1C9C, "M", "ნ"),
+ (0x1C9D, "M", "ო"),
+ (0x1C9E, "M", "პ"),
+ (0x1C9F, "M", "ჟ"),
+ (0x1CA0, "M", "რ"),
+ (0x1CA1, "M", "ს"),
+ (0x1CA2, "M", "ტ"),
+ (0x1CA3, "M", "უ"),
+ (0x1CA4, "M", "ფ"),
+ (0x1CA5, "M", "ქ"),
+ (0x1CA6, "M", "ღ"),
+ (0x1CA7, "M", "ყ"),
+ (0x1CA8, "M", "შ"),
+ (0x1CA9, "M", "ჩ"),
+ (0x1CAA, "M", "ც"),
+ (0x1CAB, "M", "ძ"),
+ (0x1CAC, "M", "წ"),
+ (0x1CAD, "M", "ჭ"),
+ (0x1CAE, "M", "ხ"),
+ (0x1CAF, "M", "ჯ"),
+ (0x1CB0, "M", "ჰ"),
+ (0x1CB1, "M", "ჱ"),
+ (0x1CB2, "M", "ჲ"),
+ (0x1CB3, "M", "ჳ"),
+ (0x1CB4, "M", "ჴ"),
+ (0x1CB5, "M", "ჵ"),
+ (0x1CB6, "M", "ჶ"),
+ (0x1CB7, "M", "ჷ"),
+ (0x1CB8, "M", "ჸ"),
+ (0x1CB9, "M", "ჹ"),
+ (0x1CBA, "M", "ჺ"),
+ (0x1CBB, "X"),
+ (0x1CBD, "M", "ჽ"),
+ (0x1CBE, "M", "ჾ"),
+ (0x1CBF, "M", "ჿ"),
+ (0x1CC0, "V"),
+ (0x1CC8, "X"),
+ (0x1CD0, "V"),
+ (0x1CFB, "X"),
+ (0x1D00, "V"),
+ (0x1D2C, "M", "a"),
+ (0x1D2D, "M", "æ"),
+ (0x1D2E, "M", "b"),
+ (0x1D2F, "V"),
+ (0x1D30, "M", "d"),
+ (0x1D31, "M", "e"),
+ (0x1D32, "M", "ǝ"),
+ (0x1D33, "M", "g"),
+ (0x1D34, "M", "h"),
+ (0x1D35, "M", "i"),
+ (0x1D36, "M", "j"),
+ (0x1D37, "M", "k"),
+ (0x1D38, "M", "l"),
+ (0x1D39, "M", "m"),
+ (0x1D3A, "M", "n"),
+ (0x1D3B, "V"),
+ (0x1D3C, "M", "o"),
+ (0x1D3D, "M", "ȣ"),
+ (0x1D3E, "M", "p"),
+ (0x1D3F, "M", "r"),
+ (0x1D40, "M", "t"),
+ (0x1D41, "M", "u"),
+ (0x1D42, "M", "w"),
+ (0x1D43, "M", "a"),
+ (0x1D44, "M", "ɐ"),
+ (0x1D45, "M", "ɑ"),
+ (0x1D46, "M", "ᴂ"),
+ (0x1D47, "M", "b"),
+ (0x1D48, "M", "d"),
+ (0x1D49, "M", "e"),
+ (0x1D4A, "M", "ə"),
+ (0x1D4B, "M", "ɛ"),
+ (0x1D4C, "M", "ɜ"),
+ (0x1D4D, "M", "g"),
+ (0x1D4E, "V"),
+ (0x1D4F, "M", "k"),
+ (0x1D50, "M", "m"),
+ (0x1D51, "M", "ŋ"),
+ (0x1D52, "M", "o"),
+ (0x1D53, "M", "ɔ"),
+ ]
+
+
+def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D54, "M", "ᴖ"),
+ (0x1D55, "M", "ᴗ"),
+ (0x1D56, "M", "p"),
+ (0x1D57, "M", "t"),
+ (0x1D58, "M", "u"),
+ (0x1D59, "M", "ᴝ"),
+ (0x1D5A, "M", "ɯ"),
+ (0x1D5B, "M", "v"),
+ (0x1D5C, "M", "ᴥ"),
+ (0x1D5D, "M", "β"),
+ (0x1D5E, "M", "γ"),
+ (0x1D5F, "M", "δ"),
+ (0x1D60, "M", "φ"),
+ (0x1D61, "M", "χ"),
+ (0x1D62, "M", "i"),
+ (0x1D63, "M", "r"),
+ (0x1D64, "M", "u"),
+ (0x1D65, "M", "v"),
+ (0x1D66, "M", "β"),
+ (0x1D67, "M", "γ"),
+ (0x1D68, "M", "ρ"),
+ (0x1D69, "M", "φ"),
+ (0x1D6A, "M", "χ"),
+ (0x1D6B, "V"),
+ (0x1D78, "M", "н"),
+ (0x1D79, "V"),
+ (0x1D9B, "M", "ɒ"),
+ (0x1D9C, "M", "c"),
+ (0x1D9D, "M", "ɕ"),
+ (0x1D9E, "M", "ð"),
+ (0x1D9F, "M", "ɜ"),
+ (0x1DA0, "M", "f"),
+ (0x1DA1, "M", "ɟ"),
+ (0x1DA2, "M", "ɡ"),
+ (0x1DA3, "M", "ɥ"),
+ (0x1DA4, "M", "ɨ"),
+ (0x1DA5, "M", "ɩ"),
+ (0x1DA6, "M", "ɪ"),
+ (0x1DA7, "M", "ᵻ"),
+ (0x1DA8, "M", "ʝ"),
+ (0x1DA9, "M", "ɭ"),
+ (0x1DAA, "M", "ᶅ"),
+ (0x1DAB, "M", "ʟ"),
+ (0x1DAC, "M", "ɱ"),
+ (0x1DAD, "M", "ɰ"),
+ (0x1DAE, "M", "ɲ"),
+ (0x1DAF, "M", "ɳ"),
+ (0x1DB0, "M", "ɴ"),
+ (0x1DB1, "M", "ɵ"),
+ (0x1DB2, "M", "ɸ"),
+ (0x1DB3, "M", "ʂ"),
+ (0x1DB4, "M", "ʃ"),
+ (0x1DB5, "M", "ƫ"),
+ (0x1DB6, "M", "ʉ"),
+ (0x1DB7, "M", "ʊ"),
+ (0x1DB8, "M", "ᴜ"),
+ (0x1DB9, "M", "ʋ"),
+ (0x1DBA, "M", "ʌ"),
+ (0x1DBB, "M", "z"),
+ (0x1DBC, "M", "ʐ"),
+ (0x1DBD, "M", "ʑ"),
+ (0x1DBE, "M", "ʒ"),
+ (0x1DBF, "M", "θ"),
+ (0x1DC0, "V"),
+ (0x1E00, "M", "ḁ"),
+ (0x1E01, "V"),
+ (0x1E02, "M", "ḃ"),
+ (0x1E03, "V"),
+ (0x1E04, "M", "ḅ"),
+ (0x1E05, "V"),
+ (0x1E06, "M", "ḇ"),
+ (0x1E07, "V"),
+ (0x1E08, "M", "ḉ"),
+ (0x1E09, "V"),
+ (0x1E0A, "M", "ḋ"),
+ (0x1E0B, "V"),
+ (0x1E0C, "M", "ḍ"),
+ (0x1E0D, "V"),
+ (0x1E0E, "M", "ḏ"),
+ (0x1E0F, "V"),
+ (0x1E10, "M", "ḑ"),
+ (0x1E11, "V"),
+ (0x1E12, "M", "ḓ"),
+ (0x1E13, "V"),
+ (0x1E14, "M", "ḕ"),
+ (0x1E15, "V"),
+ (0x1E16, "M", "ḗ"),
+ (0x1E17, "V"),
+ (0x1E18, "M", "ḙ"),
+ (0x1E19, "V"),
+ (0x1E1A, "M", "ḛ"),
+ (0x1E1B, "V"),
+ (0x1E1C, "M", "ḝ"),
+ (0x1E1D, "V"),
+ (0x1E1E, "M", "ḟ"),
+ (0x1E1F, "V"),
+ (0x1E20, "M", "ḡ"),
+ (0x1E21, "V"),
+ (0x1E22, "M", "ḣ"),
+ (0x1E23, "V"),
+ ]
+
+
+def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E24, "M", "ḥ"),
+ (0x1E25, "V"),
+ (0x1E26, "M", "ḧ"),
+ (0x1E27, "V"),
+ (0x1E28, "M", "ḩ"),
+ (0x1E29, "V"),
+ (0x1E2A, "M", "ḫ"),
+ (0x1E2B, "V"),
+ (0x1E2C, "M", "ḭ"),
+ (0x1E2D, "V"),
+ (0x1E2E, "M", "ḯ"),
+ (0x1E2F, "V"),
+ (0x1E30, "M", "ḱ"),
+ (0x1E31, "V"),
+ (0x1E32, "M", "ḳ"),
+ (0x1E33, "V"),
+ (0x1E34, "M", "ḵ"),
+ (0x1E35, "V"),
+ (0x1E36, "M", "ḷ"),
+ (0x1E37, "V"),
+ (0x1E38, "M", "ḹ"),
+ (0x1E39, "V"),
+ (0x1E3A, "M", "ḻ"),
+ (0x1E3B, "V"),
+ (0x1E3C, "M", "ḽ"),
+ (0x1E3D, "V"),
+ (0x1E3E, "M", "ḿ"),
+ (0x1E3F, "V"),
+ (0x1E40, "M", "ṁ"),
+ (0x1E41, "V"),
+ (0x1E42, "M", "ṃ"),
+ (0x1E43, "V"),
+ (0x1E44, "M", "ṅ"),
+ (0x1E45, "V"),
+ (0x1E46, "M", "ṇ"),
+ (0x1E47, "V"),
+ (0x1E48, "M", "ṉ"),
+ (0x1E49, "V"),
+ (0x1E4A, "M", "ṋ"),
+ (0x1E4B, "V"),
+ (0x1E4C, "M", "ṍ"),
+ (0x1E4D, "V"),
+ (0x1E4E, "M", "ṏ"),
+ (0x1E4F, "V"),
+ (0x1E50, "M", "ṑ"),
+ (0x1E51, "V"),
+ (0x1E52, "M", "ṓ"),
+ (0x1E53, "V"),
+ (0x1E54, "M", "ṕ"),
+ (0x1E55, "V"),
+ (0x1E56, "M", "ṗ"),
+ (0x1E57, "V"),
+ (0x1E58, "M", "ṙ"),
+ (0x1E59, "V"),
+ (0x1E5A, "M", "ṛ"),
+ (0x1E5B, "V"),
+ (0x1E5C, "M", "ṝ"),
+ (0x1E5D, "V"),
+ (0x1E5E, "M", "ṟ"),
+ (0x1E5F, "V"),
+ (0x1E60, "M", "ṡ"),
+ (0x1E61, "V"),
+ (0x1E62, "M", "ṣ"),
+ (0x1E63, "V"),
+ (0x1E64, "M", "ṥ"),
+ (0x1E65, "V"),
+ (0x1E66, "M", "ṧ"),
+ (0x1E67, "V"),
+ (0x1E68, "M", "ṩ"),
+ (0x1E69, "V"),
+ (0x1E6A, "M", "ṫ"),
+ (0x1E6B, "V"),
+ (0x1E6C, "M", "ṭ"),
+ (0x1E6D, "V"),
+ (0x1E6E, "M", "ṯ"),
+ (0x1E6F, "V"),
+ (0x1E70, "M", "ṱ"),
+ (0x1E71, "V"),
+ (0x1E72, "M", "ṳ"),
+ (0x1E73, "V"),
+ (0x1E74, "M", "ṵ"),
+ (0x1E75, "V"),
+ (0x1E76, "M", "ṷ"),
+ (0x1E77, "V"),
+ (0x1E78, "M", "ṹ"),
+ (0x1E79, "V"),
+ (0x1E7A, "M", "ṻ"),
+ (0x1E7B, "V"),
+ (0x1E7C, "M", "ṽ"),
+ (0x1E7D, "V"),
+ (0x1E7E, "M", "ṿ"),
+ (0x1E7F, "V"),
+ (0x1E80, "M", "ẁ"),
+ (0x1E81, "V"),
+ (0x1E82, "M", "ẃ"),
+ (0x1E83, "V"),
+ (0x1E84, "M", "ẅ"),
+ (0x1E85, "V"),
+ (0x1E86, "M", "ẇ"),
+ (0x1E87, "V"),
+ ]
+
+
+def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E88, "M", "ẉ"),
+ (0x1E89, "V"),
+ (0x1E8A, "M", "ẋ"),
+ (0x1E8B, "V"),
+ (0x1E8C, "M", "ẍ"),
+ (0x1E8D, "V"),
+ (0x1E8E, "M", "ẏ"),
+ (0x1E8F, "V"),
+ (0x1E90, "M", "ẑ"),
+ (0x1E91, "V"),
+ (0x1E92, "M", "ẓ"),
+ (0x1E93, "V"),
+ (0x1E94, "M", "ẕ"),
+ (0x1E95, "V"),
+ (0x1E9A, "M", "aʾ"),
+ (0x1E9B, "M", "ṡ"),
+ (0x1E9C, "V"),
+ (0x1E9E, "M", "ß"),
+ (0x1E9F, "V"),
+ (0x1EA0, "M", "ạ"),
+ (0x1EA1, "V"),
+ (0x1EA2, "M", "ả"),
+ (0x1EA3, "V"),
+ (0x1EA4, "M", "ấ"),
+ (0x1EA5, "V"),
+ (0x1EA6, "M", "ầ"),
+ (0x1EA7, "V"),
+ (0x1EA8, "M", "ẩ"),
+ (0x1EA9, "V"),
+ (0x1EAA, "M", "ẫ"),
+ (0x1EAB, "V"),
+ (0x1EAC, "M", "ậ"),
+ (0x1EAD, "V"),
+ (0x1EAE, "M", "ắ"),
+ (0x1EAF, "V"),
+ (0x1EB0, "M", "ằ"),
+ (0x1EB1, "V"),
+ (0x1EB2, "M", "ẳ"),
+ (0x1EB3, "V"),
+ (0x1EB4, "M", "ẵ"),
+ (0x1EB5, "V"),
+ (0x1EB6, "M", "ặ"),
+ (0x1EB7, "V"),
+ (0x1EB8, "M", "ẹ"),
+ (0x1EB9, "V"),
+ (0x1EBA, "M", "ẻ"),
+ (0x1EBB, "V"),
+ (0x1EBC, "M", "ẽ"),
+ (0x1EBD, "V"),
+ (0x1EBE, "M", "ế"),
+ (0x1EBF, "V"),
+ (0x1EC0, "M", "ề"),
+ (0x1EC1, "V"),
+ (0x1EC2, "M", "ể"),
+ (0x1EC3, "V"),
+ (0x1EC4, "M", "ễ"),
+ (0x1EC5, "V"),
+ (0x1EC6, "M", "ệ"),
+ (0x1EC7, "V"),
+ (0x1EC8, "M", "ỉ"),
+ (0x1EC9, "V"),
+ (0x1ECA, "M", "ị"),
+ (0x1ECB, "V"),
+ (0x1ECC, "M", "ọ"),
+ (0x1ECD, "V"),
+ (0x1ECE, "M", "ỏ"),
+ (0x1ECF, "V"),
+ (0x1ED0, "M", "ố"),
+ (0x1ED1, "V"),
+ (0x1ED2, "M", "ồ"),
+ (0x1ED3, "V"),
+ (0x1ED4, "M", "ổ"),
+ (0x1ED5, "V"),
+ (0x1ED6, "M", "ỗ"),
+ (0x1ED7, "V"),
+ (0x1ED8, "M", "ộ"),
+ (0x1ED9, "V"),
+ (0x1EDA, "M", "ớ"),
+ (0x1EDB, "V"),
+ (0x1EDC, "M", "ờ"),
+ (0x1EDD, "V"),
+ (0x1EDE, "M", "ở"),
+ (0x1EDF, "V"),
+ (0x1EE0, "M", "ỡ"),
+ (0x1EE1, "V"),
+ (0x1EE2, "M", "ợ"),
+ (0x1EE3, "V"),
+ (0x1EE4, "M", "ụ"),
+ (0x1EE5, "V"),
+ (0x1EE6, "M", "ủ"),
+ (0x1EE7, "V"),
+ (0x1EE8, "M", "ứ"),
+ (0x1EE9, "V"),
+ (0x1EEA, "M", "ừ"),
+ (0x1EEB, "V"),
+ (0x1EEC, "M", "ử"),
+ (0x1EED, "V"),
+ (0x1EEE, "M", "ữ"),
+ (0x1EEF, "V"),
+ (0x1EF0, "M", "ự"),
+ ]
+
+
+def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EF1, "V"),
+ (0x1EF2, "M", "ỳ"),
+ (0x1EF3, "V"),
+ (0x1EF4, "M", "ỵ"),
+ (0x1EF5, "V"),
+ (0x1EF6, "M", "ỷ"),
+ (0x1EF7, "V"),
+ (0x1EF8, "M", "ỹ"),
+ (0x1EF9, "V"),
+ (0x1EFA, "M", "ỻ"),
+ (0x1EFB, "V"),
+ (0x1EFC, "M", "ỽ"),
+ (0x1EFD, "V"),
+ (0x1EFE, "M", "ỿ"),
+ (0x1EFF, "V"),
+ (0x1F08, "M", "ἀ"),
+ (0x1F09, "M", "ἁ"),
+ (0x1F0A, "M", "ἂ"),
+ (0x1F0B, "M", "ἃ"),
+ (0x1F0C, "M", "ἄ"),
+ (0x1F0D, "M", "ἅ"),
+ (0x1F0E, "M", "ἆ"),
+ (0x1F0F, "M", "ἇ"),
+ (0x1F10, "V"),
+ (0x1F16, "X"),
+ (0x1F18, "M", "ἐ"),
+ (0x1F19, "M", "ἑ"),
+ (0x1F1A, "M", "ἒ"),
+ (0x1F1B, "M", "ἓ"),
+ (0x1F1C, "M", "ἔ"),
+ (0x1F1D, "M", "ἕ"),
+ (0x1F1E, "X"),
+ (0x1F20, "V"),
+ (0x1F28, "M", "ἠ"),
+ (0x1F29, "M", "ἡ"),
+ (0x1F2A, "M", "ἢ"),
+ (0x1F2B, "M", "ἣ"),
+ (0x1F2C, "M", "ἤ"),
+ (0x1F2D, "M", "ἥ"),
+ (0x1F2E, "M", "ἦ"),
+ (0x1F2F, "M", "ἧ"),
+ (0x1F30, "V"),
+ (0x1F38, "M", "ἰ"),
+ (0x1F39, "M", "ἱ"),
+ (0x1F3A, "M", "ἲ"),
+ (0x1F3B, "M", "ἳ"),
+ (0x1F3C, "M", "ἴ"),
+ (0x1F3D, "M", "ἵ"),
+ (0x1F3E, "M", "ἶ"),
+ (0x1F3F, "M", "ἷ"),
+ (0x1F40, "V"),
+ (0x1F46, "X"),
+ (0x1F48, "M", "ὀ"),
+ (0x1F49, "M", "ὁ"),
+ (0x1F4A, "M", "ὂ"),
+ (0x1F4B, "M", "ὃ"),
+ (0x1F4C, "M", "ὄ"),
+ (0x1F4D, "M", "ὅ"),
+ (0x1F4E, "X"),
+ (0x1F50, "V"),
+ (0x1F58, "X"),
+ (0x1F59, "M", "ὑ"),
+ (0x1F5A, "X"),
+ (0x1F5B, "M", "ὓ"),
+ (0x1F5C, "X"),
+ (0x1F5D, "M", "ὕ"),
+ (0x1F5E, "X"),
+ (0x1F5F, "M", "ὗ"),
+ (0x1F60, "V"),
+ (0x1F68, "M", "ὠ"),
+ (0x1F69, "M", "ὡ"),
+ (0x1F6A, "M", "ὢ"),
+ (0x1F6B, "M", "ὣ"),
+ (0x1F6C, "M", "ὤ"),
+ (0x1F6D, "M", "ὥ"),
+ (0x1F6E, "M", "ὦ"),
+ (0x1F6F, "M", "ὧ"),
+ (0x1F70, "V"),
+ (0x1F71, "M", "ά"),
+ (0x1F72, "V"),
+ (0x1F73, "M", "έ"),
+ (0x1F74, "V"),
+ (0x1F75, "M", "ή"),
+ (0x1F76, "V"),
+ (0x1F77, "M", "ί"),
+ (0x1F78, "V"),
+ (0x1F79, "M", "ό"),
+ (0x1F7A, "V"),
+ (0x1F7B, "M", "ύ"),
+ (0x1F7C, "V"),
+ (0x1F7D, "M", "ώ"),
+ (0x1F7E, "X"),
+ (0x1F80, "M", "ἀι"),
+ (0x1F81, "M", "ἁι"),
+ (0x1F82, "M", "ἂι"),
+ (0x1F83, "M", "ἃι"),
+ (0x1F84, "M", "ἄι"),
+ (0x1F85, "M", "ἅι"),
+ (0x1F86, "M", "ἆι"),
+ (0x1F87, "M", "ἇι"),
+ ]
+
+
+def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F88, "M", "ἀι"),
+ (0x1F89, "M", "ἁι"),
+ (0x1F8A, "M", "ἂι"),
+ (0x1F8B, "M", "ἃι"),
+ (0x1F8C, "M", "ἄι"),
+ (0x1F8D, "M", "ἅι"),
+ (0x1F8E, "M", "ἆι"),
+ (0x1F8F, "M", "ἇι"),
+ (0x1F90, "M", "ἠι"),
+ (0x1F91, "M", "ἡι"),
+ (0x1F92, "M", "ἢι"),
+ (0x1F93, "M", "ἣι"),
+ (0x1F94, "M", "ἤι"),
+ (0x1F95, "M", "ἥι"),
+ (0x1F96, "M", "ἦι"),
+ (0x1F97, "M", "ἧι"),
+ (0x1F98, "M", "ἠι"),
+ (0x1F99, "M", "ἡι"),
+ (0x1F9A, "M", "ἢι"),
+ (0x1F9B, "M", "ἣι"),
+ (0x1F9C, "M", "ἤι"),
+ (0x1F9D, "M", "ἥι"),
+ (0x1F9E, "M", "ἦι"),
+ (0x1F9F, "M", "ἧι"),
+ (0x1FA0, "M", "ὠι"),
+ (0x1FA1, "M", "ὡι"),
+ (0x1FA2, "M", "ὢι"),
+ (0x1FA3, "M", "ὣι"),
+ (0x1FA4, "M", "ὤι"),
+ (0x1FA5, "M", "ὥι"),
+ (0x1FA6, "M", "ὦι"),
+ (0x1FA7, "M", "ὧι"),
+ (0x1FA8, "M", "ὠι"),
+ (0x1FA9, "M", "ὡι"),
+ (0x1FAA, "M", "ὢι"),
+ (0x1FAB, "M", "ὣι"),
+ (0x1FAC, "M", "ὤι"),
+ (0x1FAD, "M", "ὥι"),
+ (0x1FAE, "M", "ὦι"),
+ (0x1FAF, "M", "ὧι"),
+ (0x1FB0, "V"),
+ (0x1FB2, "M", "ὰι"),
+ (0x1FB3, "M", "αι"),
+ (0x1FB4, "M", "άι"),
+ (0x1FB5, "X"),
+ (0x1FB6, "V"),
+ (0x1FB7, "M", "ᾶι"),
+ (0x1FB8, "M", "ᾰ"),
+ (0x1FB9, "M", "ᾱ"),
+ (0x1FBA, "M", "ὰ"),
+ (0x1FBB, "M", "ά"),
+ (0x1FBC, "M", "αι"),
+ (0x1FBD, "3", " ̓"),
+ (0x1FBE, "M", "ι"),
+ (0x1FBF, "3", " ̓"),
+ (0x1FC0, "3", " ͂"),
+ (0x1FC1, "3", " ̈͂"),
+ (0x1FC2, "M", "ὴι"),
+ (0x1FC3, "M", "ηι"),
+ (0x1FC4, "M", "ήι"),
+ (0x1FC5, "X"),
+ (0x1FC6, "V"),
+ (0x1FC7, "M", "ῆι"),
+ (0x1FC8, "M", "ὲ"),
+ (0x1FC9, "M", "έ"),
+ (0x1FCA, "M", "ὴ"),
+ (0x1FCB, "M", "ή"),
+ (0x1FCC, "M", "ηι"),
+ (0x1FCD, "3", " ̓̀"),
+ (0x1FCE, "3", " ̓́"),
+ (0x1FCF, "3", " ̓͂"),
+ (0x1FD0, "V"),
+ (0x1FD3, "M", "ΐ"),
+ (0x1FD4, "X"),
+ (0x1FD6, "V"),
+ (0x1FD8, "M", "ῐ"),
+ (0x1FD9, "M", "ῑ"),
+ (0x1FDA, "M", "ὶ"),
+ (0x1FDB, "M", "ί"),
+ (0x1FDC, "X"),
+ (0x1FDD, "3", " ̔̀"),
+ (0x1FDE, "3", " ̔́"),
+ (0x1FDF, "3", " ̔͂"),
+ (0x1FE0, "V"),
+ (0x1FE3, "M", "ΰ"),
+ (0x1FE4, "V"),
+ (0x1FE8, "M", "ῠ"),
+ (0x1FE9, "M", "ῡ"),
+ (0x1FEA, "M", "ὺ"),
+ (0x1FEB, "M", "ύ"),
+ (0x1FEC, "M", "ῥ"),
+ (0x1FED, "3", " ̈̀"),
+ (0x1FEE, "3", " ̈́"),
+ (0x1FEF, "3", "`"),
+ (0x1FF0, "X"),
+ (0x1FF2, "M", "ὼι"),
+ (0x1FF3, "M", "ωι"),
+ (0x1FF4, "M", "ώι"),
+ (0x1FF5, "X"),
+ (0x1FF6, "V"),
+ ]
+
+
+def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1FF7, "M", "ῶι"),
+ (0x1FF8, "M", "ὸ"),
+ (0x1FF9, "M", "ό"),
+ (0x1FFA, "M", "ὼ"),
+ (0x1FFB, "M", "ώ"),
+ (0x1FFC, "M", "ωι"),
+ (0x1FFD, "3", " ́"),
+ (0x1FFE, "3", " ̔"),
+ (0x1FFF, "X"),
+ (0x2000, "3", " "),
+ (0x200B, "I"),
+ (0x200C, "D", ""),
+ (0x200E, "X"),
+ (0x2010, "V"),
+ (0x2011, "M", "‐"),
+ (0x2012, "V"),
+ (0x2017, "3", " ̳"),
+ (0x2018, "V"),
+ (0x2024, "X"),
+ (0x2027, "V"),
+ (0x2028, "X"),
+ (0x202F, "3", " "),
+ (0x2030, "V"),
+ (0x2033, "M", "′′"),
+ (0x2034, "M", "′′′"),
+ (0x2035, "V"),
+ (0x2036, "M", "‵‵"),
+ (0x2037, "M", "‵‵‵"),
+ (0x2038, "V"),
+ (0x203C, "3", "!!"),
+ (0x203D, "V"),
+ (0x203E, "3", " ̅"),
+ (0x203F, "V"),
+ (0x2047, "3", "??"),
+ (0x2048, "3", "?!"),
+ (0x2049, "3", "!?"),
+ (0x204A, "V"),
+ (0x2057, "M", "′′′′"),
+ (0x2058, "V"),
+ (0x205F, "3", " "),
+ (0x2060, "I"),
+ (0x2061, "X"),
+ (0x2064, "I"),
+ (0x2065, "X"),
+ (0x2070, "M", "0"),
+ (0x2071, "M", "i"),
+ (0x2072, "X"),
+ (0x2074, "M", "4"),
+ (0x2075, "M", "5"),
+ (0x2076, "M", "6"),
+ (0x2077, "M", "7"),
+ (0x2078, "M", "8"),
+ (0x2079, "M", "9"),
+ (0x207A, "3", "+"),
+ (0x207B, "M", "−"),
+ (0x207C, "3", "="),
+ (0x207D, "3", "("),
+ (0x207E, "3", ")"),
+ (0x207F, "M", "n"),
+ (0x2080, "M", "0"),
+ (0x2081, "M", "1"),
+ (0x2082, "M", "2"),
+ (0x2083, "M", "3"),
+ (0x2084, "M", "4"),
+ (0x2085, "M", "5"),
+ (0x2086, "M", "6"),
+ (0x2087, "M", "7"),
+ (0x2088, "M", "8"),
+ (0x2089, "M", "9"),
+ (0x208A, "3", "+"),
+ (0x208B, "M", "−"),
+ (0x208C, "3", "="),
+ (0x208D, "3", "("),
+ (0x208E, "3", ")"),
+ (0x208F, "X"),
+ (0x2090, "M", "a"),
+ (0x2091, "M", "e"),
+ (0x2092, "M", "o"),
+ (0x2093, "M", "x"),
+ (0x2094, "M", "ə"),
+ (0x2095, "M", "h"),
+ (0x2096, "M", "k"),
+ (0x2097, "M", "l"),
+ (0x2098, "M", "m"),
+ (0x2099, "M", "n"),
+ (0x209A, "M", "p"),
+ (0x209B, "M", "s"),
+ (0x209C, "M", "t"),
+ (0x209D, "X"),
+ (0x20A0, "V"),
+ (0x20A8, "M", "rs"),
+ (0x20A9, "V"),
+ (0x20C1, "X"),
+ (0x20D0, "V"),
+ (0x20F1, "X"),
+ (0x2100, "3", "a/c"),
+ (0x2101, "3", "a/s"),
+ (0x2102, "M", "c"),
+ (0x2103, "M", "°c"),
+ (0x2104, "V"),
+ ]
+
+
+def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2105, "3", "c/o"),
+ (0x2106, "3", "c/u"),
+ (0x2107, "M", "ɛ"),
+ (0x2108, "V"),
+ (0x2109, "M", "°f"),
+ (0x210A, "M", "g"),
+ (0x210B, "M", "h"),
+ (0x210F, "M", "ħ"),
+ (0x2110, "M", "i"),
+ (0x2112, "M", "l"),
+ (0x2114, "V"),
+ (0x2115, "M", "n"),
+ (0x2116, "M", "no"),
+ (0x2117, "V"),
+ (0x2119, "M", "p"),
+ (0x211A, "M", "q"),
+ (0x211B, "M", "r"),
+ (0x211E, "V"),
+ (0x2120, "M", "sm"),
+ (0x2121, "M", "tel"),
+ (0x2122, "M", "tm"),
+ (0x2123, "V"),
+ (0x2124, "M", "z"),
+ (0x2125, "V"),
+ (0x2126, "M", "ω"),
+ (0x2127, "V"),
+ (0x2128, "M", "z"),
+ (0x2129, "V"),
+ (0x212A, "M", "k"),
+ (0x212B, "M", "å"),
+ (0x212C, "M", "b"),
+ (0x212D, "M", "c"),
+ (0x212E, "V"),
+ (0x212F, "M", "e"),
+ (0x2131, "M", "f"),
+ (0x2132, "X"),
+ (0x2133, "M", "m"),
+ (0x2134, "M", "o"),
+ (0x2135, "M", "א"),
+ (0x2136, "M", "ב"),
+ (0x2137, "M", "ג"),
+ (0x2138, "M", "ד"),
+ (0x2139, "M", "i"),
+ (0x213A, "V"),
+ (0x213B, "M", "fax"),
+ (0x213C, "M", "π"),
+ (0x213D, "M", "γ"),
+ (0x213F, "M", "π"),
+ (0x2140, "M", "∑"),
+ (0x2141, "V"),
+ (0x2145, "M", "d"),
+ (0x2147, "M", "e"),
+ (0x2148, "M", "i"),
+ (0x2149, "M", "j"),
+ (0x214A, "V"),
+ (0x2150, "M", "1⁄7"),
+ (0x2151, "M", "1⁄9"),
+ (0x2152, "M", "1⁄10"),
+ (0x2153, "M", "1⁄3"),
+ (0x2154, "M", "2⁄3"),
+ (0x2155, "M", "1⁄5"),
+ (0x2156, "M", "2⁄5"),
+ (0x2157, "M", "3⁄5"),
+ (0x2158, "M", "4⁄5"),
+ (0x2159, "M", "1⁄6"),
+ (0x215A, "M", "5⁄6"),
+ (0x215B, "M", "1⁄8"),
+ (0x215C, "M", "3⁄8"),
+ (0x215D, "M", "5⁄8"),
+ (0x215E, "M", "7⁄8"),
+ (0x215F, "M", "1⁄"),
+ (0x2160, "M", "i"),
+ (0x2161, "M", "ii"),
+ (0x2162, "M", "iii"),
+ (0x2163, "M", "iv"),
+ (0x2164, "M", "v"),
+ (0x2165, "M", "vi"),
+ (0x2166, "M", "vii"),
+ (0x2167, "M", "viii"),
+ (0x2168, "M", "ix"),
+ (0x2169, "M", "x"),
+ (0x216A, "M", "xi"),
+ (0x216B, "M", "xii"),
+ (0x216C, "M", "l"),
+ (0x216D, "M", "c"),
+ (0x216E, "M", "d"),
+ (0x216F, "M", "m"),
+ (0x2170, "M", "i"),
+ (0x2171, "M", "ii"),
+ (0x2172, "M", "iii"),
+ (0x2173, "M", "iv"),
+ (0x2174, "M", "v"),
+ (0x2175, "M", "vi"),
+ (0x2176, "M", "vii"),
+ (0x2177, "M", "viii"),
+ (0x2178, "M", "ix"),
+ (0x2179, "M", "x"),
+ (0x217A, "M", "xi"),
+ (0x217B, "M", "xii"),
+ (0x217C, "M", "l"),
+ ]
+
+
+def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x217D, "M", "c"),
+ (0x217E, "M", "d"),
+ (0x217F, "M", "m"),
+ (0x2180, "V"),
+ (0x2183, "X"),
+ (0x2184, "V"),
+ (0x2189, "M", "0⁄3"),
+ (0x218A, "V"),
+ (0x218C, "X"),
+ (0x2190, "V"),
+ (0x222C, "M", "∫∫"),
+ (0x222D, "M", "∫∫∫"),
+ (0x222E, "V"),
+ (0x222F, "M", "∮∮"),
+ (0x2230, "M", "∮∮∮"),
+ (0x2231, "V"),
+ (0x2329, "M", "〈"),
+ (0x232A, "M", "〉"),
+ (0x232B, "V"),
+ (0x2427, "X"),
+ (0x2440, "V"),
+ (0x244B, "X"),
+ (0x2460, "M", "1"),
+ (0x2461, "M", "2"),
+ (0x2462, "M", "3"),
+ (0x2463, "M", "4"),
+ (0x2464, "M", "5"),
+ (0x2465, "M", "6"),
+ (0x2466, "M", "7"),
+ (0x2467, "M", "8"),
+ (0x2468, "M", "9"),
+ (0x2469, "M", "10"),
+ (0x246A, "M", "11"),
+ (0x246B, "M", "12"),
+ (0x246C, "M", "13"),
+ (0x246D, "M", "14"),
+ (0x246E, "M", "15"),
+ (0x246F, "M", "16"),
+ (0x2470, "M", "17"),
+ (0x2471, "M", "18"),
+ (0x2472, "M", "19"),
+ (0x2473, "M", "20"),
+ (0x2474, "3", "(1)"),
+ (0x2475, "3", "(2)"),
+ (0x2476, "3", "(3)"),
+ (0x2477, "3", "(4)"),
+ (0x2478, "3", "(5)"),
+ (0x2479, "3", "(6)"),
+ (0x247A, "3", "(7)"),
+ (0x247B, "3", "(8)"),
+ (0x247C, "3", "(9)"),
+ (0x247D, "3", "(10)"),
+ (0x247E, "3", "(11)"),
+ (0x247F, "3", "(12)"),
+ (0x2480, "3", "(13)"),
+ (0x2481, "3", "(14)"),
+ (0x2482, "3", "(15)"),
+ (0x2483, "3", "(16)"),
+ (0x2484, "3", "(17)"),
+ (0x2485, "3", "(18)"),
+ (0x2486, "3", "(19)"),
+ (0x2487, "3", "(20)"),
+ (0x2488, "X"),
+ (0x249C, "3", "(a)"),
+ (0x249D, "3", "(b)"),
+ (0x249E, "3", "(c)"),
+ (0x249F, "3", "(d)"),
+ (0x24A0, "3", "(e)"),
+ (0x24A1, "3", "(f)"),
+ (0x24A2, "3", "(g)"),
+ (0x24A3, "3", "(h)"),
+ (0x24A4, "3", "(i)"),
+ (0x24A5, "3", "(j)"),
+ (0x24A6, "3", "(k)"),
+ (0x24A7, "3", "(l)"),
+ (0x24A8, "3", "(m)"),
+ (0x24A9, "3", "(n)"),
+ (0x24AA, "3", "(o)"),
+ (0x24AB, "3", "(p)"),
+ (0x24AC, "3", "(q)"),
+ (0x24AD, "3", "(r)"),
+ (0x24AE, "3", "(s)"),
+ (0x24AF, "3", "(t)"),
+ (0x24B0, "3", "(u)"),
+ (0x24B1, "3", "(v)"),
+ (0x24B2, "3", "(w)"),
+ (0x24B3, "3", "(x)"),
+ (0x24B4, "3", "(y)"),
+ (0x24B5, "3", "(z)"),
+ (0x24B6, "M", "a"),
+ (0x24B7, "M", "b"),
+ (0x24B8, "M", "c"),
+ (0x24B9, "M", "d"),
+ (0x24BA, "M", "e"),
+ (0x24BB, "M", "f"),
+ (0x24BC, "M", "g"),
+ (0x24BD, "M", "h"),
+ (0x24BE, "M", "i"),
+ (0x24BF, "M", "j"),
+ (0x24C0, "M", "k"),
+ ]
+
+
+def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x24C1, "M", "l"),
+ (0x24C2, "M", "m"),
+ (0x24C3, "M", "n"),
+ (0x24C4, "M", "o"),
+ (0x24C5, "M", "p"),
+ (0x24C6, "M", "q"),
+ (0x24C7, "M", "r"),
+ (0x24C8, "M", "s"),
+ (0x24C9, "M", "t"),
+ (0x24CA, "M", "u"),
+ (0x24CB, "M", "v"),
+ (0x24CC, "M", "w"),
+ (0x24CD, "M", "x"),
+ (0x24CE, "M", "y"),
+ (0x24CF, "M", "z"),
+ (0x24D0, "M", "a"),
+ (0x24D1, "M", "b"),
+ (0x24D2, "M", "c"),
+ (0x24D3, "M", "d"),
+ (0x24D4, "M", "e"),
+ (0x24D5, "M", "f"),
+ (0x24D6, "M", "g"),
+ (0x24D7, "M", "h"),
+ (0x24D8, "M", "i"),
+ (0x24D9, "M", "j"),
+ (0x24DA, "M", "k"),
+ (0x24DB, "M", "l"),
+ (0x24DC, "M", "m"),
+ (0x24DD, "M", "n"),
+ (0x24DE, "M", "o"),
+ (0x24DF, "M", "p"),
+ (0x24E0, "M", "q"),
+ (0x24E1, "M", "r"),
+ (0x24E2, "M", "s"),
+ (0x24E3, "M", "t"),
+ (0x24E4, "M", "u"),
+ (0x24E5, "M", "v"),
+ (0x24E6, "M", "w"),
+ (0x24E7, "M", "x"),
+ (0x24E8, "M", "y"),
+ (0x24E9, "M", "z"),
+ (0x24EA, "M", "0"),
+ (0x24EB, "V"),
+ (0x2A0C, "M", "∫∫∫∫"),
+ (0x2A0D, "V"),
+ (0x2A74, "3", "::="),
+ (0x2A75, "3", "=="),
+ (0x2A76, "3", "==="),
+ (0x2A77, "V"),
+ (0x2ADC, "M", "⫝̸"),
+ (0x2ADD, "V"),
+ (0x2B74, "X"),
+ (0x2B76, "V"),
+ (0x2B96, "X"),
+ (0x2B97, "V"),
+ (0x2C00, "M", "ⰰ"),
+ (0x2C01, "M", "ⰱ"),
+ (0x2C02, "M", "ⰲ"),
+ (0x2C03, "M", "ⰳ"),
+ (0x2C04, "M", "ⰴ"),
+ (0x2C05, "M", "ⰵ"),
+ (0x2C06, "M", "ⰶ"),
+ (0x2C07, "M", "ⰷ"),
+ (0x2C08, "M", "ⰸ"),
+ (0x2C09, "M", "ⰹ"),
+ (0x2C0A, "M", "ⰺ"),
+ (0x2C0B, "M", "ⰻ"),
+ (0x2C0C, "M", "ⰼ"),
+ (0x2C0D, "M", "ⰽ"),
+ (0x2C0E, "M", "ⰾ"),
+ (0x2C0F, "M", "ⰿ"),
+ (0x2C10, "M", "ⱀ"),
+ (0x2C11, "M", "ⱁ"),
+ (0x2C12, "M", "ⱂ"),
+ (0x2C13, "M", "ⱃ"),
+ (0x2C14, "M", "ⱄ"),
+ (0x2C15, "M", "ⱅ"),
+ (0x2C16, "M", "ⱆ"),
+ (0x2C17, "M", "ⱇ"),
+ (0x2C18, "M", "ⱈ"),
+ (0x2C19, "M", "ⱉ"),
+ (0x2C1A, "M", "ⱊ"),
+ (0x2C1B, "M", "ⱋ"),
+ (0x2C1C, "M", "ⱌ"),
+ (0x2C1D, "M", "ⱍ"),
+ (0x2C1E, "M", "ⱎ"),
+ (0x2C1F, "M", "ⱏ"),
+ (0x2C20, "M", "ⱐ"),
+ (0x2C21, "M", "ⱑ"),
+ (0x2C22, "M", "ⱒ"),
+ (0x2C23, "M", "ⱓ"),
+ (0x2C24, "M", "ⱔ"),
+ (0x2C25, "M", "ⱕ"),
+ (0x2C26, "M", "ⱖ"),
+ (0x2C27, "M", "ⱗ"),
+ (0x2C28, "M", "ⱘ"),
+ (0x2C29, "M", "ⱙ"),
+ (0x2C2A, "M", "ⱚ"),
+ (0x2C2B, "M", "ⱛ"),
+ (0x2C2C, "M", "ⱜ"),
+ ]
+
+
+def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2C2D, "M", "ⱝ"),
+ (0x2C2E, "M", "ⱞ"),
+ (0x2C2F, "M", "ⱟ"),
+ (0x2C30, "V"),
+ (0x2C60, "M", "ⱡ"),
+ (0x2C61, "V"),
+ (0x2C62, "M", "ɫ"),
+ (0x2C63, "M", "ᵽ"),
+ (0x2C64, "M", "ɽ"),
+ (0x2C65, "V"),
+ (0x2C67, "M", "ⱨ"),
+ (0x2C68, "V"),
+ (0x2C69, "M", "ⱪ"),
+ (0x2C6A, "V"),
+ (0x2C6B, "M", "ⱬ"),
+ (0x2C6C, "V"),
+ (0x2C6D, "M", "ɑ"),
+ (0x2C6E, "M", "ɱ"),
+ (0x2C6F, "M", "ɐ"),
+ (0x2C70, "M", "ɒ"),
+ (0x2C71, "V"),
+ (0x2C72, "M", "ⱳ"),
+ (0x2C73, "V"),
+ (0x2C75, "M", "ⱶ"),
+ (0x2C76, "V"),
+ (0x2C7C, "M", "j"),
+ (0x2C7D, "M", "v"),
+ (0x2C7E, "M", "ȿ"),
+ (0x2C7F, "M", "ɀ"),
+ (0x2C80, "M", "ⲁ"),
+ (0x2C81, "V"),
+ (0x2C82, "M", "ⲃ"),
+ (0x2C83, "V"),
+ (0x2C84, "M", "ⲅ"),
+ (0x2C85, "V"),
+ (0x2C86, "M", "ⲇ"),
+ (0x2C87, "V"),
+ (0x2C88, "M", "ⲉ"),
+ (0x2C89, "V"),
+ (0x2C8A, "M", "ⲋ"),
+ (0x2C8B, "V"),
+ (0x2C8C, "M", "ⲍ"),
+ (0x2C8D, "V"),
+ (0x2C8E, "M", "ⲏ"),
+ (0x2C8F, "V"),
+ (0x2C90, "M", "ⲑ"),
+ (0x2C91, "V"),
+ (0x2C92, "M", "ⲓ"),
+ (0x2C93, "V"),
+ (0x2C94, "M", "ⲕ"),
+ (0x2C95, "V"),
+ (0x2C96, "M", "ⲗ"),
+ (0x2C97, "V"),
+ (0x2C98, "M", "ⲙ"),
+ (0x2C99, "V"),
+ (0x2C9A, "M", "ⲛ"),
+ (0x2C9B, "V"),
+ (0x2C9C, "M", "ⲝ"),
+ (0x2C9D, "V"),
+ (0x2C9E, "M", "ⲟ"),
+ (0x2C9F, "V"),
+ (0x2CA0, "M", "ⲡ"),
+ (0x2CA1, "V"),
+ (0x2CA2, "M", "ⲣ"),
+ (0x2CA3, "V"),
+ (0x2CA4, "M", "ⲥ"),
+ (0x2CA5, "V"),
+ (0x2CA6, "M", "ⲧ"),
+ (0x2CA7, "V"),
+ (0x2CA8, "M", "ⲩ"),
+ (0x2CA9, "V"),
+ (0x2CAA, "M", "ⲫ"),
+ (0x2CAB, "V"),
+ (0x2CAC, "M", "ⲭ"),
+ (0x2CAD, "V"),
+ (0x2CAE, "M", "ⲯ"),
+ (0x2CAF, "V"),
+ (0x2CB0, "M", "ⲱ"),
+ (0x2CB1, "V"),
+ (0x2CB2, "M", "ⲳ"),
+ (0x2CB3, "V"),
+ (0x2CB4, "M", "ⲵ"),
+ (0x2CB5, "V"),
+ (0x2CB6, "M", "ⲷ"),
+ (0x2CB7, "V"),
+ (0x2CB8, "M", "ⲹ"),
+ (0x2CB9, "V"),
+ (0x2CBA, "M", "ⲻ"),
+ (0x2CBB, "V"),
+ (0x2CBC, "M", "ⲽ"),
+ (0x2CBD, "V"),
+ (0x2CBE, "M", "ⲿ"),
+ (0x2CBF, "V"),
+ (0x2CC0, "M", "ⳁ"),
+ (0x2CC1, "V"),
+ (0x2CC2, "M", "ⳃ"),
+ (0x2CC3, "V"),
+ (0x2CC4, "M", "ⳅ"),
+ (0x2CC5, "V"),
+ (0x2CC6, "M", "ⳇ"),
+ ]
+
+
+def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2CC7, "V"),
+ (0x2CC8, "M", "ⳉ"),
+ (0x2CC9, "V"),
+ (0x2CCA, "M", "ⳋ"),
+ (0x2CCB, "V"),
+ (0x2CCC, "M", "ⳍ"),
+ (0x2CCD, "V"),
+ (0x2CCE, "M", "ⳏ"),
+ (0x2CCF, "V"),
+ (0x2CD0, "M", "ⳑ"),
+ (0x2CD1, "V"),
+ (0x2CD2, "M", "ⳓ"),
+ (0x2CD3, "V"),
+ (0x2CD4, "M", "ⳕ"),
+ (0x2CD5, "V"),
+ (0x2CD6, "M", "ⳗ"),
+ (0x2CD7, "V"),
+ (0x2CD8, "M", "ⳙ"),
+ (0x2CD9, "V"),
+ (0x2CDA, "M", "ⳛ"),
+ (0x2CDB, "V"),
+ (0x2CDC, "M", "ⳝ"),
+ (0x2CDD, "V"),
+ (0x2CDE, "M", "ⳟ"),
+ (0x2CDF, "V"),
+ (0x2CE0, "M", "ⳡ"),
+ (0x2CE1, "V"),
+ (0x2CE2, "M", "ⳣ"),
+ (0x2CE3, "V"),
+ (0x2CEB, "M", "ⳬ"),
+ (0x2CEC, "V"),
+ (0x2CED, "M", "ⳮ"),
+ (0x2CEE, "V"),
+ (0x2CF2, "M", "ⳳ"),
+ (0x2CF3, "V"),
+ (0x2CF4, "X"),
+ (0x2CF9, "V"),
+ (0x2D26, "X"),
+ (0x2D27, "V"),
+ (0x2D28, "X"),
+ (0x2D2D, "V"),
+ (0x2D2E, "X"),
+ (0x2D30, "V"),
+ (0x2D68, "X"),
+ (0x2D6F, "M", "ⵡ"),
+ (0x2D70, "V"),
+ (0x2D71, "X"),
+ (0x2D7F, "V"),
+ (0x2D97, "X"),
+ (0x2DA0, "V"),
+ (0x2DA7, "X"),
+ (0x2DA8, "V"),
+ (0x2DAF, "X"),
+ (0x2DB0, "V"),
+ (0x2DB7, "X"),
+ (0x2DB8, "V"),
+ (0x2DBF, "X"),
+ (0x2DC0, "V"),
+ (0x2DC7, "X"),
+ (0x2DC8, "V"),
+ (0x2DCF, "X"),
+ (0x2DD0, "V"),
+ (0x2DD7, "X"),
+ (0x2DD8, "V"),
+ (0x2DDF, "X"),
+ (0x2DE0, "V"),
+ (0x2E5E, "X"),
+ (0x2E80, "V"),
+ (0x2E9A, "X"),
+ (0x2E9B, "V"),
+ (0x2E9F, "M", "母"),
+ (0x2EA0, "V"),
+ (0x2EF3, "M", "龟"),
+ (0x2EF4, "X"),
+ (0x2F00, "M", "一"),
+ (0x2F01, "M", "丨"),
+ (0x2F02, "M", "丶"),
+ (0x2F03, "M", "丿"),
+ (0x2F04, "M", "乙"),
+ (0x2F05, "M", "亅"),
+ (0x2F06, "M", "二"),
+ (0x2F07, "M", "亠"),
+ (0x2F08, "M", "人"),
+ (0x2F09, "M", "儿"),
+ (0x2F0A, "M", "入"),
+ (0x2F0B, "M", "八"),
+ (0x2F0C, "M", "冂"),
+ (0x2F0D, "M", "冖"),
+ (0x2F0E, "M", "冫"),
+ (0x2F0F, "M", "几"),
+ (0x2F10, "M", "凵"),
+ (0x2F11, "M", "刀"),
+ (0x2F12, "M", "力"),
+ (0x2F13, "M", "勹"),
+ (0x2F14, "M", "匕"),
+ (0x2F15, "M", "匚"),
+ (0x2F16, "M", "匸"),
+ (0x2F17, "M", "十"),
+ (0x2F18, "M", "卜"),
+ (0x2F19, "M", "卩"),
+ ]
+
+
+def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F1A, "M", "厂"),
+ (0x2F1B, "M", "厶"),
+ (0x2F1C, "M", "又"),
+ (0x2F1D, "M", "口"),
+ (0x2F1E, "M", "囗"),
+ (0x2F1F, "M", "土"),
+ (0x2F20, "M", "士"),
+ (0x2F21, "M", "夂"),
+ (0x2F22, "M", "夊"),
+ (0x2F23, "M", "夕"),
+ (0x2F24, "M", "大"),
+ (0x2F25, "M", "女"),
+ (0x2F26, "M", "子"),
+ (0x2F27, "M", "宀"),
+ (0x2F28, "M", "寸"),
+ (0x2F29, "M", "小"),
+ (0x2F2A, "M", "尢"),
+ (0x2F2B, "M", "尸"),
+ (0x2F2C, "M", "屮"),
+ (0x2F2D, "M", "山"),
+ (0x2F2E, "M", "巛"),
+ (0x2F2F, "M", "工"),
+ (0x2F30, "M", "己"),
+ (0x2F31, "M", "巾"),
+ (0x2F32, "M", "干"),
+ (0x2F33, "M", "幺"),
+ (0x2F34, "M", "广"),
+ (0x2F35, "M", "廴"),
+ (0x2F36, "M", "廾"),
+ (0x2F37, "M", "弋"),
+ (0x2F38, "M", "弓"),
+ (0x2F39, "M", "彐"),
+ (0x2F3A, "M", "彡"),
+ (0x2F3B, "M", "彳"),
+ (0x2F3C, "M", "心"),
+ (0x2F3D, "M", "戈"),
+ (0x2F3E, "M", "戶"),
+ (0x2F3F, "M", "手"),
+ (0x2F40, "M", "支"),
+ (0x2F41, "M", "攴"),
+ (0x2F42, "M", "文"),
+ (0x2F43, "M", "斗"),
+ (0x2F44, "M", "斤"),
+ (0x2F45, "M", "方"),
+ (0x2F46, "M", "无"),
+ (0x2F47, "M", "日"),
+ (0x2F48, "M", "曰"),
+ (0x2F49, "M", "月"),
+ (0x2F4A, "M", "木"),
+ (0x2F4B, "M", "欠"),
+ (0x2F4C, "M", "止"),
+ (0x2F4D, "M", "歹"),
+ (0x2F4E, "M", "殳"),
+ (0x2F4F, "M", "毋"),
+ (0x2F50, "M", "比"),
+ (0x2F51, "M", "毛"),
+ (0x2F52, "M", "氏"),
+ (0x2F53, "M", "气"),
+ (0x2F54, "M", "水"),
+ (0x2F55, "M", "火"),
+ (0x2F56, "M", "爪"),
+ (0x2F57, "M", "父"),
+ (0x2F58, "M", "爻"),
+ (0x2F59, "M", "爿"),
+ (0x2F5A, "M", "片"),
+ (0x2F5B, "M", "牙"),
+ (0x2F5C, "M", "牛"),
+ (0x2F5D, "M", "犬"),
+ (0x2F5E, "M", "玄"),
+ (0x2F5F, "M", "玉"),
+ (0x2F60, "M", "瓜"),
+ (0x2F61, "M", "瓦"),
+ (0x2F62, "M", "甘"),
+ (0x2F63, "M", "生"),
+ (0x2F64, "M", "用"),
+ (0x2F65, "M", "田"),
+ (0x2F66, "M", "疋"),
+ (0x2F67, "M", "疒"),
+ (0x2F68, "M", "癶"),
+ (0x2F69, "M", "白"),
+ (0x2F6A, "M", "皮"),
+ (0x2F6B, "M", "皿"),
+ (0x2F6C, "M", "目"),
+ (0x2F6D, "M", "矛"),
+ (0x2F6E, "M", "矢"),
+ (0x2F6F, "M", "石"),
+ (0x2F70, "M", "示"),
+ (0x2F71, "M", "禸"),
+ (0x2F72, "M", "禾"),
+ (0x2F73, "M", "穴"),
+ (0x2F74, "M", "立"),
+ (0x2F75, "M", "竹"),
+ (0x2F76, "M", "米"),
+ (0x2F77, "M", "糸"),
+ (0x2F78, "M", "缶"),
+ (0x2F79, "M", "网"),
+ (0x2F7A, "M", "羊"),
+ (0x2F7B, "M", "羽"),
+ (0x2F7C, "M", "老"),
+ (0x2F7D, "M", "而"),
+ ]
+
+
+def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F7E, "M", "耒"),
+ (0x2F7F, "M", "耳"),
+ (0x2F80, "M", "聿"),
+ (0x2F81, "M", "肉"),
+ (0x2F82, "M", "臣"),
+ (0x2F83, "M", "自"),
+ (0x2F84, "M", "至"),
+ (0x2F85, "M", "臼"),
+ (0x2F86, "M", "舌"),
+ (0x2F87, "M", "舛"),
+ (0x2F88, "M", "舟"),
+ (0x2F89, "M", "艮"),
+ (0x2F8A, "M", "色"),
+ (0x2F8B, "M", "艸"),
+ (0x2F8C, "M", "虍"),
+ (0x2F8D, "M", "虫"),
+ (0x2F8E, "M", "血"),
+ (0x2F8F, "M", "行"),
+ (0x2F90, "M", "衣"),
+ (0x2F91, "M", "襾"),
+ (0x2F92, "M", "見"),
+ (0x2F93, "M", "角"),
+ (0x2F94, "M", "言"),
+ (0x2F95, "M", "谷"),
+ (0x2F96, "M", "豆"),
+ (0x2F97, "M", "豕"),
+ (0x2F98, "M", "豸"),
+ (0x2F99, "M", "貝"),
+ (0x2F9A, "M", "赤"),
+ (0x2F9B, "M", "走"),
+ (0x2F9C, "M", "足"),
+ (0x2F9D, "M", "身"),
+ (0x2F9E, "M", "車"),
+ (0x2F9F, "M", "辛"),
+ (0x2FA0, "M", "辰"),
+ (0x2FA1, "M", "辵"),
+ (0x2FA2, "M", "邑"),
+ (0x2FA3, "M", "酉"),
+ (0x2FA4, "M", "釆"),
+ (0x2FA5, "M", "里"),
+ (0x2FA6, "M", "金"),
+ (0x2FA7, "M", "長"),
+ (0x2FA8, "M", "門"),
+ (0x2FA9, "M", "阜"),
+ (0x2FAA, "M", "隶"),
+ (0x2FAB, "M", "隹"),
+ (0x2FAC, "M", "雨"),
+ (0x2FAD, "M", "靑"),
+ (0x2FAE, "M", "非"),
+ (0x2FAF, "M", "面"),
+ (0x2FB0, "M", "革"),
+ (0x2FB1, "M", "韋"),
+ (0x2FB2, "M", "韭"),
+ (0x2FB3, "M", "音"),
+ (0x2FB4, "M", "頁"),
+ (0x2FB5, "M", "風"),
+ (0x2FB6, "M", "飛"),
+ (0x2FB7, "M", "食"),
+ (0x2FB8, "M", "首"),
+ (0x2FB9, "M", "香"),
+ (0x2FBA, "M", "馬"),
+ (0x2FBB, "M", "骨"),
+ (0x2FBC, "M", "高"),
+ (0x2FBD, "M", "髟"),
+ (0x2FBE, "M", "鬥"),
+ (0x2FBF, "M", "鬯"),
+ (0x2FC0, "M", "鬲"),
+ (0x2FC1, "M", "鬼"),
+ (0x2FC2, "M", "魚"),
+ (0x2FC3, "M", "鳥"),
+ (0x2FC4, "M", "鹵"),
+ (0x2FC5, "M", "鹿"),
+ (0x2FC6, "M", "麥"),
+ (0x2FC7, "M", "麻"),
+ (0x2FC8, "M", "黃"),
+ (0x2FC9, "M", "黍"),
+ (0x2FCA, "M", "黑"),
+ (0x2FCB, "M", "黹"),
+ (0x2FCC, "M", "黽"),
+ (0x2FCD, "M", "鼎"),
+ (0x2FCE, "M", "鼓"),
+ (0x2FCF, "M", "鼠"),
+ (0x2FD0, "M", "鼻"),
+ (0x2FD1, "M", "齊"),
+ (0x2FD2, "M", "齒"),
+ (0x2FD3, "M", "龍"),
+ (0x2FD4, "M", "龜"),
+ (0x2FD5, "M", "龠"),
+ (0x2FD6, "X"),
+ (0x3000, "3", " "),
+ (0x3001, "V"),
+ (0x3002, "M", "."),
+ (0x3003, "V"),
+ (0x3036, "M", "〒"),
+ (0x3037, "V"),
+ (0x3038, "M", "十"),
+ (0x3039, "M", "卄"),
+ (0x303A, "M", "卅"),
+ (0x303B, "V"),
+ (0x3040, "X"),
+ ]
+
+
+def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3041, "V"),
+ (0x3097, "X"),
+ (0x3099, "V"),
+ (0x309B, "3", " ゙"),
+ (0x309C, "3", " ゚"),
+ (0x309D, "V"),
+ (0x309F, "M", "より"),
+ (0x30A0, "V"),
+ (0x30FF, "M", "コト"),
+ (0x3100, "X"),
+ (0x3105, "V"),
+ (0x3130, "X"),
+ (0x3131, "M", "ᄀ"),
+ (0x3132, "M", "ᄁ"),
+ (0x3133, "M", "ᆪ"),
+ (0x3134, "M", "ᄂ"),
+ (0x3135, "M", "ᆬ"),
+ (0x3136, "M", "ᆭ"),
+ (0x3137, "M", "ᄃ"),
+ (0x3138, "M", "ᄄ"),
+ (0x3139, "M", "ᄅ"),
+ (0x313A, "M", "ᆰ"),
+ (0x313B, "M", "ᆱ"),
+ (0x313C, "M", "ᆲ"),
+ (0x313D, "M", "ᆳ"),
+ (0x313E, "M", "ᆴ"),
+ (0x313F, "M", "ᆵ"),
+ (0x3140, "M", "ᄚ"),
+ (0x3141, "M", "ᄆ"),
+ (0x3142, "M", "ᄇ"),
+ (0x3143, "M", "ᄈ"),
+ (0x3144, "M", "ᄡ"),
+ (0x3145, "M", "ᄉ"),
+ (0x3146, "M", "ᄊ"),
+ (0x3147, "M", "ᄋ"),
+ (0x3148, "M", "ᄌ"),
+ (0x3149, "M", "ᄍ"),
+ (0x314A, "M", "ᄎ"),
+ (0x314B, "M", "ᄏ"),
+ (0x314C, "M", "ᄐ"),
+ (0x314D, "M", "ᄑ"),
+ (0x314E, "M", "ᄒ"),
+ (0x314F, "M", "ᅡ"),
+ (0x3150, "M", "ᅢ"),
+ (0x3151, "M", "ᅣ"),
+ (0x3152, "M", "ᅤ"),
+ (0x3153, "M", "ᅥ"),
+ (0x3154, "M", "ᅦ"),
+ (0x3155, "M", "ᅧ"),
+ (0x3156, "M", "ᅨ"),
+ (0x3157, "M", "ᅩ"),
+ (0x3158, "M", "ᅪ"),
+ (0x3159, "M", "ᅫ"),
+ (0x315A, "M", "ᅬ"),
+ (0x315B, "M", "ᅭ"),
+ (0x315C, "M", "ᅮ"),
+ (0x315D, "M", "ᅯ"),
+ (0x315E, "M", "ᅰ"),
+ (0x315F, "M", "ᅱ"),
+ (0x3160, "M", "ᅲ"),
+ (0x3161, "M", "ᅳ"),
+ (0x3162, "M", "ᅴ"),
+ (0x3163, "M", "ᅵ"),
+ (0x3164, "X"),
+ (0x3165, "M", "ᄔ"),
+ (0x3166, "M", "ᄕ"),
+ (0x3167, "M", "ᇇ"),
+ (0x3168, "M", "ᇈ"),
+ (0x3169, "M", "ᇌ"),
+ (0x316A, "M", "ᇎ"),
+ (0x316B, "M", "ᇓ"),
+ (0x316C, "M", "ᇗ"),
+ (0x316D, "M", "ᇙ"),
+ (0x316E, "M", "ᄜ"),
+ (0x316F, "M", "ᇝ"),
+ (0x3170, "M", "ᇟ"),
+ (0x3171, "M", "ᄝ"),
+ (0x3172, "M", "ᄞ"),
+ (0x3173, "M", "ᄠ"),
+ (0x3174, "M", "ᄢ"),
+ (0x3175, "M", "ᄣ"),
+ (0x3176, "M", "ᄧ"),
+ (0x3177, "M", "ᄩ"),
+ (0x3178, "M", "ᄫ"),
+ (0x3179, "M", "ᄬ"),
+ (0x317A, "M", "ᄭ"),
+ (0x317B, "M", "ᄮ"),
+ (0x317C, "M", "ᄯ"),
+ (0x317D, "M", "ᄲ"),
+ (0x317E, "M", "ᄶ"),
+ (0x317F, "M", "ᅀ"),
+ (0x3180, "M", "ᅇ"),
+ (0x3181, "M", "ᅌ"),
+ (0x3182, "M", "ᇱ"),
+ (0x3183, "M", "ᇲ"),
+ (0x3184, "M", "ᅗ"),
+ (0x3185, "M", "ᅘ"),
+ (0x3186, "M", "ᅙ"),
+ (0x3187, "M", "ᆄ"),
+ (0x3188, "M", "ᆅ"),
+ ]
+
+
+def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3189, "M", "ᆈ"),
+ (0x318A, "M", "ᆑ"),
+ (0x318B, "M", "ᆒ"),
+ (0x318C, "M", "ᆔ"),
+ (0x318D, "M", "ᆞ"),
+ (0x318E, "M", "ᆡ"),
+ (0x318F, "X"),
+ (0x3190, "V"),
+ (0x3192, "M", "一"),
+ (0x3193, "M", "二"),
+ (0x3194, "M", "三"),
+ (0x3195, "M", "四"),
+ (0x3196, "M", "上"),
+ (0x3197, "M", "中"),
+ (0x3198, "M", "下"),
+ (0x3199, "M", "甲"),
+ (0x319A, "M", "乙"),
+ (0x319B, "M", "丙"),
+ (0x319C, "M", "丁"),
+ (0x319D, "M", "天"),
+ (0x319E, "M", "地"),
+ (0x319F, "M", "人"),
+ (0x31A0, "V"),
+ (0x31E4, "X"),
+ (0x31F0, "V"),
+ (0x3200, "3", "(ᄀ)"),
+ (0x3201, "3", "(ᄂ)"),
+ (0x3202, "3", "(ᄃ)"),
+ (0x3203, "3", "(ᄅ)"),
+ (0x3204, "3", "(ᄆ)"),
+ (0x3205, "3", "(ᄇ)"),
+ (0x3206, "3", "(ᄉ)"),
+ (0x3207, "3", "(ᄋ)"),
+ (0x3208, "3", "(ᄌ)"),
+ (0x3209, "3", "(ᄎ)"),
+ (0x320A, "3", "(ᄏ)"),
+ (0x320B, "3", "(ᄐ)"),
+ (0x320C, "3", "(ᄑ)"),
+ (0x320D, "3", "(ᄒ)"),
+ (0x320E, "3", "(가)"),
+ (0x320F, "3", "(나)"),
+ (0x3210, "3", "(다)"),
+ (0x3211, "3", "(라)"),
+ (0x3212, "3", "(마)"),
+ (0x3213, "3", "(바)"),
+ (0x3214, "3", "(사)"),
+ (0x3215, "3", "(아)"),
+ (0x3216, "3", "(자)"),
+ (0x3217, "3", "(차)"),
+ (0x3218, "3", "(카)"),
+ (0x3219, "3", "(타)"),
+ (0x321A, "3", "(파)"),
+ (0x321B, "3", "(하)"),
+ (0x321C, "3", "(주)"),
+ (0x321D, "3", "(오전)"),
+ (0x321E, "3", "(오후)"),
+ (0x321F, "X"),
+ (0x3220, "3", "(一)"),
+ (0x3221, "3", "(二)"),
+ (0x3222, "3", "(三)"),
+ (0x3223, "3", "(四)"),
+ (0x3224, "3", "(五)"),
+ (0x3225, "3", "(六)"),
+ (0x3226, "3", "(七)"),
+ (0x3227, "3", "(八)"),
+ (0x3228, "3", "(九)"),
+ (0x3229, "3", "(十)"),
+ (0x322A, "3", "(月)"),
+ (0x322B, "3", "(火)"),
+ (0x322C, "3", "(水)"),
+ (0x322D, "3", "(木)"),
+ (0x322E, "3", "(金)"),
+ (0x322F, "3", "(土)"),
+ (0x3230, "3", "(日)"),
+ (0x3231, "3", "(株)"),
+ (0x3232, "3", "(有)"),
+ (0x3233, "3", "(社)"),
+ (0x3234, "3", "(名)"),
+ (0x3235, "3", "(特)"),
+ (0x3236, "3", "(財)"),
+ (0x3237, "3", "(祝)"),
+ (0x3238, "3", "(労)"),
+ (0x3239, "3", "(代)"),
+ (0x323A, "3", "(呼)"),
+ (0x323B, "3", "(学)"),
+ (0x323C, "3", "(監)"),
+ (0x323D, "3", "(企)"),
+ (0x323E, "3", "(資)"),
+ (0x323F, "3", "(協)"),
+ (0x3240, "3", "(祭)"),
+ (0x3241, "3", "(休)"),
+ (0x3242, "3", "(自)"),
+ (0x3243, "3", "(至)"),
+ (0x3244, "M", "問"),
+ (0x3245, "M", "幼"),
+ (0x3246, "M", "文"),
+ (0x3247, "M", "箏"),
+ (0x3248, "V"),
+ (0x3250, "M", "pte"),
+ (0x3251, "M", "21"),
+ ]
+
+
+def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3252, "M", "22"),
+ (0x3253, "M", "23"),
+ (0x3254, "M", "24"),
+ (0x3255, "M", "25"),
+ (0x3256, "M", "26"),
+ (0x3257, "M", "27"),
+ (0x3258, "M", "28"),
+ (0x3259, "M", "29"),
+ (0x325A, "M", "30"),
+ (0x325B, "M", "31"),
+ (0x325C, "M", "32"),
+ (0x325D, "M", "33"),
+ (0x325E, "M", "34"),
+ (0x325F, "M", "35"),
+ (0x3260, "M", "ᄀ"),
+ (0x3261, "M", "ᄂ"),
+ (0x3262, "M", "ᄃ"),
+ (0x3263, "M", "ᄅ"),
+ (0x3264, "M", "ᄆ"),
+ (0x3265, "M", "ᄇ"),
+ (0x3266, "M", "ᄉ"),
+ (0x3267, "M", "ᄋ"),
+ (0x3268, "M", "ᄌ"),
+ (0x3269, "M", "ᄎ"),
+ (0x326A, "M", "ᄏ"),
+ (0x326B, "M", "ᄐ"),
+ (0x326C, "M", "ᄑ"),
+ (0x326D, "M", "ᄒ"),
+ (0x326E, "M", "가"),
+ (0x326F, "M", "나"),
+ (0x3270, "M", "다"),
+ (0x3271, "M", "라"),
+ (0x3272, "M", "마"),
+ (0x3273, "M", "바"),
+ (0x3274, "M", "사"),
+ (0x3275, "M", "아"),
+ (0x3276, "M", "자"),
+ (0x3277, "M", "차"),
+ (0x3278, "M", "카"),
+ (0x3279, "M", "타"),
+ (0x327A, "M", "파"),
+ (0x327B, "M", "하"),
+ (0x327C, "M", "참고"),
+ (0x327D, "M", "주의"),
+ (0x327E, "M", "우"),
+ (0x327F, "V"),
+ (0x3280, "M", "一"),
+ (0x3281, "M", "二"),
+ (0x3282, "M", "三"),
+ (0x3283, "M", "四"),
+ (0x3284, "M", "五"),
+ (0x3285, "M", "六"),
+ (0x3286, "M", "七"),
+ (0x3287, "M", "八"),
+ (0x3288, "M", "九"),
+ (0x3289, "M", "十"),
+ (0x328A, "M", "月"),
+ (0x328B, "M", "火"),
+ (0x328C, "M", "水"),
+ (0x328D, "M", "木"),
+ (0x328E, "M", "金"),
+ (0x328F, "M", "土"),
+ (0x3290, "M", "日"),
+ (0x3291, "M", "株"),
+ (0x3292, "M", "有"),
+ (0x3293, "M", "社"),
+ (0x3294, "M", "名"),
+ (0x3295, "M", "特"),
+ (0x3296, "M", "財"),
+ (0x3297, "M", "祝"),
+ (0x3298, "M", "労"),
+ (0x3299, "M", "秘"),
+ (0x329A, "M", "男"),
+ (0x329B, "M", "女"),
+ (0x329C, "M", "適"),
+ (0x329D, "M", "優"),
+ (0x329E, "M", "印"),
+ (0x329F, "M", "注"),
+ (0x32A0, "M", "項"),
+ (0x32A1, "M", "休"),
+ (0x32A2, "M", "写"),
+ (0x32A3, "M", "正"),
+ (0x32A4, "M", "上"),
+ (0x32A5, "M", "中"),
+ (0x32A6, "M", "下"),
+ (0x32A7, "M", "左"),
+ (0x32A8, "M", "右"),
+ (0x32A9, "M", "医"),
+ (0x32AA, "M", "宗"),
+ (0x32AB, "M", "学"),
+ (0x32AC, "M", "監"),
+ (0x32AD, "M", "企"),
+ (0x32AE, "M", "資"),
+ (0x32AF, "M", "協"),
+ (0x32B0, "M", "夜"),
+ (0x32B1, "M", "36"),
+ (0x32B2, "M", "37"),
+ (0x32B3, "M", "38"),
+ (0x32B4, "M", "39"),
+ (0x32B5, "M", "40"),
+ ]
+
+
+def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x32B6, "M", "41"),
+ (0x32B7, "M", "42"),
+ (0x32B8, "M", "43"),
+ (0x32B9, "M", "44"),
+ (0x32BA, "M", "45"),
+ (0x32BB, "M", "46"),
+ (0x32BC, "M", "47"),
+ (0x32BD, "M", "48"),
+ (0x32BE, "M", "49"),
+ (0x32BF, "M", "50"),
+ (0x32C0, "M", "1月"),
+ (0x32C1, "M", "2月"),
+ (0x32C2, "M", "3月"),
+ (0x32C3, "M", "4月"),
+ (0x32C4, "M", "5月"),
+ (0x32C5, "M", "6月"),
+ (0x32C6, "M", "7月"),
+ (0x32C7, "M", "8月"),
+ (0x32C8, "M", "9月"),
+ (0x32C9, "M", "10月"),
+ (0x32CA, "M", "11月"),
+ (0x32CB, "M", "12月"),
+ (0x32CC, "M", "hg"),
+ (0x32CD, "M", "erg"),
+ (0x32CE, "M", "ev"),
+ (0x32CF, "M", "ltd"),
+ (0x32D0, "M", "ア"),
+ (0x32D1, "M", "イ"),
+ (0x32D2, "M", "ウ"),
+ (0x32D3, "M", "エ"),
+ (0x32D4, "M", "オ"),
+ (0x32D5, "M", "カ"),
+ (0x32D6, "M", "キ"),
+ (0x32D7, "M", "ク"),
+ (0x32D8, "M", "ケ"),
+ (0x32D9, "M", "コ"),
+ (0x32DA, "M", "サ"),
+ (0x32DB, "M", "シ"),
+ (0x32DC, "M", "ス"),
+ (0x32DD, "M", "セ"),
+ (0x32DE, "M", "ソ"),
+ (0x32DF, "M", "タ"),
+ (0x32E0, "M", "チ"),
+ (0x32E1, "M", "ツ"),
+ (0x32E2, "M", "テ"),
+ (0x32E3, "M", "ト"),
+ (0x32E4, "M", "ナ"),
+ (0x32E5, "M", "ニ"),
+ (0x32E6, "M", "ヌ"),
+ (0x32E7, "M", "ネ"),
+ (0x32E8, "M", "ノ"),
+ (0x32E9, "M", "ハ"),
+ (0x32EA, "M", "ヒ"),
+ (0x32EB, "M", "フ"),
+ (0x32EC, "M", "ヘ"),
+ (0x32ED, "M", "ホ"),
+ (0x32EE, "M", "マ"),
+ (0x32EF, "M", "ミ"),
+ (0x32F0, "M", "ム"),
+ (0x32F1, "M", "メ"),
+ (0x32F2, "M", "モ"),
+ (0x32F3, "M", "ヤ"),
+ (0x32F4, "M", "ユ"),
+ (0x32F5, "M", "ヨ"),
+ (0x32F6, "M", "ラ"),
+ (0x32F7, "M", "リ"),
+ (0x32F8, "M", "ル"),
+ (0x32F9, "M", "レ"),
+ (0x32FA, "M", "ロ"),
+ (0x32FB, "M", "ワ"),
+ (0x32FC, "M", "ヰ"),
+ (0x32FD, "M", "ヱ"),
+ (0x32FE, "M", "ヲ"),
+ (0x32FF, "M", "令和"),
+ (0x3300, "M", "アパート"),
+ (0x3301, "M", "アルファ"),
+ (0x3302, "M", "アンペア"),
+ (0x3303, "M", "アール"),
+ (0x3304, "M", "イニング"),
+ (0x3305, "M", "インチ"),
+ (0x3306, "M", "ウォン"),
+ (0x3307, "M", "エスクード"),
+ (0x3308, "M", "エーカー"),
+ (0x3309, "M", "オンス"),
+ (0x330A, "M", "オーム"),
+ (0x330B, "M", "カイリ"),
+ (0x330C, "M", "カラット"),
+ (0x330D, "M", "カロリー"),
+ (0x330E, "M", "ガロン"),
+ (0x330F, "M", "ガンマ"),
+ (0x3310, "M", "ギガ"),
+ (0x3311, "M", "ギニー"),
+ (0x3312, "M", "キュリー"),
+ (0x3313, "M", "ギルダー"),
+ (0x3314, "M", "キロ"),
+ (0x3315, "M", "キログラム"),
+ (0x3316, "M", "キロメートル"),
+ (0x3317, "M", "キロワット"),
+ (0x3318, "M", "グラム"),
+ (0x3319, "M", "グラムトン"),
+ ]
+
+
+def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x331A, "M", "クルゼイロ"),
+ (0x331B, "M", "クローネ"),
+ (0x331C, "M", "ケース"),
+ (0x331D, "M", "コルナ"),
+ (0x331E, "M", "コーポ"),
+ (0x331F, "M", "サイクル"),
+ (0x3320, "M", "サンチーム"),
+ (0x3321, "M", "シリング"),
+ (0x3322, "M", "センチ"),
+ (0x3323, "M", "セント"),
+ (0x3324, "M", "ダース"),
+ (0x3325, "M", "デシ"),
+ (0x3326, "M", "ドル"),
+ (0x3327, "M", "トン"),
+ (0x3328, "M", "ナノ"),
+ (0x3329, "M", "ノット"),
+ (0x332A, "M", "ハイツ"),
+ (0x332B, "M", "パーセント"),
+ (0x332C, "M", "パーツ"),
+ (0x332D, "M", "バーレル"),
+ (0x332E, "M", "ピアストル"),
+ (0x332F, "M", "ピクル"),
+ (0x3330, "M", "ピコ"),
+ (0x3331, "M", "ビル"),
+ (0x3332, "M", "ファラッド"),
+ (0x3333, "M", "フィート"),
+ (0x3334, "M", "ブッシェル"),
+ (0x3335, "M", "フラン"),
+ (0x3336, "M", "ヘクタール"),
+ (0x3337, "M", "ペソ"),
+ (0x3338, "M", "ペニヒ"),
+ (0x3339, "M", "ヘルツ"),
+ (0x333A, "M", "ペンス"),
+ (0x333B, "M", "ページ"),
+ (0x333C, "M", "ベータ"),
+ (0x333D, "M", "ポイント"),
+ (0x333E, "M", "ボルト"),
+ (0x333F, "M", "ホン"),
+ (0x3340, "M", "ポンド"),
+ (0x3341, "M", "ホール"),
+ (0x3342, "M", "ホーン"),
+ (0x3343, "M", "マイクロ"),
+ (0x3344, "M", "マイル"),
+ (0x3345, "M", "マッハ"),
+ (0x3346, "M", "マルク"),
+ (0x3347, "M", "マンション"),
+ (0x3348, "M", "ミクロン"),
+ (0x3349, "M", "ミリ"),
+ (0x334A, "M", "ミリバール"),
+ (0x334B, "M", "メガ"),
+ (0x334C, "M", "メガトン"),
+ (0x334D, "M", "メートル"),
+ (0x334E, "M", "ヤード"),
+ (0x334F, "M", "ヤール"),
+ (0x3350, "M", "ユアン"),
+ (0x3351, "M", "リットル"),
+ (0x3352, "M", "リラ"),
+ (0x3353, "M", "ルピー"),
+ (0x3354, "M", "ルーブル"),
+ (0x3355, "M", "レム"),
+ (0x3356, "M", "レントゲン"),
+ (0x3357, "M", "ワット"),
+ (0x3358, "M", "0点"),
+ (0x3359, "M", "1点"),
+ (0x335A, "M", "2点"),
+ (0x335B, "M", "3点"),
+ (0x335C, "M", "4点"),
+ (0x335D, "M", "5点"),
+ (0x335E, "M", "6点"),
+ (0x335F, "M", "7点"),
+ (0x3360, "M", "8点"),
+ (0x3361, "M", "9点"),
+ (0x3362, "M", "10点"),
+ (0x3363, "M", "11点"),
+ (0x3364, "M", "12点"),
+ (0x3365, "M", "13点"),
+ (0x3366, "M", "14点"),
+ (0x3367, "M", "15点"),
+ (0x3368, "M", "16点"),
+ (0x3369, "M", "17点"),
+ (0x336A, "M", "18点"),
+ (0x336B, "M", "19点"),
+ (0x336C, "M", "20点"),
+ (0x336D, "M", "21点"),
+ (0x336E, "M", "22点"),
+ (0x336F, "M", "23点"),
+ (0x3370, "M", "24点"),
+ (0x3371, "M", "hpa"),
+ (0x3372, "M", "da"),
+ (0x3373, "M", "au"),
+ (0x3374, "M", "bar"),
+ (0x3375, "M", "ov"),
+ (0x3376, "M", "pc"),
+ (0x3377, "M", "dm"),
+ (0x3378, "M", "dm2"),
+ (0x3379, "M", "dm3"),
+ (0x337A, "M", "iu"),
+ (0x337B, "M", "平成"),
+ (0x337C, "M", "昭和"),
+ (0x337D, "M", "大正"),
+ ]
+
+
+def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x337E, "M", "明治"),
+ (0x337F, "M", "株式会社"),
+ (0x3380, "M", "pa"),
+ (0x3381, "M", "na"),
+ (0x3382, "M", "μa"),
+ (0x3383, "M", "ma"),
+ (0x3384, "M", "ka"),
+ (0x3385, "M", "kb"),
+ (0x3386, "M", "mb"),
+ (0x3387, "M", "gb"),
+ (0x3388, "M", "cal"),
+ (0x3389, "M", "kcal"),
+ (0x338A, "M", "pf"),
+ (0x338B, "M", "nf"),
+ (0x338C, "M", "μf"),
+ (0x338D, "M", "μg"),
+ (0x338E, "M", "mg"),
+ (0x338F, "M", "kg"),
+ (0x3390, "M", "hz"),
+ (0x3391, "M", "khz"),
+ (0x3392, "M", "mhz"),
+ (0x3393, "M", "ghz"),
+ (0x3394, "M", "thz"),
+ (0x3395, "M", "μl"),
+ (0x3396, "M", "ml"),
+ (0x3397, "M", "dl"),
+ (0x3398, "M", "kl"),
+ (0x3399, "M", "fm"),
+ (0x339A, "M", "nm"),
+ (0x339B, "M", "μm"),
+ (0x339C, "M", "mm"),
+ (0x339D, "M", "cm"),
+ (0x339E, "M", "km"),
+ (0x339F, "M", "mm2"),
+ (0x33A0, "M", "cm2"),
+ (0x33A1, "M", "m2"),
+ (0x33A2, "M", "km2"),
+ (0x33A3, "M", "mm3"),
+ (0x33A4, "M", "cm3"),
+ (0x33A5, "M", "m3"),
+ (0x33A6, "M", "km3"),
+ (0x33A7, "M", "m∕s"),
+ (0x33A8, "M", "m∕s2"),
+ (0x33A9, "M", "pa"),
+ (0x33AA, "M", "kpa"),
+ (0x33AB, "M", "mpa"),
+ (0x33AC, "M", "gpa"),
+ (0x33AD, "M", "rad"),
+ (0x33AE, "M", "rad∕s"),
+ (0x33AF, "M", "rad∕s2"),
+ (0x33B0, "M", "ps"),
+ (0x33B1, "M", "ns"),
+ (0x33B2, "M", "μs"),
+ (0x33B3, "M", "ms"),
+ (0x33B4, "M", "pv"),
+ (0x33B5, "M", "nv"),
+ (0x33B6, "M", "μv"),
+ (0x33B7, "M", "mv"),
+ (0x33B8, "M", "kv"),
+ (0x33B9, "M", "mv"),
+ (0x33BA, "M", "pw"),
+ (0x33BB, "M", "nw"),
+ (0x33BC, "M", "μw"),
+ (0x33BD, "M", "mw"),
+ (0x33BE, "M", "kw"),
+ (0x33BF, "M", "mw"),
+ (0x33C0, "M", "kω"),
+ (0x33C1, "M", "mω"),
+ (0x33C2, "X"),
+ (0x33C3, "M", "bq"),
+ (0x33C4, "M", "cc"),
+ (0x33C5, "M", "cd"),
+ (0x33C6, "M", "c∕kg"),
+ (0x33C7, "X"),
+ (0x33C8, "M", "db"),
+ (0x33C9, "M", "gy"),
+ (0x33CA, "M", "ha"),
+ (0x33CB, "M", "hp"),
+ (0x33CC, "M", "in"),
+ (0x33CD, "M", "kk"),
+ (0x33CE, "M", "km"),
+ (0x33CF, "M", "kt"),
+ (0x33D0, "M", "lm"),
+ (0x33D1, "M", "ln"),
+ (0x33D2, "M", "log"),
+ (0x33D3, "M", "lx"),
+ (0x33D4, "M", "mb"),
+ (0x33D5, "M", "mil"),
+ (0x33D6, "M", "mol"),
+ (0x33D7, "M", "ph"),
+ (0x33D8, "X"),
+ (0x33D9, "M", "ppm"),
+ (0x33DA, "M", "pr"),
+ (0x33DB, "M", "sr"),
+ (0x33DC, "M", "sv"),
+ (0x33DD, "M", "wb"),
+ (0x33DE, "M", "v∕m"),
+ (0x33DF, "M", "a∕m"),
+ (0x33E0, "M", "1日"),
+ (0x33E1, "M", "2日"),
+ ]
+
+
+def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x33E2, "M", "3日"),
+ (0x33E3, "M", "4日"),
+ (0x33E4, "M", "5日"),
+ (0x33E5, "M", "6日"),
+ (0x33E6, "M", "7日"),
+ (0x33E7, "M", "8日"),
+ (0x33E8, "M", "9日"),
+ (0x33E9, "M", "10日"),
+ (0x33EA, "M", "11日"),
+ (0x33EB, "M", "12日"),
+ (0x33EC, "M", "13日"),
+ (0x33ED, "M", "14日"),
+ (0x33EE, "M", "15日"),
+ (0x33EF, "M", "16日"),
+ (0x33F0, "M", "17日"),
+ (0x33F1, "M", "18日"),
+ (0x33F2, "M", "19日"),
+ (0x33F3, "M", "20日"),
+ (0x33F4, "M", "21日"),
+ (0x33F5, "M", "22日"),
+ (0x33F6, "M", "23日"),
+ (0x33F7, "M", "24日"),
+ (0x33F8, "M", "25日"),
+ (0x33F9, "M", "26日"),
+ (0x33FA, "M", "27日"),
+ (0x33FB, "M", "28日"),
+ (0x33FC, "M", "29日"),
+ (0x33FD, "M", "30日"),
+ (0x33FE, "M", "31日"),
+ (0x33FF, "M", "gal"),
+ (0x3400, "V"),
+ (0xA48D, "X"),
+ (0xA490, "V"),
+ (0xA4C7, "X"),
+ (0xA4D0, "V"),
+ (0xA62C, "X"),
+ (0xA640, "M", "ꙁ"),
+ (0xA641, "V"),
+ (0xA642, "M", "ꙃ"),
+ (0xA643, "V"),
+ (0xA644, "M", "ꙅ"),
+ (0xA645, "V"),
+ (0xA646, "M", "ꙇ"),
+ (0xA647, "V"),
+ (0xA648, "M", "ꙉ"),
+ (0xA649, "V"),
+ (0xA64A, "M", "ꙋ"),
+ (0xA64B, "V"),
+ (0xA64C, "M", "ꙍ"),
+ (0xA64D, "V"),
+ (0xA64E, "M", "ꙏ"),
+ (0xA64F, "V"),
+ (0xA650, "M", "ꙑ"),
+ (0xA651, "V"),
+ (0xA652, "M", "ꙓ"),
+ (0xA653, "V"),
+ (0xA654, "M", "ꙕ"),
+ (0xA655, "V"),
+ (0xA656, "M", "ꙗ"),
+ (0xA657, "V"),
+ (0xA658, "M", "ꙙ"),
+ (0xA659, "V"),
+ (0xA65A, "M", "ꙛ"),
+ (0xA65B, "V"),
+ (0xA65C, "M", "ꙝ"),
+ (0xA65D, "V"),
+ (0xA65E, "M", "ꙟ"),
+ (0xA65F, "V"),
+ (0xA660, "M", "ꙡ"),
+ (0xA661, "V"),
+ (0xA662, "M", "ꙣ"),
+ (0xA663, "V"),
+ (0xA664, "M", "ꙥ"),
+ (0xA665, "V"),
+ (0xA666, "M", "ꙧ"),
+ (0xA667, "V"),
+ (0xA668, "M", "ꙩ"),
+ (0xA669, "V"),
+ (0xA66A, "M", "ꙫ"),
+ (0xA66B, "V"),
+ (0xA66C, "M", "ꙭ"),
+ (0xA66D, "V"),
+ (0xA680, "M", "ꚁ"),
+ (0xA681, "V"),
+ (0xA682, "M", "ꚃ"),
+ (0xA683, "V"),
+ (0xA684, "M", "ꚅ"),
+ (0xA685, "V"),
+ (0xA686, "M", "ꚇ"),
+ (0xA687, "V"),
+ (0xA688, "M", "ꚉ"),
+ (0xA689, "V"),
+ (0xA68A, "M", "ꚋ"),
+ (0xA68B, "V"),
+ (0xA68C, "M", "ꚍ"),
+ (0xA68D, "V"),
+ (0xA68E, "M", "ꚏ"),
+ (0xA68F, "V"),
+ (0xA690, "M", "ꚑ"),
+ (0xA691, "V"),
+ ]
+
+
+def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA692, "M", "ꚓ"),
+ (0xA693, "V"),
+ (0xA694, "M", "ꚕ"),
+ (0xA695, "V"),
+ (0xA696, "M", "ꚗ"),
+ (0xA697, "V"),
+ (0xA698, "M", "ꚙ"),
+ (0xA699, "V"),
+ (0xA69A, "M", "ꚛ"),
+ (0xA69B, "V"),
+ (0xA69C, "M", "ъ"),
+ (0xA69D, "M", "ь"),
+ (0xA69E, "V"),
+ (0xA6F8, "X"),
+ (0xA700, "V"),
+ (0xA722, "M", "ꜣ"),
+ (0xA723, "V"),
+ (0xA724, "M", "ꜥ"),
+ (0xA725, "V"),
+ (0xA726, "M", "ꜧ"),
+ (0xA727, "V"),
+ (0xA728, "M", "ꜩ"),
+ (0xA729, "V"),
+ (0xA72A, "M", "ꜫ"),
+ (0xA72B, "V"),
+ (0xA72C, "M", "ꜭ"),
+ (0xA72D, "V"),
+ (0xA72E, "M", "ꜯ"),
+ (0xA72F, "V"),
+ (0xA732, "M", "ꜳ"),
+ (0xA733, "V"),
+ (0xA734, "M", "ꜵ"),
+ (0xA735, "V"),
+ (0xA736, "M", "ꜷ"),
+ (0xA737, "V"),
+ (0xA738, "M", "ꜹ"),
+ (0xA739, "V"),
+ (0xA73A, "M", "ꜻ"),
+ (0xA73B, "V"),
+ (0xA73C, "M", "ꜽ"),
+ (0xA73D, "V"),
+ (0xA73E, "M", "ꜿ"),
+ (0xA73F, "V"),
+ (0xA740, "M", "ꝁ"),
+ (0xA741, "V"),
+ (0xA742, "M", "ꝃ"),
+ (0xA743, "V"),
+ (0xA744, "M", "ꝅ"),
+ (0xA745, "V"),
+ (0xA746, "M", "ꝇ"),
+ (0xA747, "V"),
+ (0xA748, "M", "ꝉ"),
+ (0xA749, "V"),
+ (0xA74A, "M", "ꝋ"),
+ (0xA74B, "V"),
+ (0xA74C, "M", "ꝍ"),
+ (0xA74D, "V"),
+ (0xA74E, "M", "ꝏ"),
+ (0xA74F, "V"),
+ (0xA750, "M", "ꝑ"),
+ (0xA751, "V"),
+ (0xA752, "M", "ꝓ"),
+ (0xA753, "V"),
+ (0xA754, "M", "ꝕ"),
+ (0xA755, "V"),
+ (0xA756, "M", "ꝗ"),
+ (0xA757, "V"),
+ (0xA758, "M", "ꝙ"),
+ (0xA759, "V"),
+ (0xA75A, "M", "ꝛ"),
+ (0xA75B, "V"),
+ (0xA75C, "M", "ꝝ"),
+ (0xA75D, "V"),
+ (0xA75E, "M", "ꝟ"),
+ (0xA75F, "V"),
+ (0xA760, "M", "ꝡ"),
+ (0xA761, "V"),
+ (0xA762, "M", "ꝣ"),
+ (0xA763, "V"),
+ (0xA764, "M", "ꝥ"),
+ (0xA765, "V"),
+ (0xA766, "M", "ꝧ"),
+ (0xA767, "V"),
+ (0xA768, "M", "ꝩ"),
+ (0xA769, "V"),
+ (0xA76A, "M", "ꝫ"),
+ (0xA76B, "V"),
+ (0xA76C, "M", "ꝭ"),
+ (0xA76D, "V"),
+ (0xA76E, "M", "ꝯ"),
+ (0xA76F, "V"),
+ (0xA770, "M", "ꝯ"),
+ (0xA771, "V"),
+ (0xA779, "M", "ꝺ"),
+ (0xA77A, "V"),
+ (0xA77B, "M", "ꝼ"),
+ (0xA77C, "V"),
+ (0xA77D, "M", "ᵹ"),
+ (0xA77E, "M", "ꝿ"),
+ (0xA77F, "V"),
+ ]
+
+
+def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA780, "M", "ꞁ"),
+ (0xA781, "V"),
+ (0xA782, "M", "ꞃ"),
+ (0xA783, "V"),
+ (0xA784, "M", "ꞅ"),
+ (0xA785, "V"),
+ (0xA786, "M", "ꞇ"),
+ (0xA787, "V"),
+ (0xA78B, "M", "ꞌ"),
+ (0xA78C, "V"),
+ (0xA78D, "M", "ɥ"),
+ (0xA78E, "V"),
+ (0xA790, "M", "ꞑ"),
+ (0xA791, "V"),
+ (0xA792, "M", "ꞓ"),
+ (0xA793, "V"),
+ (0xA796, "M", "ꞗ"),
+ (0xA797, "V"),
+ (0xA798, "M", "ꞙ"),
+ (0xA799, "V"),
+ (0xA79A, "M", "ꞛ"),
+ (0xA79B, "V"),
+ (0xA79C, "M", "ꞝ"),
+ (0xA79D, "V"),
+ (0xA79E, "M", "ꞟ"),
+ (0xA79F, "V"),
+ (0xA7A0, "M", "ꞡ"),
+ (0xA7A1, "V"),
+ (0xA7A2, "M", "ꞣ"),
+ (0xA7A3, "V"),
+ (0xA7A4, "M", "ꞥ"),
+ (0xA7A5, "V"),
+ (0xA7A6, "M", "ꞧ"),
+ (0xA7A7, "V"),
+ (0xA7A8, "M", "ꞩ"),
+ (0xA7A9, "V"),
+ (0xA7AA, "M", "ɦ"),
+ (0xA7AB, "M", "ɜ"),
+ (0xA7AC, "M", "ɡ"),
+ (0xA7AD, "M", "ɬ"),
+ (0xA7AE, "M", "ɪ"),
+ (0xA7AF, "V"),
+ (0xA7B0, "M", "ʞ"),
+ (0xA7B1, "M", "ʇ"),
+ (0xA7B2, "M", "ʝ"),
+ (0xA7B3, "M", "ꭓ"),
+ (0xA7B4, "M", "ꞵ"),
+ (0xA7B5, "V"),
+ (0xA7B6, "M", "ꞷ"),
+ (0xA7B7, "V"),
+ (0xA7B8, "M", "ꞹ"),
+ (0xA7B9, "V"),
+ (0xA7BA, "M", "ꞻ"),
+ (0xA7BB, "V"),
+ (0xA7BC, "M", "ꞽ"),
+ (0xA7BD, "V"),
+ (0xA7BE, "M", "ꞿ"),
+ (0xA7BF, "V"),
+ (0xA7C0, "M", "ꟁ"),
+ (0xA7C1, "V"),
+ (0xA7C2, "M", "ꟃ"),
+ (0xA7C3, "V"),
+ (0xA7C4, "M", "ꞔ"),
+ (0xA7C5, "M", "ʂ"),
+ (0xA7C6, "M", "ᶎ"),
+ (0xA7C7, "M", "ꟈ"),
+ (0xA7C8, "V"),
+ (0xA7C9, "M", "ꟊ"),
+ (0xA7CA, "V"),
+ (0xA7CB, "X"),
+ (0xA7D0, "M", "ꟑ"),
+ (0xA7D1, "V"),
+ (0xA7D2, "X"),
+ (0xA7D3, "V"),
+ (0xA7D4, "X"),
+ (0xA7D5, "V"),
+ (0xA7D6, "M", "ꟗ"),
+ (0xA7D7, "V"),
+ (0xA7D8, "M", "ꟙ"),
+ (0xA7D9, "V"),
+ (0xA7DA, "X"),
+ (0xA7F2, "M", "c"),
+ (0xA7F3, "M", "f"),
+ (0xA7F4, "M", "q"),
+ (0xA7F5, "M", "ꟶ"),
+ (0xA7F6, "V"),
+ (0xA7F8, "M", "ħ"),
+ (0xA7F9, "M", "œ"),
+ (0xA7FA, "V"),
+ (0xA82D, "X"),
+ (0xA830, "V"),
+ (0xA83A, "X"),
+ (0xA840, "V"),
+ (0xA878, "X"),
+ (0xA880, "V"),
+ (0xA8C6, "X"),
+ (0xA8CE, "V"),
+ (0xA8DA, "X"),
+ (0xA8E0, "V"),
+ (0xA954, "X"),
+ ]
+
+
+def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA95F, "V"),
+ (0xA97D, "X"),
+ (0xA980, "V"),
+ (0xA9CE, "X"),
+ (0xA9CF, "V"),
+ (0xA9DA, "X"),
+ (0xA9DE, "V"),
+ (0xA9FF, "X"),
+ (0xAA00, "V"),
+ (0xAA37, "X"),
+ (0xAA40, "V"),
+ (0xAA4E, "X"),
+ (0xAA50, "V"),
+ (0xAA5A, "X"),
+ (0xAA5C, "V"),
+ (0xAAC3, "X"),
+ (0xAADB, "V"),
+ (0xAAF7, "X"),
+ (0xAB01, "V"),
+ (0xAB07, "X"),
+ (0xAB09, "V"),
+ (0xAB0F, "X"),
+ (0xAB11, "V"),
+ (0xAB17, "X"),
+ (0xAB20, "V"),
+ (0xAB27, "X"),
+ (0xAB28, "V"),
+ (0xAB2F, "X"),
+ (0xAB30, "V"),
+ (0xAB5C, "M", "ꜧ"),
+ (0xAB5D, "M", "ꬷ"),
+ (0xAB5E, "M", "ɫ"),
+ (0xAB5F, "M", "ꭒ"),
+ (0xAB60, "V"),
+ (0xAB69, "M", "ʍ"),
+ (0xAB6A, "V"),
+ (0xAB6C, "X"),
+ (0xAB70, "M", "Ꭰ"),
+ (0xAB71, "M", "Ꭱ"),
+ (0xAB72, "M", "Ꭲ"),
+ (0xAB73, "M", "Ꭳ"),
+ (0xAB74, "M", "Ꭴ"),
+ (0xAB75, "M", "Ꭵ"),
+ (0xAB76, "M", "Ꭶ"),
+ (0xAB77, "M", "Ꭷ"),
+ (0xAB78, "M", "Ꭸ"),
+ (0xAB79, "M", "Ꭹ"),
+ (0xAB7A, "M", "Ꭺ"),
+ (0xAB7B, "M", "Ꭻ"),
+ (0xAB7C, "M", "Ꭼ"),
+ (0xAB7D, "M", "Ꭽ"),
+ (0xAB7E, "M", "Ꭾ"),
+ (0xAB7F, "M", "Ꭿ"),
+ (0xAB80, "M", "Ꮀ"),
+ (0xAB81, "M", "Ꮁ"),
+ (0xAB82, "M", "Ꮂ"),
+ (0xAB83, "M", "Ꮃ"),
+ (0xAB84, "M", "Ꮄ"),
+ (0xAB85, "M", "Ꮅ"),
+ (0xAB86, "M", "Ꮆ"),
+ (0xAB87, "M", "Ꮇ"),
+ (0xAB88, "M", "Ꮈ"),
+ (0xAB89, "M", "Ꮉ"),
+ (0xAB8A, "M", "Ꮊ"),
+ (0xAB8B, "M", "Ꮋ"),
+ (0xAB8C, "M", "Ꮌ"),
+ (0xAB8D, "M", "Ꮍ"),
+ (0xAB8E, "M", "Ꮎ"),
+ (0xAB8F, "M", "Ꮏ"),
+ (0xAB90, "M", "Ꮐ"),
+ (0xAB91, "M", "Ꮑ"),
+ (0xAB92, "M", "Ꮒ"),
+ (0xAB93, "M", "Ꮓ"),
+ (0xAB94, "M", "Ꮔ"),
+ (0xAB95, "M", "Ꮕ"),
+ (0xAB96, "M", "Ꮖ"),
+ (0xAB97, "M", "Ꮗ"),
+ (0xAB98, "M", "Ꮘ"),
+ (0xAB99, "M", "Ꮙ"),
+ (0xAB9A, "M", "Ꮚ"),
+ (0xAB9B, "M", "Ꮛ"),
+ (0xAB9C, "M", "Ꮜ"),
+ (0xAB9D, "M", "Ꮝ"),
+ (0xAB9E, "M", "Ꮞ"),
+ (0xAB9F, "M", "Ꮟ"),
+ (0xABA0, "M", "Ꮠ"),
+ (0xABA1, "M", "Ꮡ"),
+ (0xABA2, "M", "Ꮢ"),
+ (0xABA3, "M", "Ꮣ"),
+ (0xABA4, "M", "Ꮤ"),
+ (0xABA5, "M", "Ꮥ"),
+ (0xABA6, "M", "Ꮦ"),
+ (0xABA7, "M", "Ꮧ"),
+ (0xABA8, "M", "Ꮨ"),
+ (0xABA9, "M", "Ꮩ"),
+ (0xABAA, "M", "Ꮪ"),
+ (0xABAB, "M", "Ꮫ"),
+ (0xABAC, "M", "Ꮬ"),
+ (0xABAD, "M", "Ꮭ"),
+ (0xABAE, "M", "Ꮮ"),
+ ]
+
+
+def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xABAF, "M", "Ꮯ"),
+ (0xABB0, "M", "Ꮰ"),
+ (0xABB1, "M", "Ꮱ"),
+ (0xABB2, "M", "Ꮲ"),
+ (0xABB3, "M", "Ꮳ"),
+ (0xABB4, "M", "Ꮴ"),
+ (0xABB5, "M", "Ꮵ"),
+ (0xABB6, "M", "Ꮶ"),
+ (0xABB7, "M", "Ꮷ"),
+ (0xABB8, "M", "Ꮸ"),
+ (0xABB9, "M", "Ꮹ"),
+ (0xABBA, "M", "Ꮺ"),
+ (0xABBB, "M", "Ꮻ"),
+ (0xABBC, "M", "Ꮼ"),
+ (0xABBD, "M", "Ꮽ"),
+ (0xABBE, "M", "Ꮾ"),
+ (0xABBF, "M", "Ꮿ"),
+ (0xABC0, "V"),
+ (0xABEE, "X"),
+ (0xABF0, "V"),
+ (0xABFA, "X"),
+ (0xAC00, "V"),
+ (0xD7A4, "X"),
+ (0xD7B0, "V"),
+ (0xD7C7, "X"),
+ (0xD7CB, "V"),
+ (0xD7FC, "X"),
+ (0xF900, "M", "豈"),
+ (0xF901, "M", "更"),
+ (0xF902, "M", "車"),
+ (0xF903, "M", "賈"),
+ (0xF904, "M", "滑"),
+ (0xF905, "M", "串"),
+ (0xF906, "M", "句"),
+ (0xF907, "M", "龜"),
+ (0xF909, "M", "契"),
+ (0xF90A, "M", "金"),
+ (0xF90B, "M", "喇"),
+ (0xF90C, "M", "奈"),
+ (0xF90D, "M", "懶"),
+ (0xF90E, "M", "癩"),
+ (0xF90F, "M", "羅"),
+ (0xF910, "M", "蘿"),
+ (0xF911, "M", "螺"),
+ (0xF912, "M", "裸"),
+ (0xF913, "M", "邏"),
+ (0xF914, "M", "樂"),
+ (0xF915, "M", "洛"),
+ (0xF916, "M", "烙"),
+ (0xF917, "M", "珞"),
+ (0xF918, "M", "落"),
+ (0xF919, "M", "酪"),
+ (0xF91A, "M", "駱"),
+ (0xF91B, "M", "亂"),
+ (0xF91C, "M", "卵"),
+ (0xF91D, "M", "欄"),
+ (0xF91E, "M", "爛"),
+ (0xF91F, "M", "蘭"),
+ (0xF920, "M", "鸞"),
+ (0xF921, "M", "嵐"),
+ (0xF922, "M", "濫"),
+ (0xF923, "M", "藍"),
+ (0xF924, "M", "襤"),
+ (0xF925, "M", "拉"),
+ (0xF926, "M", "臘"),
+ (0xF927, "M", "蠟"),
+ (0xF928, "M", "廊"),
+ (0xF929, "M", "朗"),
+ (0xF92A, "M", "浪"),
+ (0xF92B, "M", "狼"),
+ (0xF92C, "M", "郎"),
+ (0xF92D, "M", "來"),
+ (0xF92E, "M", "冷"),
+ (0xF92F, "M", "勞"),
+ (0xF930, "M", "擄"),
+ (0xF931, "M", "櫓"),
+ (0xF932, "M", "爐"),
+ (0xF933, "M", "盧"),
+ (0xF934, "M", "老"),
+ (0xF935, "M", "蘆"),
+ (0xF936, "M", "虜"),
+ (0xF937, "M", "路"),
+ (0xF938, "M", "露"),
+ (0xF939, "M", "魯"),
+ (0xF93A, "M", "鷺"),
+ (0xF93B, "M", "碌"),
+ (0xF93C, "M", "祿"),
+ (0xF93D, "M", "綠"),
+ (0xF93E, "M", "菉"),
+ (0xF93F, "M", "錄"),
+ (0xF940, "M", "鹿"),
+ (0xF941, "M", "論"),
+ (0xF942, "M", "壟"),
+ (0xF943, "M", "弄"),
+ (0xF944, "M", "籠"),
+ (0xF945, "M", "聾"),
+ (0xF946, "M", "牢"),
+ (0xF947, "M", "磊"),
+ (0xF948, "M", "賂"),
+ (0xF949, "M", "雷"),
+ ]
+
+
+def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF94A, "M", "壘"),
+ (0xF94B, "M", "屢"),
+ (0xF94C, "M", "樓"),
+ (0xF94D, "M", "淚"),
+ (0xF94E, "M", "漏"),
+ (0xF94F, "M", "累"),
+ (0xF950, "M", "縷"),
+ (0xF951, "M", "陋"),
+ (0xF952, "M", "勒"),
+ (0xF953, "M", "肋"),
+ (0xF954, "M", "凜"),
+ (0xF955, "M", "凌"),
+ (0xF956, "M", "稜"),
+ (0xF957, "M", "綾"),
+ (0xF958, "M", "菱"),
+ (0xF959, "M", "陵"),
+ (0xF95A, "M", "讀"),
+ (0xF95B, "M", "拏"),
+ (0xF95C, "M", "樂"),
+ (0xF95D, "M", "諾"),
+ (0xF95E, "M", "丹"),
+ (0xF95F, "M", "寧"),
+ (0xF960, "M", "怒"),
+ (0xF961, "M", "率"),
+ (0xF962, "M", "異"),
+ (0xF963, "M", "北"),
+ (0xF964, "M", "磻"),
+ (0xF965, "M", "便"),
+ (0xF966, "M", "復"),
+ (0xF967, "M", "不"),
+ (0xF968, "M", "泌"),
+ (0xF969, "M", "數"),
+ (0xF96A, "M", "索"),
+ (0xF96B, "M", "參"),
+ (0xF96C, "M", "塞"),
+ (0xF96D, "M", "省"),
+ (0xF96E, "M", "葉"),
+ (0xF96F, "M", "說"),
+ (0xF970, "M", "殺"),
+ (0xF971, "M", "辰"),
+ (0xF972, "M", "沈"),
+ (0xF973, "M", "拾"),
+ (0xF974, "M", "若"),
+ (0xF975, "M", "掠"),
+ (0xF976, "M", "略"),
+ (0xF977, "M", "亮"),
+ (0xF978, "M", "兩"),
+ (0xF979, "M", "凉"),
+ (0xF97A, "M", "梁"),
+ (0xF97B, "M", "糧"),
+ (0xF97C, "M", "良"),
+ (0xF97D, "M", "諒"),
+ (0xF97E, "M", "量"),
+ (0xF97F, "M", "勵"),
+ (0xF980, "M", "呂"),
+ (0xF981, "M", "女"),
+ (0xF982, "M", "廬"),
+ (0xF983, "M", "旅"),
+ (0xF984, "M", "濾"),
+ (0xF985, "M", "礪"),
+ (0xF986, "M", "閭"),
+ (0xF987, "M", "驪"),
+ (0xF988, "M", "麗"),
+ (0xF989, "M", "黎"),
+ (0xF98A, "M", "力"),
+ (0xF98B, "M", "曆"),
+ (0xF98C, "M", "歷"),
+ (0xF98D, "M", "轢"),
+ (0xF98E, "M", "年"),
+ (0xF98F, "M", "憐"),
+ (0xF990, "M", "戀"),
+ (0xF991, "M", "撚"),
+ (0xF992, "M", "漣"),
+ (0xF993, "M", "煉"),
+ (0xF994, "M", "璉"),
+ (0xF995, "M", "秊"),
+ (0xF996, "M", "練"),
+ (0xF997, "M", "聯"),
+ (0xF998, "M", "輦"),
+ (0xF999, "M", "蓮"),
+ (0xF99A, "M", "連"),
+ (0xF99B, "M", "鍊"),
+ (0xF99C, "M", "列"),
+ (0xF99D, "M", "劣"),
+ (0xF99E, "M", "咽"),
+ (0xF99F, "M", "烈"),
+ (0xF9A0, "M", "裂"),
+ (0xF9A1, "M", "說"),
+ (0xF9A2, "M", "廉"),
+ (0xF9A3, "M", "念"),
+ (0xF9A4, "M", "捻"),
+ (0xF9A5, "M", "殮"),
+ (0xF9A6, "M", "簾"),
+ (0xF9A7, "M", "獵"),
+ (0xF9A8, "M", "令"),
+ (0xF9A9, "M", "囹"),
+ (0xF9AA, "M", "寧"),
+ (0xF9AB, "M", "嶺"),
+ (0xF9AC, "M", "怜"),
+ (0xF9AD, "M", "玲"),
+ ]
+
+
+def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF9AE, "M", "瑩"),
+ (0xF9AF, "M", "羚"),
+ (0xF9B0, "M", "聆"),
+ (0xF9B1, "M", "鈴"),
+ (0xF9B2, "M", "零"),
+ (0xF9B3, "M", "靈"),
+ (0xF9B4, "M", "領"),
+ (0xF9B5, "M", "例"),
+ (0xF9B6, "M", "禮"),
+ (0xF9B7, "M", "醴"),
+ (0xF9B8, "M", "隸"),
+ (0xF9B9, "M", "惡"),
+ (0xF9BA, "M", "了"),
+ (0xF9BB, "M", "僚"),
+ (0xF9BC, "M", "寮"),
+ (0xF9BD, "M", "尿"),
+ (0xF9BE, "M", "料"),
+ (0xF9BF, "M", "樂"),
+ (0xF9C0, "M", "燎"),
+ (0xF9C1, "M", "療"),
+ (0xF9C2, "M", "蓼"),
+ (0xF9C3, "M", "遼"),
+ (0xF9C4, "M", "龍"),
+ (0xF9C5, "M", "暈"),
+ (0xF9C6, "M", "阮"),
+ (0xF9C7, "M", "劉"),
+ (0xF9C8, "M", "杻"),
+ (0xF9C9, "M", "柳"),
+ (0xF9CA, "M", "流"),
+ (0xF9CB, "M", "溜"),
+ (0xF9CC, "M", "琉"),
+ (0xF9CD, "M", "留"),
+ (0xF9CE, "M", "硫"),
+ (0xF9CF, "M", "紐"),
+ (0xF9D0, "M", "類"),
+ (0xF9D1, "M", "六"),
+ (0xF9D2, "M", "戮"),
+ (0xF9D3, "M", "陸"),
+ (0xF9D4, "M", "倫"),
+ (0xF9D5, "M", "崙"),
+ (0xF9D6, "M", "淪"),
+ (0xF9D7, "M", "輪"),
+ (0xF9D8, "M", "律"),
+ (0xF9D9, "M", "慄"),
+ (0xF9DA, "M", "栗"),
+ (0xF9DB, "M", "率"),
+ (0xF9DC, "M", "隆"),
+ (0xF9DD, "M", "利"),
+ (0xF9DE, "M", "吏"),
+ (0xF9DF, "M", "履"),
+ (0xF9E0, "M", "易"),
+ (0xF9E1, "M", "李"),
+ (0xF9E2, "M", "梨"),
+ (0xF9E3, "M", "泥"),
+ (0xF9E4, "M", "理"),
+ (0xF9E5, "M", "痢"),
+ (0xF9E6, "M", "罹"),
+ (0xF9E7, "M", "裏"),
+ (0xF9E8, "M", "裡"),
+ (0xF9E9, "M", "里"),
+ (0xF9EA, "M", "離"),
+ (0xF9EB, "M", "匿"),
+ (0xF9EC, "M", "溺"),
+ (0xF9ED, "M", "吝"),
+ (0xF9EE, "M", "燐"),
+ (0xF9EF, "M", "璘"),
+ (0xF9F0, "M", "藺"),
+ (0xF9F1, "M", "隣"),
+ (0xF9F2, "M", "鱗"),
+ (0xF9F3, "M", "麟"),
+ (0xF9F4, "M", "林"),
+ (0xF9F5, "M", "淋"),
+ (0xF9F6, "M", "臨"),
+ (0xF9F7, "M", "立"),
+ (0xF9F8, "M", "笠"),
+ (0xF9F9, "M", "粒"),
+ (0xF9FA, "M", "狀"),
+ (0xF9FB, "M", "炙"),
+ (0xF9FC, "M", "識"),
+ (0xF9FD, "M", "什"),
+ (0xF9FE, "M", "茶"),
+ (0xF9FF, "M", "刺"),
+ (0xFA00, "M", "切"),
+ (0xFA01, "M", "度"),
+ (0xFA02, "M", "拓"),
+ (0xFA03, "M", "糖"),
+ (0xFA04, "M", "宅"),
+ (0xFA05, "M", "洞"),
+ (0xFA06, "M", "暴"),
+ (0xFA07, "M", "輻"),
+ (0xFA08, "M", "行"),
+ (0xFA09, "M", "降"),
+ (0xFA0A, "M", "見"),
+ (0xFA0B, "M", "廓"),
+ (0xFA0C, "M", "兀"),
+ (0xFA0D, "M", "嗀"),
+ (0xFA0E, "V"),
+ (0xFA10, "M", "塚"),
+ (0xFA11, "V"),
+ (0xFA12, "M", "晴"),
+ ]
+
+
+def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFA13, "V"),
+ (0xFA15, "M", "凞"),
+ (0xFA16, "M", "猪"),
+ (0xFA17, "M", "益"),
+ (0xFA18, "M", "礼"),
+ (0xFA19, "M", "神"),
+ (0xFA1A, "M", "祥"),
+ (0xFA1B, "M", "福"),
+ (0xFA1C, "M", "靖"),
+ (0xFA1D, "M", "精"),
+ (0xFA1E, "M", "羽"),
+ (0xFA1F, "V"),
+ (0xFA20, "M", "蘒"),
+ (0xFA21, "V"),
+ (0xFA22, "M", "諸"),
+ (0xFA23, "V"),
+ (0xFA25, "M", "逸"),
+ (0xFA26, "M", "都"),
+ (0xFA27, "V"),
+ (0xFA2A, "M", "飯"),
+ (0xFA2B, "M", "飼"),
+ (0xFA2C, "M", "館"),
+ (0xFA2D, "M", "鶴"),
+ (0xFA2E, "M", "郞"),
+ (0xFA2F, "M", "隷"),
+ (0xFA30, "M", "侮"),
+ (0xFA31, "M", "僧"),
+ (0xFA32, "M", "免"),
+ (0xFA33, "M", "勉"),
+ (0xFA34, "M", "勤"),
+ (0xFA35, "M", "卑"),
+ (0xFA36, "M", "喝"),
+ (0xFA37, "M", "嘆"),
+ (0xFA38, "M", "器"),
+ (0xFA39, "M", "塀"),
+ (0xFA3A, "M", "墨"),
+ (0xFA3B, "M", "層"),
+ (0xFA3C, "M", "屮"),
+ (0xFA3D, "M", "悔"),
+ (0xFA3E, "M", "慨"),
+ (0xFA3F, "M", "憎"),
+ (0xFA40, "M", "懲"),
+ (0xFA41, "M", "敏"),
+ (0xFA42, "M", "既"),
+ (0xFA43, "M", "暑"),
+ (0xFA44, "M", "梅"),
+ (0xFA45, "M", "海"),
+ (0xFA46, "M", "渚"),
+ (0xFA47, "M", "漢"),
+ (0xFA48, "M", "煮"),
+ (0xFA49, "M", "爫"),
+ (0xFA4A, "M", "琢"),
+ (0xFA4B, "M", "碑"),
+ (0xFA4C, "M", "社"),
+ (0xFA4D, "M", "祉"),
+ (0xFA4E, "M", "祈"),
+ (0xFA4F, "M", "祐"),
+ (0xFA50, "M", "祖"),
+ (0xFA51, "M", "祝"),
+ (0xFA52, "M", "禍"),
+ (0xFA53, "M", "禎"),
+ (0xFA54, "M", "穀"),
+ (0xFA55, "M", "突"),
+ (0xFA56, "M", "節"),
+ (0xFA57, "M", "練"),
+ (0xFA58, "M", "縉"),
+ (0xFA59, "M", "繁"),
+ (0xFA5A, "M", "署"),
+ (0xFA5B, "M", "者"),
+ (0xFA5C, "M", "臭"),
+ (0xFA5D, "M", "艹"),
+ (0xFA5F, "M", "著"),
+ (0xFA60, "M", "褐"),
+ (0xFA61, "M", "視"),
+ (0xFA62, "M", "謁"),
+ (0xFA63, "M", "謹"),
+ (0xFA64, "M", "賓"),
+ (0xFA65, "M", "贈"),
+ (0xFA66, "M", "辶"),
+ (0xFA67, "M", "逸"),
+ (0xFA68, "M", "難"),
+ (0xFA69, "M", "響"),
+ (0xFA6A, "M", "頻"),
+ (0xFA6B, "M", "恵"),
+ (0xFA6C, "M", "𤋮"),
+ (0xFA6D, "M", "舘"),
+ (0xFA6E, "X"),
+ (0xFA70, "M", "並"),
+ (0xFA71, "M", "况"),
+ (0xFA72, "M", "全"),
+ (0xFA73, "M", "侀"),
+ (0xFA74, "M", "充"),
+ (0xFA75, "M", "冀"),
+ (0xFA76, "M", "勇"),
+ (0xFA77, "M", "勺"),
+ (0xFA78, "M", "喝"),
+ (0xFA79, "M", "啕"),
+ (0xFA7A, "M", "喙"),
+ (0xFA7B, "M", "嗢"),
+ (0xFA7C, "M", "塚"),
+ ]
+
+
+def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFA7D, "M", "墳"),
+ (0xFA7E, "M", "奄"),
+ (0xFA7F, "M", "奔"),
+ (0xFA80, "M", "婢"),
+ (0xFA81, "M", "嬨"),
+ (0xFA82, "M", "廒"),
+ (0xFA83, "M", "廙"),
+ (0xFA84, "M", "彩"),
+ (0xFA85, "M", "徭"),
+ (0xFA86, "M", "惘"),
+ (0xFA87, "M", "慎"),
+ (0xFA88, "M", "愈"),
+ (0xFA89, "M", "憎"),
+ (0xFA8A, "M", "慠"),
+ (0xFA8B, "M", "懲"),
+ (0xFA8C, "M", "戴"),
+ (0xFA8D, "M", "揄"),
+ (0xFA8E, "M", "搜"),
+ (0xFA8F, "M", "摒"),
+ (0xFA90, "M", "敖"),
+ (0xFA91, "M", "晴"),
+ (0xFA92, "M", "朗"),
+ (0xFA93, "M", "望"),
+ (0xFA94, "M", "杖"),
+ (0xFA95, "M", "歹"),
+ (0xFA96, "M", "殺"),
+ (0xFA97, "M", "流"),
+ (0xFA98, "M", "滛"),
+ (0xFA99, "M", "滋"),
+ (0xFA9A, "M", "漢"),
+ (0xFA9B, "M", "瀞"),
+ (0xFA9C, "M", "煮"),
+ (0xFA9D, "M", "瞧"),
+ (0xFA9E, "M", "爵"),
+ (0xFA9F, "M", "犯"),
+ (0xFAA0, "M", "猪"),
+ (0xFAA1, "M", "瑱"),
+ (0xFAA2, "M", "甆"),
+ (0xFAA3, "M", "画"),
+ (0xFAA4, "M", "瘝"),
+ (0xFAA5, "M", "瘟"),
+ (0xFAA6, "M", "益"),
+ (0xFAA7, "M", "盛"),
+ (0xFAA8, "M", "直"),
+ (0xFAA9, "M", "睊"),
+ (0xFAAA, "M", "着"),
+ (0xFAAB, "M", "磌"),
+ (0xFAAC, "M", "窱"),
+ (0xFAAD, "M", "節"),
+ (0xFAAE, "M", "类"),
+ (0xFAAF, "M", "絛"),
+ (0xFAB0, "M", "練"),
+ (0xFAB1, "M", "缾"),
+ (0xFAB2, "M", "者"),
+ (0xFAB3, "M", "荒"),
+ (0xFAB4, "M", "華"),
+ (0xFAB5, "M", "蝹"),
+ (0xFAB6, "M", "襁"),
+ (0xFAB7, "M", "覆"),
+ (0xFAB8, "M", "視"),
+ (0xFAB9, "M", "調"),
+ (0xFABA, "M", "諸"),
+ (0xFABB, "M", "請"),
+ (0xFABC, "M", "謁"),
+ (0xFABD, "M", "諾"),
+ (0xFABE, "M", "諭"),
+ (0xFABF, "M", "謹"),
+ (0xFAC0, "M", "變"),
+ (0xFAC1, "M", "贈"),
+ (0xFAC2, "M", "輸"),
+ (0xFAC3, "M", "遲"),
+ (0xFAC4, "M", "醙"),
+ (0xFAC5, "M", "鉶"),
+ (0xFAC6, "M", "陼"),
+ (0xFAC7, "M", "難"),
+ (0xFAC8, "M", "靖"),
+ (0xFAC9, "M", "韛"),
+ (0xFACA, "M", "響"),
+ (0xFACB, "M", "頋"),
+ (0xFACC, "M", "頻"),
+ (0xFACD, "M", "鬒"),
+ (0xFACE, "M", "龜"),
+ (0xFACF, "M", "𢡊"),
+ (0xFAD0, "M", "𢡄"),
+ (0xFAD1, "M", "𣏕"),
+ (0xFAD2, "M", "㮝"),
+ (0xFAD3, "M", "䀘"),
+ (0xFAD4, "M", "䀹"),
+ (0xFAD5, "M", "𥉉"),
+ (0xFAD6, "M", "𥳐"),
+ (0xFAD7, "M", "𧻓"),
+ (0xFAD8, "M", "齃"),
+ (0xFAD9, "M", "龎"),
+ (0xFADA, "X"),
+ (0xFB00, "M", "ff"),
+ (0xFB01, "M", "fi"),
+ (0xFB02, "M", "fl"),
+ (0xFB03, "M", "ffi"),
+ (0xFB04, "M", "ffl"),
+ (0xFB05, "M", "st"),
+ ]
+
+
+def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFB07, "X"),
+ (0xFB13, "M", "մն"),
+ (0xFB14, "M", "մե"),
+ (0xFB15, "M", "մի"),
+ (0xFB16, "M", "վն"),
+ (0xFB17, "M", "մխ"),
+ (0xFB18, "X"),
+ (0xFB1D, "M", "יִ"),
+ (0xFB1E, "V"),
+ (0xFB1F, "M", "ײַ"),
+ (0xFB20, "M", "ע"),
+ (0xFB21, "M", "א"),
+ (0xFB22, "M", "ד"),
+ (0xFB23, "M", "ה"),
+ (0xFB24, "M", "כ"),
+ (0xFB25, "M", "ל"),
+ (0xFB26, "M", "ם"),
+ (0xFB27, "M", "ר"),
+ (0xFB28, "M", "ת"),
+ (0xFB29, "3", "+"),
+ (0xFB2A, "M", "שׁ"),
+ (0xFB2B, "M", "שׂ"),
+ (0xFB2C, "M", "שּׁ"),
+ (0xFB2D, "M", "שּׂ"),
+ (0xFB2E, "M", "אַ"),
+ (0xFB2F, "M", "אָ"),
+ (0xFB30, "M", "אּ"),
+ (0xFB31, "M", "בּ"),
+ (0xFB32, "M", "גּ"),
+ (0xFB33, "M", "דּ"),
+ (0xFB34, "M", "הּ"),
+ (0xFB35, "M", "וּ"),
+ (0xFB36, "M", "זּ"),
+ (0xFB37, "X"),
+ (0xFB38, "M", "טּ"),
+ (0xFB39, "M", "יּ"),
+ (0xFB3A, "M", "ךּ"),
+ (0xFB3B, "M", "כּ"),
+ (0xFB3C, "M", "לּ"),
+ (0xFB3D, "X"),
+ (0xFB3E, "M", "מּ"),
+ (0xFB3F, "X"),
+ (0xFB40, "M", "נּ"),
+ (0xFB41, "M", "סּ"),
+ (0xFB42, "X"),
+ (0xFB43, "M", "ףּ"),
+ (0xFB44, "M", "פּ"),
+ (0xFB45, "X"),
+ (0xFB46, "M", "צּ"),
+ (0xFB47, "M", "קּ"),
+ (0xFB48, "M", "רּ"),
+ (0xFB49, "M", "שּ"),
+ (0xFB4A, "M", "תּ"),
+ (0xFB4B, "M", "וֹ"),
+ (0xFB4C, "M", "בֿ"),
+ (0xFB4D, "M", "כֿ"),
+ (0xFB4E, "M", "פֿ"),
+ (0xFB4F, "M", "אל"),
+ (0xFB50, "M", "ٱ"),
+ (0xFB52, "M", "ٻ"),
+ (0xFB56, "M", "پ"),
+ (0xFB5A, "M", "ڀ"),
+ (0xFB5E, "M", "ٺ"),
+ (0xFB62, "M", "ٿ"),
+ (0xFB66, "M", "ٹ"),
+ (0xFB6A, "M", "ڤ"),
+ (0xFB6E, "M", "ڦ"),
+ (0xFB72, "M", "ڄ"),
+ (0xFB76, "M", "ڃ"),
+ (0xFB7A, "M", "چ"),
+ (0xFB7E, "M", "ڇ"),
+ (0xFB82, "M", "ڍ"),
+ (0xFB84, "M", "ڌ"),
+ (0xFB86, "M", "ڎ"),
+ (0xFB88, "M", "ڈ"),
+ (0xFB8A, "M", "ژ"),
+ (0xFB8C, "M", "ڑ"),
+ (0xFB8E, "M", "ک"),
+ (0xFB92, "M", "گ"),
+ (0xFB96, "M", "ڳ"),
+ (0xFB9A, "M", "ڱ"),
+ (0xFB9E, "M", "ں"),
+ (0xFBA0, "M", "ڻ"),
+ (0xFBA4, "M", "ۀ"),
+ (0xFBA6, "M", "ہ"),
+ (0xFBAA, "M", "ھ"),
+ (0xFBAE, "M", "ے"),
+ (0xFBB0, "M", "ۓ"),
+ (0xFBB2, "V"),
+ (0xFBC3, "X"),
+ (0xFBD3, "M", "ڭ"),
+ (0xFBD7, "M", "ۇ"),
+ (0xFBD9, "M", "ۆ"),
+ (0xFBDB, "M", "ۈ"),
+ (0xFBDD, "M", "ۇٴ"),
+ (0xFBDE, "M", "ۋ"),
+ (0xFBE0, "M", "ۅ"),
+ (0xFBE2, "M", "ۉ"),
+ (0xFBE4, "M", "ې"),
+ (0xFBE8, "M", "ى"),
+ ]
+
+
+def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFBEA, "M", "ئا"),
+ (0xFBEC, "M", "ئە"),
+ (0xFBEE, "M", "ئو"),
+ (0xFBF0, "M", "ئۇ"),
+ (0xFBF2, "M", "ئۆ"),
+ (0xFBF4, "M", "ئۈ"),
+ (0xFBF6, "M", "ئې"),
+ (0xFBF9, "M", "ئى"),
+ (0xFBFC, "M", "ی"),
+ (0xFC00, "M", "ئج"),
+ (0xFC01, "M", "ئح"),
+ (0xFC02, "M", "ئم"),
+ (0xFC03, "M", "ئى"),
+ (0xFC04, "M", "ئي"),
+ (0xFC05, "M", "بج"),
+ (0xFC06, "M", "بح"),
+ (0xFC07, "M", "بخ"),
+ (0xFC08, "M", "بم"),
+ (0xFC09, "M", "بى"),
+ (0xFC0A, "M", "بي"),
+ (0xFC0B, "M", "تج"),
+ (0xFC0C, "M", "تح"),
+ (0xFC0D, "M", "تخ"),
+ (0xFC0E, "M", "تم"),
+ (0xFC0F, "M", "تى"),
+ (0xFC10, "M", "تي"),
+ (0xFC11, "M", "ثج"),
+ (0xFC12, "M", "ثم"),
+ (0xFC13, "M", "ثى"),
+ (0xFC14, "M", "ثي"),
+ (0xFC15, "M", "جح"),
+ (0xFC16, "M", "جم"),
+ (0xFC17, "M", "حج"),
+ (0xFC18, "M", "حم"),
+ (0xFC19, "M", "خج"),
+ (0xFC1A, "M", "خح"),
+ (0xFC1B, "M", "خم"),
+ (0xFC1C, "M", "سج"),
+ (0xFC1D, "M", "سح"),
+ (0xFC1E, "M", "سخ"),
+ (0xFC1F, "M", "سم"),
+ (0xFC20, "M", "صح"),
+ (0xFC21, "M", "صم"),
+ (0xFC22, "M", "ضج"),
+ (0xFC23, "M", "ضح"),
+ (0xFC24, "M", "ضخ"),
+ (0xFC25, "M", "ضم"),
+ (0xFC26, "M", "طح"),
+ (0xFC27, "M", "طم"),
+ (0xFC28, "M", "ظم"),
+ (0xFC29, "M", "عج"),
+ (0xFC2A, "M", "عم"),
+ (0xFC2B, "M", "غج"),
+ (0xFC2C, "M", "غم"),
+ (0xFC2D, "M", "فج"),
+ (0xFC2E, "M", "فح"),
+ (0xFC2F, "M", "فخ"),
+ (0xFC30, "M", "فم"),
+ (0xFC31, "M", "فى"),
+ (0xFC32, "M", "في"),
+ (0xFC33, "M", "قح"),
+ (0xFC34, "M", "قم"),
+ (0xFC35, "M", "قى"),
+ (0xFC36, "M", "قي"),
+ (0xFC37, "M", "كا"),
+ (0xFC38, "M", "كج"),
+ (0xFC39, "M", "كح"),
+ (0xFC3A, "M", "كخ"),
+ (0xFC3B, "M", "كل"),
+ (0xFC3C, "M", "كم"),
+ (0xFC3D, "M", "كى"),
+ (0xFC3E, "M", "كي"),
+ (0xFC3F, "M", "لج"),
+ (0xFC40, "M", "لح"),
+ (0xFC41, "M", "لخ"),
+ (0xFC42, "M", "لم"),
+ (0xFC43, "M", "لى"),
+ (0xFC44, "M", "لي"),
+ (0xFC45, "M", "مج"),
+ (0xFC46, "M", "مح"),
+ (0xFC47, "M", "مخ"),
+ (0xFC48, "M", "مم"),
+ (0xFC49, "M", "مى"),
+ (0xFC4A, "M", "مي"),
+ (0xFC4B, "M", "نج"),
+ (0xFC4C, "M", "نح"),
+ (0xFC4D, "M", "نخ"),
+ (0xFC4E, "M", "نم"),
+ (0xFC4F, "M", "نى"),
+ (0xFC50, "M", "ني"),
+ (0xFC51, "M", "هج"),
+ (0xFC52, "M", "هم"),
+ (0xFC53, "M", "هى"),
+ (0xFC54, "M", "هي"),
+ (0xFC55, "M", "يج"),
+ (0xFC56, "M", "يح"),
+ (0xFC57, "M", "يخ"),
+ (0xFC58, "M", "يم"),
+ (0xFC59, "M", "يى"),
+ (0xFC5A, "M", "يي"),
+ ]
+
+
+def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFC5B, "M", "ذٰ"),
+ (0xFC5C, "M", "رٰ"),
+ (0xFC5D, "M", "ىٰ"),
+ (0xFC5E, "3", " ٌّ"),
+ (0xFC5F, "3", " ٍّ"),
+ (0xFC60, "3", " َّ"),
+ (0xFC61, "3", " ُّ"),
+ (0xFC62, "3", " ِّ"),
+ (0xFC63, "3", " ّٰ"),
+ (0xFC64, "M", "ئر"),
+ (0xFC65, "M", "ئز"),
+ (0xFC66, "M", "ئم"),
+ (0xFC67, "M", "ئن"),
+ (0xFC68, "M", "ئى"),
+ (0xFC69, "M", "ئي"),
+ (0xFC6A, "M", "بر"),
+ (0xFC6B, "M", "بز"),
+ (0xFC6C, "M", "بم"),
+ (0xFC6D, "M", "بن"),
+ (0xFC6E, "M", "بى"),
+ (0xFC6F, "M", "بي"),
+ (0xFC70, "M", "تر"),
+ (0xFC71, "M", "تز"),
+ (0xFC72, "M", "تم"),
+ (0xFC73, "M", "تن"),
+ (0xFC74, "M", "تى"),
+ (0xFC75, "M", "تي"),
+ (0xFC76, "M", "ثر"),
+ (0xFC77, "M", "ثز"),
+ (0xFC78, "M", "ثم"),
+ (0xFC79, "M", "ثن"),
+ (0xFC7A, "M", "ثى"),
+ (0xFC7B, "M", "ثي"),
+ (0xFC7C, "M", "فى"),
+ (0xFC7D, "M", "في"),
+ (0xFC7E, "M", "قى"),
+ (0xFC7F, "M", "قي"),
+ (0xFC80, "M", "كا"),
+ (0xFC81, "M", "كل"),
+ (0xFC82, "M", "كم"),
+ (0xFC83, "M", "كى"),
+ (0xFC84, "M", "كي"),
+ (0xFC85, "M", "لم"),
+ (0xFC86, "M", "لى"),
+ (0xFC87, "M", "لي"),
+ (0xFC88, "M", "ما"),
+ (0xFC89, "M", "مم"),
+ (0xFC8A, "M", "نر"),
+ (0xFC8B, "M", "نز"),
+ (0xFC8C, "M", "نم"),
+ (0xFC8D, "M", "نن"),
+ (0xFC8E, "M", "نى"),
+ (0xFC8F, "M", "ني"),
+ (0xFC90, "M", "ىٰ"),
+ (0xFC91, "M", "ير"),
+ (0xFC92, "M", "يز"),
+ (0xFC93, "M", "يم"),
+ (0xFC94, "M", "ين"),
+ (0xFC95, "M", "يى"),
+ (0xFC96, "M", "يي"),
+ (0xFC97, "M", "ئج"),
+ (0xFC98, "M", "ئح"),
+ (0xFC99, "M", "ئخ"),
+ (0xFC9A, "M", "ئم"),
+ (0xFC9B, "M", "ئه"),
+ (0xFC9C, "M", "بج"),
+ (0xFC9D, "M", "بح"),
+ (0xFC9E, "M", "بخ"),
+ (0xFC9F, "M", "بم"),
+ (0xFCA0, "M", "به"),
+ (0xFCA1, "M", "تج"),
+ (0xFCA2, "M", "تح"),
+ (0xFCA3, "M", "تخ"),
+ (0xFCA4, "M", "تم"),
+ (0xFCA5, "M", "ته"),
+ (0xFCA6, "M", "ثم"),
+ (0xFCA7, "M", "جح"),
+ (0xFCA8, "M", "جم"),
+ (0xFCA9, "M", "حج"),
+ (0xFCAA, "M", "حم"),
+ (0xFCAB, "M", "خج"),
+ (0xFCAC, "M", "خم"),
+ (0xFCAD, "M", "سج"),
+ (0xFCAE, "M", "سح"),
+ (0xFCAF, "M", "سخ"),
+ (0xFCB0, "M", "سم"),
+ (0xFCB1, "M", "صح"),
+ (0xFCB2, "M", "صخ"),
+ (0xFCB3, "M", "صم"),
+ (0xFCB4, "M", "ضج"),
+ (0xFCB5, "M", "ضح"),
+ (0xFCB6, "M", "ضخ"),
+ (0xFCB7, "M", "ضم"),
+ (0xFCB8, "M", "طح"),
+ (0xFCB9, "M", "ظم"),
+ (0xFCBA, "M", "عج"),
+ (0xFCBB, "M", "عم"),
+ (0xFCBC, "M", "غج"),
+ (0xFCBD, "M", "غم"),
+ (0xFCBE, "M", "فج"),
+ ]
+
+
+def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFCBF, "M", "فح"),
+ (0xFCC0, "M", "فخ"),
+ (0xFCC1, "M", "فم"),
+ (0xFCC2, "M", "قح"),
+ (0xFCC3, "M", "قم"),
+ (0xFCC4, "M", "كج"),
+ (0xFCC5, "M", "كح"),
+ (0xFCC6, "M", "كخ"),
+ (0xFCC7, "M", "كل"),
+ (0xFCC8, "M", "كم"),
+ (0xFCC9, "M", "لج"),
+ (0xFCCA, "M", "لح"),
+ (0xFCCB, "M", "لخ"),
+ (0xFCCC, "M", "لم"),
+ (0xFCCD, "M", "له"),
+ (0xFCCE, "M", "مج"),
+ (0xFCCF, "M", "مح"),
+ (0xFCD0, "M", "مخ"),
+ (0xFCD1, "M", "مم"),
+ (0xFCD2, "M", "نج"),
+ (0xFCD3, "M", "نح"),
+ (0xFCD4, "M", "نخ"),
+ (0xFCD5, "M", "نم"),
+ (0xFCD6, "M", "نه"),
+ (0xFCD7, "M", "هج"),
+ (0xFCD8, "M", "هم"),
+ (0xFCD9, "M", "هٰ"),
+ (0xFCDA, "M", "يج"),
+ (0xFCDB, "M", "يح"),
+ (0xFCDC, "M", "يخ"),
+ (0xFCDD, "M", "يم"),
+ (0xFCDE, "M", "يه"),
+ (0xFCDF, "M", "ئم"),
+ (0xFCE0, "M", "ئه"),
+ (0xFCE1, "M", "بم"),
+ (0xFCE2, "M", "به"),
+ (0xFCE3, "M", "تم"),
+ (0xFCE4, "M", "ته"),
+ (0xFCE5, "M", "ثم"),
+ (0xFCE6, "M", "ثه"),
+ (0xFCE7, "M", "سم"),
+ (0xFCE8, "M", "سه"),
+ (0xFCE9, "M", "شم"),
+ (0xFCEA, "M", "شه"),
+ (0xFCEB, "M", "كل"),
+ (0xFCEC, "M", "كم"),
+ (0xFCED, "M", "لم"),
+ (0xFCEE, "M", "نم"),
+ (0xFCEF, "M", "نه"),
+ (0xFCF0, "M", "يم"),
+ (0xFCF1, "M", "يه"),
+ (0xFCF2, "M", "ـَّ"),
+ (0xFCF3, "M", "ـُّ"),
+ (0xFCF4, "M", "ـِّ"),
+ (0xFCF5, "M", "طى"),
+ (0xFCF6, "M", "طي"),
+ (0xFCF7, "M", "عى"),
+ (0xFCF8, "M", "عي"),
+ (0xFCF9, "M", "غى"),
+ (0xFCFA, "M", "غي"),
+ (0xFCFB, "M", "سى"),
+ (0xFCFC, "M", "سي"),
+ (0xFCFD, "M", "شى"),
+ (0xFCFE, "M", "شي"),
+ (0xFCFF, "M", "حى"),
+ (0xFD00, "M", "حي"),
+ (0xFD01, "M", "جى"),
+ (0xFD02, "M", "جي"),
+ (0xFD03, "M", "خى"),
+ (0xFD04, "M", "خي"),
+ (0xFD05, "M", "صى"),
+ (0xFD06, "M", "صي"),
+ (0xFD07, "M", "ضى"),
+ (0xFD08, "M", "ضي"),
+ (0xFD09, "M", "شج"),
+ (0xFD0A, "M", "شح"),
+ (0xFD0B, "M", "شخ"),
+ (0xFD0C, "M", "شم"),
+ (0xFD0D, "M", "شر"),
+ (0xFD0E, "M", "سر"),
+ (0xFD0F, "M", "صر"),
+ (0xFD10, "M", "ضر"),
+ (0xFD11, "M", "طى"),
+ (0xFD12, "M", "طي"),
+ (0xFD13, "M", "عى"),
+ (0xFD14, "M", "عي"),
+ (0xFD15, "M", "غى"),
+ (0xFD16, "M", "غي"),
+ (0xFD17, "M", "سى"),
+ (0xFD18, "M", "سي"),
+ (0xFD19, "M", "شى"),
+ (0xFD1A, "M", "شي"),
+ (0xFD1B, "M", "حى"),
+ (0xFD1C, "M", "حي"),
+ (0xFD1D, "M", "جى"),
+ (0xFD1E, "M", "جي"),
+ (0xFD1F, "M", "خى"),
+ (0xFD20, "M", "خي"),
+ (0xFD21, "M", "صى"),
+ (0xFD22, "M", "صي"),
+ ]
+
+
+def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFD23, "M", "ضى"),
+ (0xFD24, "M", "ضي"),
+ (0xFD25, "M", "شج"),
+ (0xFD26, "M", "شح"),
+ (0xFD27, "M", "شخ"),
+ (0xFD28, "M", "شم"),
+ (0xFD29, "M", "شر"),
+ (0xFD2A, "M", "سر"),
+ (0xFD2B, "M", "صر"),
+ (0xFD2C, "M", "ضر"),
+ (0xFD2D, "M", "شج"),
+ (0xFD2E, "M", "شح"),
+ (0xFD2F, "M", "شخ"),
+ (0xFD30, "M", "شم"),
+ (0xFD31, "M", "سه"),
+ (0xFD32, "M", "شه"),
+ (0xFD33, "M", "طم"),
+ (0xFD34, "M", "سج"),
+ (0xFD35, "M", "سح"),
+ (0xFD36, "M", "سخ"),
+ (0xFD37, "M", "شج"),
+ (0xFD38, "M", "شح"),
+ (0xFD39, "M", "شخ"),
+ (0xFD3A, "M", "طم"),
+ (0xFD3B, "M", "ظم"),
+ (0xFD3C, "M", "اً"),
+ (0xFD3E, "V"),
+ (0xFD50, "M", "تجم"),
+ (0xFD51, "M", "تحج"),
+ (0xFD53, "M", "تحم"),
+ (0xFD54, "M", "تخم"),
+ (0xFD55, "M", "تمج"),
+ (0xFD56, "M", "تمح"),
+ (0xFD57, "M", "تمخ"),
+ (0xFD58, "M", "جمح"),
+ (0xFD5A, "M", "حمي"),
+ (0xFD5B, "M", "حمى"),
+ (0xFD5C, "M", "سحج"),
+ (0xFD5D, "M", "سجح"),
+ (0xFD5E, "M", "سجى"),
+ (0xFD5F, "M", "سمح"),
+ (0xFD61, "M", "سمج"),
+ (0xFD62, "M", "سمم"),
+ (0xFD64, "M", "صحح"),
+ (0xFD66, "M", "صمم"),
+ (0xFD67, "M", "شحم"),
+ (0xFD69, "M", "شجي"),
+ (0xFD6A, "M", "شمخ"),
+ (0xFD6C, "M", "شمم"),
+ (0xFD6E, "M", "ضحى"),
+ (0xFD6F, "M", "ضخم"),
+ (0xFD71, "M", "طمح"),
+ (0xFD73, "M", "طمم"),
+ (0xFD74, "M", "طمي"),
+ (0xFD75, "M", "عجم"),
+ (0xFD76, "M", "عمم"),
+ (0xFD78, "M", "عمى"),
+ (0xFD79, "M", "غمم"),
+ (0xFD7A, "M", "غمي"),
+ (0xFD7B, "M", "غمى"),
+ (0xFD7C, "M", "فخم"),
+ (0xFD7E, "M", "قمح"),
+ (0xFD7F, "M", "قمم"),
+ (0xFD80, "M", "لحم"),
+ (0xFD81, "M", "لحي"),
+ (0xFD82, "M", "لحى"),
+ (0xFD83, "M", "لجج"),
+ (0xFD85, "M", "لخم"),
+ (0xFD87, "M", "لمح"),
+ (0xFD89, "M", "محج"),
+ (0xFD8A, "M", "محم"),
+ (0xFD8B, "M", "محي"),
+ (0xFD8C, "M", "مجح"),
+ (0xFD8D, "M", "مجم"),
+ (0xFD8E, "M", "مخج"),
+ (0xFD8F, "M", "مخم"),
+ (0xFD90, "X"),
+ (0xFD92, "M", "مجخ"),
+ (0xFD93, "M", "همج"),
+ (0xFD94, "M", "همم"),
+ (0xFD95, "M", "نحم"),
+ (0xFD96, "M", "نحى"),
+ (0xFD97, "M", "نجم"),
+ (0xFD99, "M", "نجى"),
+ (0xFD9A, "M", "نمي"),
+ (0xFD9B, "M", "نمى"),
+ (0xFD9C, "M", "يمم"),
+ (0xFD9E, "M", "بخي"),
+ (0xFD9F, "M", "تجي"),
+ (0xFDA0, "M", "تجى"),
+ (0xFDA1, "M", "تخي"),
+ (0xFDA2, "M", "تخى"),
+ (0xFDA3, "M", "تمي"),
+ (0xFDA4, "M", "تمى"),
+ (0xFDA5, "M", "جمي"),
+ (0xFDA6, "M", "جحى"),
+ (0xFDA7, "M", "جمى"),
+ (0xFDA8, "M", "سخى"),
+ (0xFDA9, "M", "صحي"),
+ (0xFDAA, "M", "شحي"),
+ ]
+
+
+def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFDAB, "M", "ضحي"),
+ (0xFDAC, "M", "لجي"),
+ (0xFDAD, "M", "لمي"),
+ (0xFDAE, "M", "يحي"),
+ (0xFDAF, "M", "يجي"),
+ (0xFDB0, "M", "يمي"),
+ (0xFDB1, "M", "ممي"),
+ (0xFDB2, "M", "قمي"),
+ (0xFDB3, "M", "نحي"),
+ (0xFDB4, "M", "قمح"),
+ (0xFDB5, "M", "لحم"),
+ (0xFDB6, "M", "عمي"),
+ (0xFDB7, "M", "كمي"),
+ (0xFDB8, "M", "نجح"),
+ (0xFDB9, "M", "مخي"),
+ (0xFDBA, "M", "لجم"),
+ (0xFDBB, "M", "كمم"),
+ (0xFDBC, "M", "لجم"),
+ (0xFDBD, "M", "نجح"),
+ (0xFDBE, "M", "جحي"),
+ (0xFDBF, "M", "حجي"),
+ (0xFDC0, "M", "مجي"),
+ (0xFDC1, "M", "فمي"),
+ (0xFDC2, "M", "بحي"),
+ (0xFDC3, "M", "كمم"),
+ (0xFDC4, "M", "عجم"),
+ (0xFDC5, "M", "صمم"),
+ (0xFDC6, "M", "سخي"),
+ (0xFDC7, "M", "نجي"),
+ (0xFDC8, "X"),
+ (0xFDCF, "V"),
+ (0xFDD0, "X"),
+ (0xFDF0, "M", "صلے"),
+ (0xFDF1, "M", "قلے"),
+ (0xFDF2, "M", "الله"),
+ (0xFDF3, "M", "اكبر"),
+ (0xFDF4, "M", "محمد"),
+ (0xFDF5, "M", "صلعم"),
+ (0xFDF6, "M", "رسول"),
+ (0xFDF7, "M", "عليه"),
+ (0xFDF8, "M", "وسلم"),
+ (0xFDF9, "M", "صلى"),
+ (0xFDFA, "3", "صلى الله عليه وسلم"),
+ (0xFDFB, "3", "جل جلاله"),
+ (0xFDFC, "M", "ریال"),
+ (0xFDFD, "V"),
+ (0xFE00, "I"),
+ (0xFE10, "3", ","),
+ (0xFE11, "M", "、"),
+ (0xFE12, "X"),
+ (0xFE13, "3", ":"),
+ (0xFE14, "3", ";"),
+ (0xFE15, "3", "!"),
+ (0xFE16, "3", "?"),
+ (0xFE17, "M", "〖"),
+ (0xFE18, "M", "〗"),
+ (0xFE19, "X"),
+ (0xFE20, "V"),
+ (0xFE30, "X"),
+ (0xFE31, "M", "—"),
+ (0xFE32, "M", "–"),
+ (0xFE33, "3", "_"),
+ (0xFE35, "3", "("),
+ (0xFE36, "3", ")"),
+ (0xFE37, "3", "{"),
+ (0xFE38, "3", "}"),
+ (0xFE39, "M", "〔"),
+ (0xFE3A, "M", "〕"),
+ (0xFE3B, "M", "【"),
+ (0xFE3C, "M", "】"),
+ (0xFE3D, "M", "《"),
+ (0xFE3E, "M", "》"),
+ (0xFE3F, "M", "〈"),
+ (0xFE40, "M", "〉"),
+ (0xFE41, "M", "「"),
+ (0xFE42, "M", "」"),
+ (0xFE43, "M", "『"),
+ (0xFE44, "M", "』"),
+ (0xFE45, "V"),
+ (0xFE47, "3", "["),
+ (0xFE48, "3", "]"),
+ (0xFE49, "3", " ̅"),
+ (0xFE4D, "3", "_"),
+ (0xFE50, "3", ","),
+ (0xFE51, "M", "、"),
+ (0xFE52, "X"),
+ (0xFE54, "3", ";"),
+ (0xFE55, "3", ":"),
+ (0xFE56, "3", "?"),
+ (0xFE57, "3", "!"),
+ (0xFE58, "M", "—"),
+ (0xFE59, "3", "("),
+ (0xFE5A, "3", ")"),
+ (0xFE5B, "3", "{"),
+ (0xFE5C, "3", "}"),
+ (0xFE5D, "M", "〔"),
+ (0xFE5E, "M", "〕"),
+ (0xFE5F, "3", "#"),
+ (0xFE60, "3", "&"),
+ (0xFE61, "3", "*"),
+ ]
+
+
+def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFE62, "3", "+"),
+ (0xFE63, "M", "-"),
+ (0xFE64, "3", "<"),
+ (0xFE65, "3", ">"),
+ (0xFE66, "3", "="),
+ (0xFE67, "X"),
+ (0xFE68, "3", "\\"),
+ (0xFE69, "3", "$"),
+ (0xFE6A, "3", "%"),
+ (0xFE6B, "3", "@"),
+ (0xFE6C, "X"),
+ (0xFE70, "3", " ً"),
+ (0xFE71, "M", "ـً"),
+ (0xFE72, "3", " ٌ"),
+ (0xFE73, "V"),
+ (0xFE74, "3", " ٍ"),
+ (0xFE75, "X"),
+ (0xFE76, "3", " َ"),
+ (0xFE77, "M", "ـَ"),
+ (0xFE78, "3", " ُ"),
+ (0xFE79, "M", "ـُ"),
+ (0xFE7A, "3", " ِ"),
+ (0xFE7B, "M", "ـِ"),
+ (0xFE7C, "3", " ّ"),
+ (0xFE7D, "M", "ـّ"),
+ (0xFE7E, "3", " ْ"),
+ (0xFE7F, "M", "ـْ"),
+ (0xFE80, "M", "ء"),
+ (0xFE81, "M", "آ"),
+ (0xFE83, "M", "أ"),
+ (0xFE85, "M", "ؤ"),
+ (0xFE87, "M", "إ"),
+ (0xFE89, "M", "ئ"),
+ (0xFE8D, "M", "ا"),
+ (0xFE8F, "M", "ب"),
+ (0xFE93, "M", "ة"),
+ (0xFE95, "M", "ت"),
+ (0xFE99, "M", "ث"),
+ (0xFE9D, "M", "ج"),
+ (0xFEA1, "M", "ح"),
+ (0xFEA5, "M", "خ"),
+ (0xFEA9, "M", "د"),
+ (0xFEAB, "M", "ذ"),
+ (0xFEAD, "M", "ر"),
+ (0xFEAF, "M", "ز"),
+ (0xFEB1, "M", "س"),
+ (0xFEB5, "M", "ش"),
+ (0xFEB9, "M", "ص"),
+ (0xFEBD, "M", "ض"),
+ (0xFEC1, "M", "ط"),
+ (0xFEC5, "M", "ظ"),
+ (0xFEC9, "M", "ع"),
+ (0xFECD, "M", "غ"),
+ (0xFED1, "M", "ف"),
+ (0xFED5, "M", "ق"),
+ (0xFED9, "M", "ك"),
+ (0xFEDD, "M", "ل"),
+ (0xFEE1, "M", "م"),
+ (0xFEE5, "M", "ن"),
+ (0xFEE9, "M", "ه"),
+ (0xFEED, "M", "و"),
+ (0xFEEF, "M", "ى"),
+ (0xFEF1, "M", "ي"),
+ (0xFEF5, "M", "لآ"),
+ (0xFEF7, "M", "لأ"),
+ (0xFEF9, "M", "لإ"),
+ (0xFEFB, "M", "لا"),
+ (0xFEFD, "X"),
+ (0xFEFF, "I"),
+ (0xFF00, "X"),
+ (0xFF01, "3", "!"),
+ (0xFF02, "3", '"'),
+ (0xFF03, "3", "#"),
+ (0xFF04, "3", "$"),
+ (0xFF05, "3", "%"),
+ (0xFF06, "3", "&"),
+ (0xFF07, "3", "'"),
+ (0xFF08, "3", "("),
+ (0xFF09, "3", ")"),
+ (0xFF0A, "3", "*"),
+ (0xFF0B, "3", "+"),
+ (0xFF0C, "3", ","),
+ (0xFF0D, "M", "-"),
+ (0xFF0E, "M", "."),
+ (0xFF0F, "3", "/"),
+ (0xFF10, "M", "0"),
+ (0xFF11, "M", "1"),
+ (0xFF12, "M", "2"),
+ (0xFF13, "M", "3"),
+ (0xFF14, "M", "4"),
+ (0xFF15, "M", "5"),
+ (0xFF16, "M", "6"),
+ (0xFF17, "M", "7"),
+ (0xFF18, "M", "8"),
+ (0xFF19, "M", "9"),
+ (0xFF1A, "3", ":"),
+ (0xFF1B, "3", ";"),
+ (0xFF1C, "3", "<"),
+ (0xFF1D, "3", "="),
+ (0xFF1E, "3", ">"),
+ ]
+
+
+def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFF1F, "3", "?"),
+ (0xFF20, "3", "@"),
+ (0xFF21, "M", "a"),
+ (0xFF22, "M", "b"),
+ (0xFF23, "M", "c"),
+ (0xFF24, "M", "d"),
+ (0xFF25, "M", "e"),
+ (0xFF26, "M", "f"),
+ (0xFF27, "M", "g"),
+ (0xFF28, "M", "h"),
+ (0xFF29, "M", "i"),
+ (0xFF2A, "M", "j"),
+ (0xFF2B, "M", "k"),
+ (0xFF2C, "M", "l"),
+ (0xFF2D, "M", "m"),
+ (0xFF2E, "M", "n"),
+ (0xFF2F, "M", "o"),
+ (0xFF30, "M", "p"),
+ (0xFF31, "M", "q"),
+ (0xFF32, "M", "r"),
+ (0xFF33, "M", "s"),
+ (0xFF34, "M", "t"),
+ (0xFF35, "M", "u"),
+ (0xFF36, "M", "v"),
+ (0xFF37, "M", "w"),
+ (0xFF38, "M", "x"),
+ (0xFF39, "M", "y"),
+ (0xFF3A, "M", "z"),
+ (0xFF3B, "3", "["),
+ (0xFF3C, "3", "\\"),
+ (0xFF3D, "3", "]"),
+ (0xFF3E, "3", "^"),
+ (0xFF3F, "3", "_"),
+ (0xFF40, "3", "`"),
+ (0xFF41, "M", "a"),
+ (0xFF42, "M", "b"),
+ (0xFF43, "M", "c"),
+ (0xFF44, "M", "d"),
+ (0xFF45, "M", "e"),
+ (0xFF46, "M", "f"),
+ (0xFF47, "M", "g"),
+ (0xFF48, "M", "h"),
+ (0xFF49, "M", "i"),
+ (0xFF4A, "M", "j"),
+ (0xFF4B, "M", "k"),
+ (0xFF4C, "M", "l"),
+ (0xFF4D, "M", "m"),
+ (0xFF4E, "M", "n"),
+ (0xFF4F, "M", "o"),
+ (0xFF50, "M", "p"),
+ (0xFF51, "M", "q"),
+ (0xFF52, "M", "r"),
+ (0xFF53, "M", "s"),
+ (0xFF54, "M", "t"),
+ (0xFF55, "M", "u"),
+ (0xFF56, "M", "v"),
+ (0xFF57, "M", "w"),
+ (0xFF58, "M", "x"),
+ (0xFF59, "M", "y"),
+ (0xFF5A, "M", "z"),
+ (0xFF5B, "3", "{"),
+ (0xFF5C, "3", "|"),
+ (0xFF5D, "3", "}"),
+ (0xFF5E, "3", "~"),
+ (0xFF5F, "M", "⦅"),
+ (0xFF60, "M", "⦆"),
+ (0xFF61, "M", "."),
+ (0xFF62, "M", "「"),
+ (0xFF63, "M", "」"),
+ (0xFF64, "M", "、"),
+ (0xFF65, "M", "・"),
+ (0xFF66, "M", "ヲ"),
+ (0xFF67, "M", "ァ"),
+ (0xFF68, "M", "ィ"),
+ (0xFF69, "M", "ゥ"),
+ (0xFF6A, "M", "ェ"),
+ (0xFF6B, "M", "ォ"),
+ (0xFF6C, "M", "ャ"),
+ (0xFF6D, "M", "ュ"),
+ (0xFF6E, "M", "ョ"),
+ (0xFF6F, "M", "ッ"),
+ (0xFF70, "M", "ー"),
+ (0xFF71, "M", "ア"),
+ (0xFF72, "M", "イ"),
+ (0xFF73, "M", "ウ"),
+ (0xFF74, "M", "エ"),
+ (0xFF75, "M", "オ"),
+ (0xFF76, "M", "カ"),
+ (0xFF77, "M", "キ"),
+ (0xFF78, "M", "ク"),
+ (0xFF79, "M", "ケ"),
+ (0xFF7A, "M", "コ"),
+ (0xFF7B, "M", "サ"),
+ (0xFF7C, "M", "シ"),
+ (0xFF7D, "M", "ス"),
+ (0xFF7E, "M", "セ"),
+ (0xFF7F, "M", "ソ"),
+ (0xFF80, "M", "タ"),
+ (0xFF81, "M", "チ"),
+ (0xFF82, "M", "ツ"),
+ ]
+
+
+def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFF83, "M", "テ"),
+ (0xFF84, "M", "ト"),
+ (0xFF85, "M", "ナ"),
+ (0xFF86, "M", "ニ"),
+ (0xFF87, "M", "ヌ"),
+ (0xFF88, "M", "ネ"),
+ (0xFF89, "M", "ノ"),
+ (0xFF8A, "M", "ハ"),
+ (0xFF8B, "M", "ヒ"),
+ (0xFF8C, "M", "フ"),
+ (0xFF8D, "M", "ヘ"),
+ (0xFF8E, "M", "ホ"),
+ (0xFF8F, "M", "マ"),
+ (0xFF90, "M", "ミ"),
+ (0xFF91, "M", "ム"),
+ (0xFF92, "M", "メ"),
+ (0xFF93, "M", "モ"),
+ (0xFF94, "M", "ヤ"),
+ (0xFF95, "M", "ユ"),
+ (0xFF96, "M", "ヨ"),
+ (0xFF97, "M", "ラ"),
+ (0xFF98, "M", "リ"),
+ (0xFF99, "M", "ル"),
+ (0xFF9A, "M", "レ"),
+ (0xFF9B, "M", "ロ"),
+ (0xFF9C, "M", "ワ"),
+ (0xFF9D, "M", "ン"),
+ (0xFF9E, "M", "゙"),
+ (0xFF9F, "M", "゚"),
+ (0xFFA0, "X"),
+ (0xFFA1, "M", "ᄀ"),
+ (0xFFA2, "M", "ᄁ"),
+ (0xFFA3, "M", "ᆪ"),
+ (0xFFA4, "M", "ᄂ"),
+ (0xFFA5, "M", "ᆬ"),
+ (0xFFA6, "M", "ᆭ"),
+ (0xFFA7, "M", "ᄃ"),
+ (0xFFA8, "M", "ᄄ"),
+ (0xFFA9, "M", "ᄅ"),
+ (0xFFAA, "M", "ᆰ"),
+ (0xFFAB, "M", "ᆱ"),
+ (0xFFAC, "M", "ᆲ"),
+ (0xFFAD, "M", "ᆳ"),
+ (0xFFAE, "M", "ᆴ"),
+ (0xFFAF, "M", "ᆵ"),
+ (0xFFB0, "M", "ᄚ"),
+ (0xFFB1, "M", "ᄆ"),
+ (0xFFB2, "M", "ᄇ"),
+ (0xFFB3, "M", "ᄈ"),
+ (0xFFB4, "M", "ᄡ"),
+ (0xFFB5, "M", "ᄉ"),
+ (0xFFB6, "M", "ᄊ"),
+ (0xFFB7, "M", "ᄋ"),
+ (0xFFB8, "M", "ᄌ"),
+ (0xFFB9, "M", "ᄍ"),
+ (0xFFBA, "M", "ᄎ"),
+ (0xFFBB, "M", "ᄏ"),
+ (0xFFBC, "M", "ᄐ"),
+ (0xFFBD, "M", "ᄑ"),
+ (0xFFBE, "M", "ᄒ"),
+ (0xFFBF, "X"),
+ (0xFFC2, "M", "ᅡ"),
+ (0xFFC3, "M", "ᅢ"),
+ (0xFFC4, "M", "ᅣ"),
+ (0xFFC5, "M", "ᅤ"),
+ (0xFFC6, "M", "ᅥ"),
+ (0xFFC7, "M", "ᅦ"),
+ (0xFFC8, "X"),
+ (0xFFCA, "M", "ᅧ"),
+ (0xFFCB, "M", "ᅨ"),
+ (0xFFCC, "M", "ᅩ"),
+ (0xFFCD, "M", "ᅪ"),
+ (0xFFCE, "M", "ᅫ"),
+ (0xFFCF, "M", "ᅬ"),
+ (0xFFD0, "X"),
+ (0xFFD2, "M", "ᅭ"),
+ (0xFFD3, "M", "ᅮ"),
+ (0xFFD4, "M", "ᅯ"),
+ (0xFFD5, "M", "ᅰ"),
+ (0xFFD6, "M", "ᅱ"),
+ (0xFFD7, "M", "ᅲ"),
+ (0xFFD8, "X"),
+ (0xFFDA, "M", "ᅳ"),
+ (0xFFDB, "M", "ᅴ"),
+ (0xFFDC, "M", "ᅵ"),
+ (0xFFDD, "X"),
+ (0xFFE0, "M", "¢"),
+ (0xFFE1, "M", "£"),
+ (0xFFE2, "M", "¬"),
+ (0xFFE3, "3", " ̄"),
+ (0xFFE4, "M", "¦"),
+ (0xFFE5, "M", "¥"),
+ (0xFFE6, "M", "₩"),
+ (0xFFE7, "X"),
+ (0xFFE8, "M", "│"),
+ (0xFFE9, "M", "←"),
+ (0xFFEA, "M", "↑"),
+ (0xFFEB, "M", "→"),
+ (0xFFEC, "M", "↓"),
+ (0xFFED, "M", "■"),
+ ]
+
+
+def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFFEE, "M", "○"),
+ (0xFFEF, "X"),
+ (0x10000, "V"),
+ (0x1000C, "X"),
+ (0x1000D, "V"),
+ (0x10027, "X"),
+ (0x10028, "V"),
+ (0x1003B, "X"),
+ (0x1003C, "V"),
+ (0x1003E, "X"),
+ (0x1003F, "V"),
+ (0x1004E, "X"),
+ (0x10050, "V"),
+ (0x1005E, "X"),
+ (0x10080, "V"),
+ (0x100FB, "X"),
+ (0x10100, "V"),
+ (0x10103, "X"),
+ (0x10107, "V"),
+ (0x10134, "X"),
+ (0x10137, "V"),
+ (0x1018F, "X"),
+ (0x10190, "V"),
+ (0x1019D, "X"),
+ (0x101A0, "V"),
+ (0x101A1, "X"),
+ (0x101D0, "V"),
+ (0x101FE, "X"),
+ (0x10280, "V"),
+ (0x1029D, "X"),
+ (0x102A0, "V"),
+ (0x102D1, "X"),
+ (0x102E0, "V"),
+ (0x102FC, "X"),
+ (0x10300, "V"),
+ (0x10324, "X"),
+ (0x1032D, "V"),
+ (0x1034B, "X"),
+ (0x10350, "V"),
+ (0x1037B, "X"),
+ (0x10380, "V"),
+ (0x1039E, "X"),
+ (0x1039F, "V"),
+ (0x103C4, "X"),
+ (0x103C8, "V"),
+ (0x103D6, "X"),
+ (0x10400, "M", "𐐨"),
+ (0x10401, "M", "𐐩"),
+ (0x10402, "M", "𐐪"),
+ (0x10403, "M", "𐐫"),
+ (0x10404, "M", "𐐬"),
+ (0x10405, "M", "𐐭"),
+ (0x10406, "M", "𐐮"),
+ (0x10407, "M", "𐐯"),
+ (0x10408, "M", "𐐰"),
+ (0x10409, "M", "𐐱"),
+ (0x1040A, "M", "𐐲"),
+ (0x1040B, "M", "𐐳"),
+ (0x1040C, "M", "𐐴"),
+ (0x1040D, "M", "𐐵"),
+ (0x1040E, "M", "𐐶"),
+ (0x1040F, "M", "𐐷"),
+ (0x10410, "M", "𐐸"),
+ (0x10411, "M", "𐐹"),
+ (0x10412, "M", "𐐺"),
+ (0x10413, "M", "𐐻"),
+ (0x10414, "M", "𐐼"),
+ (0x10415, "M", "𐐽"),
+ (0x10416, "M", "𐐾"),
+ (0x10417, "M", "𐐿"),
+ (0x10418, "M", "𐑀"),
+ (0x10419, "M", "𐑁"),
+ (0x1041A, "M", "𐑂"),
+ (0x1041B, "M", "𐑃"),
+ (0x1041C, "M", "𐑄"),
+ (0x1041D, "M", "𐑅"),
+ (0x1041E, "M", "𐑆"),
+ (0x1041F, "M", "𐑇"),
+ (0x10420, "M", "𐑈"),
+ (0x10421, "M", "𐑉"),
+ (0x10422, "M", "𐑊"),
+ (0x10423, "M", "𐑋"),
+ (0x10424, "M", "𐑌"),
+ (0x10425, "M", "𐑍"),
+ (0x10426, "M", "𐑎"),
+ (0x10427, "M", "𐑏"),
+ (0x10428, "V"),
+ (0x1049E, "X"),
+ (0x104A0, "V"),
+ (0x104AA, "X"),
+ (0x104B0, "M", "𐓘"),
+ (0x104B1, "M", "𐓙"),
+ (0x104B2, "M", "𐓚"),
+ (0x104B3, "M", "𐓛"),
+ (0x104B4, "M", "𐓜"),
+ (0x104B5, "M", "𐓝"),
+ (0x104B6, "M", "𐓞"),
+ (0x104B7, "M", "𐓟"),
+ (0x104B8, "M", "𐓠"),
+ (0x104B9, "M", "𐓡"),
+ ]
+
+
+def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x104BA, "M", "𐓢"),
+ (0x104BB, "M", "𐓣"),
+ (0x104BC, "M", "𐓤"),
+ (0x104BD, "M", "𐓥"),
+ (0x104BE, "M", "𐓦"),
+ (0x104BF, "M", "𐓧"),
+ (0x104C0, "M", "𐓨"),
+ (0x104C1, "M", "𐓩"),
+ (0x104C2, "M", "𐓪"),
+ (0x104C3, "M", "𐓫"),
+ (0x104C4, "M", "𐓬"),
+ (0x104C5, "M", "𐓭"),
+ (0x104C6, "M", "𐓮"),
+ (0x104C7, "M", "𐓯"),
+ (0x104C8, "M", "𐓰"),
+ (0x104C9, "M", "𐓱"),
+ (0x104CA, "M", "𐓲"),
+ (0x104CB, "M", "𐓳"),
+ (0x104CC, "M", "𐓴"),
+ (0x104CD, "M", "𐓵"),
+ (0x104CE, "M", "𐓶"),
+ (0x104CF, "M", "𐓷"),
+ (0x104D0, "M", "𐓸"),
+ (0x104D1, "M", "𐓹"),
+ (0x104D2, "M", "𐓺"),
+ (0x104D3, "M", "𐓻"),
+ (0x104D4, "X"),
+ (0x104D8, "V"),
+ (0x104FC, "X"),
+ (0x10500, "V"),
+ (0x10528, "X"),
+ (0x10530, "V"),
+ (0x10564, "X"),
+ (0x1056F, "V"),
+ (0x10570, "M", "𐖗"),
+ (0x10571, "M", "𐖘"),
+ (0x10572, "M", "𐖙"),
+ (0x10573, "M", "𐖚"),
+ (0x10574, "M", "𐖛"),
+ (0x10575, "M", "𐖜"),
+ (0x10576, "M", "𐖝"),
+ (0x10577, "M", "𐖞"),
+ (0x10578, "M", "𐖟"),
+ (0x10579, "M", "𐖠"),
+ (0x1057A, "M", "𐖡"),
+ (0x1057B, "X"),
+ (0x1057C, "M", "𐖣"),
+ (0x1057D, "M", "𐖤"),
+ (0x1057E, "M", "𐖥"),
+ (0x1057F, "M", "𐖦"),
+ (0x10580, "M", "𐖧"),
+ (0x10581, "M", "𐖨"),
+ (0x10582, "M", "𐖩"),
+ (0x10583, "M", "𐖪"),
+ (0x10584, "M", "𐖫"),
+ (0x10585, "M", "𐖬"),
+ (0x10586, "M", "𐖭"),
+ (0x10587, "M", "𐖮"),
+ (0x10588, "M", "𐖯"),
+ (0x10589, "M", "𐖰"),
+ (0x1058A, "M", "𐖱"),
+ (0x1058B, "X"),
+ (0x1058C, "M", "𐖳"),
+ (0x1058D, "M", "𐖴"),
+ (0x1058E, "M", "𐖵"),
+ (0x1058F, "M", "𐖶"),
+ (0x10590, "M", "𐖷"),
+ (0x10591, "M", "𐖸"),
+ (0x10592, "M", "𐖹"),
+ (0x10593, "X"),
+ (0x10594, "M", "𐖻"),
+ (0x10595, "M", "𐖼"),
+ (0x10596, "X"),
+ (0x10597, "V"),
+ (0x105A2, "X"),
+ (0x105A3, "V"),
+ (0x105B2, "X"),
+ (0x105B3, "V"),
+ (0x105BA, "X"),
+ (0x105BB, "V"),
+ (0x105BD, "X"),
+ (0x10600, "V"),
+ (0x10737, "X"),
+ (0x10740, "V"),
+ (0x10756, "X"),
+ (0x10760, "V"),
+ (0x10768, "X"),
+ (0x10780, "V"),
+ (0x10781, "M", "ː"),
+ (0x10782, "M", "ˑ"),
+ (0x10783, "M", "æ"),
+ (0x10784, "M", "ʙ"),
+ (0x10785, "M", "ɓ"),
+ (0x10786, "X"),
+ (0x10787, "M", "ʣ"),
+ (0x10788, "M", "ꭦ"),
+ (0x10789, "M", "ʥ"),
+ (0x1078A, "M", "ʤ"),
+ (0x1078B, "M", "ɖ"),
+ (0x1078C, "M", "ɗ"),
+ ]
+
+
+def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1078D, "M", "ᶑ"),
+ (0x1078E, "M", "ɘ"),
+ (0x1078F, "M", "ɞ"),
+ (0x10790, "M", "ʩ"),
+ (0x10791, "M", "ɤ"),
+ (0x10792, "M", "ɢ"),
+ (0x10793, "M", "ɠ"),
+ (0x10794, "M", "ʛ"),
+ (0x10795, "M", "ħ"),
+ (0x10796, "M", "ʜ"),
+ (0x10797, "M", "ɧ"),
+ (0x10798, "M", "ʄ"),
+ (0x10799, "M", "ʪ"),
+ (0x1079A, "M", "ʫ"),
+ (0x1079B, "M", "ɬ"),
+ (0x1079C, "M", "𝼄"),
+ (0x1079D, "M", "ꞎ"),
+ (0x1079E, "M", "ɮ"),
+ (0x1079F, "M", "𝼅"),
+ (0x107A0, "M", "ʎ"),
+ (0x107A1, "M", "𝼆"),
+ (0x107A2, "M", "ø"),
+ (0x107A3, "M", "ɶ"),
+ (0x107A4, "M", "ɷ"),
+ (0x107A5, "M", "q"),
+ (0x107A6, "M", "ɺ"),
+ (0x107A7, "M", "𝼈"),
+ (0x107A8, "M", "ɽ"),
+ (0x107A9, "M", "ɾ"),
+ (0x107AA, "M", "ʀ"),
+ (0x107AB, "M", "ʨ"),
+ (0x107AC, "M", "ʦ"),
+ (0x107AD, "M", "ꭧ"),
+ (0x107AE, "M", "ʧ"),
+ (0x107AF, "M", "ʈ"),
+ (0x107B0, "M", "ⱱ"),
+ (0x107B1, "X"),
+ (0x107B2, "M", "ʏ"),
+ (0x107B3, "M", "ʡ"),
+ (0x107B4, "M", "ʢ"),
+ (0x107B5, "M", "ʘ"),
+ (0x107B6, "M", "ǀ"),
+ (0x107B7, "M", "ǁ"),
+ (0x107B8, "M", "ǂ"),
+ (0x107B9, "M", "𝼊"),
+ (0x107BA, "M", "𝼞"),
+ (0x107BB, "X"),
+ (0x10800, "V"),
+ (0x10806, "X"),
+ (0x10808, "V"),
+ (0x10809, "X"),
+ (0x1080A, "V"),
+ (0x10836, "X"),
+ (0x10837, "V"),
+ (0x10839, "X"),
+ (0x1083C, "V"),
+ (0x1083D, "X"),
+ (0x1083F, "V"),
+ (0x10856, "X"),
+ (0x10857, "V"),
+ (0x1089F, "X"),
+ (0x108A7, "V"),
+ (0x108B0, "X"),
+ (0x108E0, "V"),
+ (0x108F3, "X"),
+ (0x108F4, "V"),
+ (0x108F6, "X"),
+ (0x108FB, "V"),
+ (0x1091C, "X"),
+ (0x1091F, "V"),
+ (0x1093A, "X"),
+ (0x1093F, "V"),
+ (0x10940, "X"),
+ (0x10980, "V"),
+ (0x109B8, "X"),
+ (0x109BC, "V"),
+ (0x109D0, "X"),
+ (0x109D2, "V"),
+ (0x10A04, "X"),
+ (0x10A05, "V"),
+ (0x10A07, "X"),
+ (0x10A0C, "V"),
+ (0x10A14, "X"),
+ (0x10A15, "V"),
+ (0x10A18, "X"),
+ (0x10A19, "V"),
+ (0x10A36, "X"),
+ (0x10A38, "V"),
+ (0x10A3B, "X"),
+ (0x10A3F, "V"),
+ (0x10A49, "X"),
+ (0x10A50, "V"),
+ (0x10A59, "X"),
+ (0x10A60, "V"),
+ (0x10AA0, "X"),
+ (0x10AC0, "V"),
+ (0x10AE7, "X"),
+ (0x10AEB, "V"),
+ (0x10AF7, "X"),
+ (0x10B00, "V"),
+ ]
+
+
+def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x10B36, "X"),
+ (0x10B39, "V"),
+ (0x10B56, "X"),
+ (0x10B58, "V"),
+ (0x10B73, "X"),
+ (0x10B78, "V"),
+ (0x10B92, "X"),
+ (0x10B99, "V"),
+ (0x10B9D, "X"),
+ (0x10BA9, "V"),
+ (0x10BB0, "X"),
+ (0x10C00, "V"),
+ (0x10C49, "X"),
+ (0x10C80, "M", "𐳀"),
+ (0x10C81, "M", "𐳁"),
+ (0x10C82, "M", "𐳂"),
+ (0x10C83, "M", "𐳃"),
+ (0x10C84, "M", "𐳄"),
+ (0x10C85, "M", "𐳅"),
+ (0x10C86, "M", "𐳆"),
+ (0x10C87, "M", "𐳇"),
+ (0x10C88, "M", "𐳈"),
+ (0x10C89, "M", "𐳉"),
+ (0x10C8A, "M", "𐳊"),
+ (0x10C8B, "M", "𐳋"),
+ (0x10C8C, "M", "𐳌"),
+ (0x10C8D, "M", "𐳍"),
+ (0x10C8E, "M", "𐳎"),
+ (0x10C8F, "M", "𐳏"),
+ (0x10C90, "M", "𐳐"),
+ (0x10C91, "M", "𐳑"),
+ (0x10C92, "M", "𐳒"),
+ (0x10C93, "M", "𐳓"),
+ (0x10C94, "M", "𐳔"),
+ (0x10C95, "M", "𐳕"),
+ (0x10C96, "M", "𐳖"),
+ (0x10C97, "M", "𐳗"),
+ (0x10C98, "M", "𐳘"),
+ (0x10C99, "M", "𐳙"),
+ (0x10C9A, "M", "𐳚"),
+ (0x10C9B, "M", "𐳛"),
+ (0x10C9C, "M", "𐳜"),
+ (0x10C9D, "M", "𐳝"),
+ (0x10C9E, "M", "𐳞"),
+ (0x10C9F, "M", "𐳟"),
+ (0x10CA0, "M", "𐳠"),
+ (0x10CA1, "M", "𐳡"),
+ (0x10CA2, "M", "𐳢"),
+ (0x10CA3, "M", "𐳣"),
+ (0x10CA4, "M", "𐳤"),
+ (0x10CA5, "M", "𐳥"),
+ (0x10CA6, "M", "𐳦"),
+ (0x10CA7, "M", "𐳧"),
+ (0x10CA8, "M", "𐳨"),
+ (0x10CA9, "M", "𐳩"),
+ (0x10CAA, "M", "𐳪"),
+ (0x10CAB, "M", "𐳫"),
+ (0x10CAC, "M", "𐳬"),
+ (0x10CAD, "M", "𐳭"),
+ (0x10CAE, "M", "𐳮"),
+ (0x10CAF, "M", "𐳯"),
+ (0x10CB0, "M", "𐳰"),
+ (0x10CB1, "M", "𐳱"),
+ (0x10CB2, "M", "𐳲"),
+ (0x10CB3, "X"),
+ (0x10CC0, "V"),
+ (0x10CF3, "X"),
+ (0x10CFA, "V"),
+ (0x10D28, "X"),
+ (0x10D30, "V"),
+ (0x10D3A, "X"),
+ (0x10E60, "V"),
+ (0x10E7F, "X"),
+ (0x10E80, "V"),
+ (0x10EAA, "X"),
+ (0x10EAB, "V"),
+ (0x10EAE, "X"),
+ (0x10EB0, "V"),
+ (0x10EB2, "X"),
+ (0x10EFD, "V"),
+ (0x10F28, "X"),
+ (0x10F30, "V"),
+ (0x10F5A, "X"),
+ (0x10F70, "V"),
+ (0x10F8A, "X"),
+ (0x10FB0, "V"),
+ (0x10FCC, "X"),
+ (0x10FE0, "V"),
+ (0x10FF7, "X"),
+ (0x11000, "V"),
+ (0x1104E, "X"),
+ (0x11052, "V"),
+ (0x11076, "X"),
+ (0x1107F, "V"),
+ (0x110BD, "X"),
+ (0x110BE, "V"),
+ (0x110C3, "X"),
+ (0x110D0, "V"),
+ (0x110E9, "X"),
+ (0x110F0, "V"),
+ ]
+
+
+def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x110FA, "X"),
+ (0x11100, "V"),
+ (0x11135, "X"),
+ (0x11136, "V"),
+ (0x11148, "X"),
+ (0x11150, "V"),
+ (0x11177, "X"),
+ (0x11180, "V"),
+ (0x111E0, "X"),
+ (0x111E1, "V"),
+ (0x111F5, "X"),
+ (0x11200, "V"),
+ (0x11212, "X"),
+ (0x11213, "V"),
+ (0x11242, "X"),
+ (0x11280, "V"),
+ (0x11287, "X"),
+ (0x11288, "V"),
+ (0x11289, "X"),
+ (0x1128A, "V"),
+ (0x1128E, "X"),
+ (0x1128F, "V"),
+ (0x1129E, "X"),
+ (0x1129F, "V"),
+ (0x112AA, "X"),
+ (0x112B0, "V"),
+ (0x112EB, "X"),
+ (0x112F0, "V"),
+ (0x112FA, "X"),
+ (0x11300, "V"),
+ (0x11304, "X"),
+ (0x11305, "V"),
+ (0x1130D, "X"),
+ (0x1130F, "V"),
+ (0x11311, "X"),
+ (0x11313, "V"),
+ (0x11329, "X"),
+ (0x1132A, "V"),
+ (0x11331, "X"),
+ (0x11332, "V"),
+ (0x11334, "X"),
+ (0x11335, "V"),
+ (0x1133A, "X"),
+ (0x1133B, "V"),
+ (0x11345, "X"),
+ (0x11347, "V"),
+ (0x11349, "X"),
+ (0x1134B, "V"),
+ (0x1134E, "X"),
+ (0x11350, "V"),
+ (0x11351, "X"),
+ (0x11357, "V"),
+ (0x11358, "X"),
+ (0x1135D, "V"),
+ (0x11364, "X"),
+ (0x11366, "V"),
+ (0x1136D, "X"),
+ (0x11370, "V"),
+ (0x11375, "X"),
+ (0x11400, "V"),
+ (0x1145C, "X"),
+ (0x1145D, "V"),
+ (0x11462, "X"),
+ (0x11480, "V"),
+ (0x114C8, "X"),
+ (0x114D0, "V"),
+ (0x114DA, "X"),
+ (0x11580, "V"),
+ (0x115B6, "X"),
+ (0x115B8, "V"),
+ (0x115DE, "X"),
+ (0x11600, "V"),
+ (0x11645, "X"),
+ (0x11650, "V"),
+ (0x1165A, "X"),
+ (0x11660, "V"),
+ (0x1166D, "X"),
+ (0x11680, "V"),
+ (0x116BA, "X"),
+ (0x116C0, "V"),
+ (0x116CA, "X"),
+ (0x11700, "V"),
+ (0x1171B, "X"),
+ (0x1171D, "V"),
+ (0x1172C, "X"),
+ (0x11730, "V"),
+ (0x11747, "X"),
+ (0x11800, "V"),
+ (0x1183C, "X"),
+ (0x118A0, "M", "𑣀"),
+ (0x118A1, "M", "𑣁"),
+ (0x118A2, "M", "𑣂"),
+ (0x118A3, "M", "𑣃"),
+ (0x118A4, "M", "𑣄"),
+ (0x118A5, "M", "𑣅"),
+ (0x118A6, "M", "𑣆"),
+ (0x118A7, "M", "𑣇"),
+ (0x118A8, "M", "𑣈"),
+ (0x118A9, "M", "𑣉"),
+ (0x118AA, "M", "𑣊"),
+ ]
+
+
+def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x118AB, "M", "𑣋"),
+ (0x118AC, "M", "𑣌"),
+ (0x118AD, "M", "𑣍"),
+ (0x118AE, "M", "𑣎"),
+ (0x118AF, "M", "𑣏"),
+ (0x118B0, "M", "𑣐"),
+ (0x118B1, "M", "𑣑"),
+ (0x118B2, "M", "𑣒"),
+ (0x118B3, "M", "𑣓"),
+ (0x118B4, "M", "𑣔"),
+ (0x118B5, "M", "𑣕"),
+ (0x118B6, "M", "𑣖"),
+ (0x118B7, "M", "𑣗"),
+ (0x118B8, "M", "𑣘"),
+ (0x118B9, "M", "𑣙"),
+ (0x118BA, "M", "𑣚"),
+ (0x118BB, "M", "𑣛"),
+ (0x118BC, "M", "𑣜"),
+ (0x118BD, "M", "𑣝"),
+ (0x118BE, "M", "𑣞"),
+ (0x118BF, "M", "𑣟"),
+ (0x118C0, "V"),
+ (0x118F3, "X"),
+ (0x118FF, "V"),
+ (0x11907, "X"),
+ (0x11909, "V"),
+ (0x1190A, "X"),
+ (0x1190C, "V"),
+ (0x11914, "X"),
+ (0x11915, "V"),
+ (0x11917, "X"),
+ (0x11918, "V"),
+ (0x11936, "X"),
+ (0x11937, "V"),
+ (0x11939, "X"),
+ (0x1193B, "V"),
+ (0x11947, "X"),
+ (0x11950, "V"),
+ (0x1195A, "X"),
+ (0x119A0, "V"),
+ (0x119A8, "X"),
+ (0x119AA, "V"),
+ (0x119D8, "X"),
+ (0x119DA, "V"),
+ (0x119E5, "X"),
+ (0x11A00, "V"),
+ (0x11A48, "X"),
+ (0x11A50, "V"),
+ (0x11AA3, "X"),
+ (0x11AB0, "V"),
+ (0x11AF9, "X"),
+ (0x11B00, "V"),
+ (0x11B0A, "X"),
+ (0x11C00, "V"),
+ (0x11C09, "X"),
+ (0x11C0A, "V"),
+ (0x11C37, "X"),
+ (0x11C38, "V"),
+ (0x11C46, "X"),
+ (0x11C50, "V"),
+ (0x11C6D, "X"),
+ (0x11C70, "V"),
+ (0x11C90, "X"),
+ (0x11C92, "V"),
+ (0x11CA8, "X"),
+ (0x11CA9, "V"),
+ (0x11CB7, "X"),
+ (0x11D00, "V"),
+ (0x11D07, "X"),
+ (0x11D08, "V"),
+ (0x11D0A, "X"),
+ (0x11D0B, "V"),
+ (0x11D37, "X"),
+ (0x11D3A, "V"),
+ (0x11D3B, "X"),
+ (0x11D3C, "V"),
+ (0x11D3E, "X"),
+ (0x11D3F, "V"),
+ (0x11D48, "X"),
+ (0x11D50, "V"),
+ (0x11D5A, "X"),
+ (0x11D60, "V"),
+ (0x11D66, "X"),
+ (0x11D67, "V"),
+ (0x11D69, "X"),
+ (0x11D6A, "V"),
+ (0x11D8F, "X"),
+ (0x11D90, "V"),
+ (0x11D92, "X"),
+ (0x11D93, "V"),
+ (0x11D99, "X"),
+ (0x11DA0, "V"),
+ (0x11DAA, "X"),
+ (0x11EE0, "V"),
+ (0x11EF9, "X"),
+ (0x11F00, "V"),
+ (0x11F11, "X"),
+ (0x11F12, "V"),
+ (0x11F3B, "X"),
+ (0x11F3E, "V"),
+ ]
+
+
+def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x11F5A, "X"),
+ (0x11FB0, "V"),
+ (0x11FB1, "X"),
+ (0x11FC0, "V"),
+ (0x11FF2, "X"),
+ (0x11FFF, "V"),
+ (0x1239A, "X"),
+ (0x12400, "V"),
+ (0x1246F, "X"),
+ (0x12470, "V"),
+ (0x12475, "X"),
+ (0x12480, "V"),
+ (0x12544, "X"),
+ (0x12F90, "V"),
+ (0x12FF3, "X"),
+ (0x13000, "V"),
+ (0x13430, "X"),
+ (0x13440, "V"),
+ (0x13456, "X"),
+ (0x14400, "V"),
+ (0x14647, "X"),
+ (0x16800, "V"),
+ (0x16A39, "X"),
+ (0x16A40, "V"),
+ (0x16A5F, "X"),
+ (0x16A60, "V"),
+ (0x16A6A, "X"),
+ (0x16A6E, "V"),
+ (0x16ABF, "X"),
+ (0x16AC0, "V"),
+ (0x16ACA, "X"),
+ (0x16AD0, "V"),
+ (0x16AEE, "X"),
+ (0x16AF0, "V"),
+ (0x16AF6, "X"),
+ (0x16B00, "V"),
+ (0x16B46, "X"),
+ (0x16B50, "V"),
+ (0x16B5A, "X"),
+ (0x16B5B, "V"),
+ (0x16B62, "X"),
+ (0x16B63, "V"),
+ (0x16B78, "X"),
+ (0x16B7D, "V"),
+ (0x16B90, "X"),
+ (0x16E40, "M", "𖹠"),
+ (0x16E41, "M", "𖹡"),
+ (0x16E42, "M", "𖹢"),
+ (0x16E43, "M", "𖹣"),
+ (0x16E44, "M", "𖹤"),
+ (0x16E45, "M", "𖹥"),
+ (0x16E46, "M", "𖹦"),
+ (0x16E47, "M", "𖹧"),
+ (0x16E48, "M", "𖹨"),
+ (0x16E49, "M", "𖹩"),
+ (0x16E4A, "M", "𖹪"),
+ (0x16E4B, "M", "𖹫"),
+ (0x16E4C, "M", "𖹬"),
+ (0x16E4D, "M", "𖹭"),
+ (0x16E4E, "M", "𖹮"),
+ (0x16E4F, "M", "𖹯"),
+ (0x16E50, "M", "𖹰"),
+ (0x16E51, "M", "𖹱"),
+ (0x16E52, "M", "𖹲"),
+ (0x16E53, "M", "𖹳"),
+ (0x16E54, "M", "𖹴"),
+ (0x16E55, "M", "𖹵"),
+ (0x16E56, "M", "𖹶"),
+ (0x16E57, "M", "𖹷"),
+ (0x16E58, "M", "𖹸"),
+ (0x16E59, "M", "𖹹"),
+ (0x16E5A, "M", "𖹺"),
+ (0x16E5B, "M", "𖹻"),
+ (0x16E5C, "M", "𖹼"),
+ (0x16E5D, "M", "𖹽"),
+ (0x16E5E, "M", "𖹾"),
+ (0x16E5F, "M", "𖹿"),
+ (0x16E60, "V"),
+ (0x16E9B, "X"),
+ (0x16F00, "V"),
+ (0x16F4B, "X"),
+ (0x16F4F, "V"),
+ (0x16F88, "X"),
+ (0x16F8F, "V"),
+ (0x16FA0, "X"),
+ (0x16FE0, "V"),
+ (0x16FE5, "X"),
+ (0x16FF0, "V"),
+ (0x16FF2, "X"),
+ (0x17000, "V"),
+ (0x187F8, "X"),
+ (0x18800, "V"),
+ (0x18CD6, "X"),
+ (0x18D00, "V"),
+ (0x18D09, "X"),
+ (0x1AFF0, "V"),
+ (0x1AFF4, "X"),
+ (0x1AFF5, "V"),
+ (0x1AFFC, "X"),
+ (0x1AFFD, "V"),
+ ]
+
+
+def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1AFFF, "X"),
+ (0x1B000, "V"),
+ (0x1B123, "X"),
+ (0x1B132, "V"),
+ (0x1B133, "X"),
+ (0x1B150, "V"),
+ (0x1B153, "X"),
+ (0x1B155, "V"),
+ (0x1B156, "X"),
+ (0x1B164, "V"),
+ (0x1B168, "X"),
+ (0x1B170, "V"),
+ (0x1B2FC, "X"),
+ (0x1BC00, "V"),
+ (0x1BC6B, "X"),
+ (0x1BC70, "V"),
+ (0x1BC7D, "X"),
+ (0x1BC80, "V"),
+ (0x1BC89, "X"),
+ (0x1BC90, "V"),
+ (0x1BC9A, "X"),
+ (0x1BC9C, "V"),
+ (0x1BCA0, "I"),
+ (0x1BCA4, "X"),
+ (0x1CF00, "V"),
+ (0x1CF2E, "X"),
+ (0x1CF30, "V"),
+ (0x1CF47, "X"),
+ (0x1CF50, "V"),
+ (0x1CFC4, "X"),
+ (0x1D000, "V"),
+ (0x1D0F6, "X"),
+ (0x1D100, "V"),
+ (0x1D127, "X"),
+ (0x1D129, "V"),
+ (0x1D15E, "M", "𝅗𝅥"),
+ (0x1D15F, "M", "𝅘𝅥"),
+ (0x1D160, "M", "𝅘𝅥𝅮"),
+ (0x1D161, "M", "𝅘𝅥𝅯"),
+ (0x1D162, "M", "𝅘𝅥𝅰"),
+ (0x1D163, "M", "𝅘𝅥𝅱"),
+ (0x1D164, "M", "𝅘𝅥𝅲"),
+ (0x1D165, "V"),
+ (0x1D173, "X"),
+ (0x1D17B, "V"),
+ (0x1D1BB, "M", "𝆹𝅥"),
+ (0x1D1BC, "M", "𝆺𝅥"),
+ (0x1D1BD, "M", "𝆹𝅥𝅮"),
+ (0x1D1BE, "M", "𝆺𝅥𝅮"),
+ (0x1D1BF, "M", "𝆹𝅥𝅯"),
+ (0x1D1C0, "M", "𝆺𝅥𝅯"),
+ (0x1D1C1, "V"),
+ (0x1D1EB, "X"),
+ (0x1D200, "V"),
+ (0x1D246, "X"),
+ (0x1D2C0, "V"),
+ (0x1D2D4, "X"),
+ (0x1D2E0, "V"),
+ (0x1D2F4, "X"),
+ (0x1D300, "V"),
+ (0x1D357, "X"),
+ (0x1D360, "V"),
+ (0x1D379, "X"),
+ (0x1D400, "M", "a"),
+ (0x1D401, "M", "b"),
+ (0x1D402, "M", "c"),
+ (0x1D403, "M", "d"),
+ (0x1D404, "M", "e"),
+ (0x1D405, "M", "f"),
+ (0x1D406, "M", "g"),
+ (0x1D407, "M", "h"),
+ (0x1D408, "M", "i"),
+ (0x1D409, "M", "j"),
+ (0x1D40A, "M", "k"),
+ (0x1D40B, "M", "l"),
+ (0x1D40C, "M", "m"),
+ (0x1D40D, "M", "n"),
+ (0x1D40E, "M", "o"),
+ (0x1D40F, "M", "p"),
+ (0x1D410, "M", "q"),
+ (0x1D411, "M", "r"),
+ (0x1D412, "M", "s"),
+ (0x1D413, "M", "t"),
+ (0x1D414, "M", "u"),
+ (0x1D415, "M", "v"),
+ (0x1D416, "M", "w"),
+ (0x1D417, "M", "x"),
+ (0x1D418, "M", "y"),
+ (0x1D419, "M", "z"),
+ (0x1D41A, "M", "a"),
+ (0x1D41B, "M", "b"),
+ (0x1D41C, "M", "c"),
+ (0x1D41D, "M", "d"),
+ (0x1D41E, "M", "e"),
+ (0x1D41F, "M", "f"),
+ (0x1D420, "M", "g"),
+ (0x1D421, "M", "h"),
+ (0x1D422, "M", "i"),
+ (0x1D423, "M", "j"),
+ (0x1D424, "M", "k"),
+ ]
+
+
+def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D425, "M", "l"),
+ (0x1D426, "M", "m"),
+ (0x1D427, "M", "n"),
+ (0x1D428, "M", "o"),
+ (0x1D429, "M", "p"),
+ (0x1D42A, "M", "q"),
+ (0x1D42B, "M", "r"),
+ (0x1D42C, "M", "s"),
+ (0x1D42D, "M", "t"),
+ (0x1D42E, "M", "u"),
+ (0x1D42F, "M", "v"),
+ (0x1D430, "M", "w"),
+ (0x1D431, "M", "x"),
+ (0x1D432, "M", "y"),
+ (0x1D433, "M", "z"),
+ (0x1D434, "M", "a"),
+ (0x1D435, "M", "b"),
+ (0x1D436, "M", "c"),
+ (0x1D437, "M", "d"),
+ (0x1D438, "M", "e"),
+ (0x1D439, "M", "f"),
+ (0x1D43A, "M", "g"),
+ (0x1D43B, "M", "h"),
+ (0x1D43C, "M", "i"),
+ (0x1D43D, "M", "j"),
+ (0x1D43E, "M", "k"),
+ (0x1D43F, "M", "l"),
+ (0x1D440, "M", "m"),
+ (0x1D441, "M", "n"),
+ (0x1D442, "M", "o"),
+ (0x1D443, "M", "p"),
+ (0x1D444, "M", "q"),
+ (0x1D445, "M", "r"),
+ (0x1D446, "M", "s"),
+ (0x1D447, "M", "t"),
+ (0x1D448, "M", "u"),
+ (0x1D449, "M", "v"),
+ (0x1D44A, "M", "w"),
+ (0x1D44B, "M", "x"),
+ (0x1D44C, "M", "y"),
+ (0x1D44D, "M", "z"),
+ (0x1D44E, "M", "a"),
+ (0x1D44F, "M", "b"),
+ (0x1D450, "M", "c"),
+ (0x1D451, "M", "d"),
+ (0x1D452, "M", "e"),
+ (0x1D453, "M", "f"),
+ (0x1D454, "M", "g"),
+ (0x1D455, "X"),
+ (0x1D456, "M", "i"),
+ (0x1D457, "M", "j"),
+ (0x1D458, "M", "k"),
+ (0x1D459, "M", "l"),
+ (0x1D45A, "M", "m"),
+ (0x1D45B, "M", "n"),
+ (0x1D45C, "M", "o"),
+ (0x1D45D, "M", "p"),
+ (0x1D45E, "M", "q"),
+ (0x1D45F, "M", "r"),
+ (0x1D460, "M", "s"),
+ (0x1D461, "M", "t"),
+ (0x1D462, "M", "u"),
+ (0x1D463, "M", "v"),
+ (0x1D464, "M", "w"),
+ (0x1D465, "M", "x"),
+ (0x1D466, "M", "y"),
+ (0x1D467, "M", "z"),
+ (0x1D468, "M", "a"),
+ (0x1D469, "M", "b"),
+ (0x1D46A, "M", "c"),
+ (0x1D46B, "M", "d"),
+ (0x1D46C, "M", "e"),
+ (0x1D46D, "M", "f"),
+ (0x1D46E, "M", "g"),
+ (0x1D46F, "M", "h"),
+ (0x1D470, "M", "i"),
+ (0x1D471, "M", "j"),
+ (0x1D472, "M", "k"),
+ (0x1D473, "M", "l"),
+ (0x1D474, "M", "m"),
+ (0x1D475, "M", "n"),
+ (0x1D476, "M", "o"),
+ (0x1D477, "M", "p"),
+ (0x1D478, "M", "q"),
+ (0x1D479, "M", "r"),
+ (0x1D47A, "M", "s"),
+ (0x1D47B, "M", "t"),
+ (0x1D47C, "M", "u"),
+ (0x1D47D, "M", "v"),
+ (0x1D47E, "M", "w"),
+ (0x1D47F, "M", "x"),
+ (0x1D480, "M", "y"),
+ (0x1D481, "M", "z"),
+ (0x1D482, "M", "a"),
+ (0x1D483, "M", "b"),
+ (0x1D484, "M", "c"),
+ (0x1D485, "M", "d"),
+ (0x1D486, "M", "e"),
+ (0x1D487, "M", "f"),
+ (0x1D488, "M", "g"),
+ ]
+
+
+def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D489, "M", "h"),
+ (0x1D48A, "M", "i"),
+ (0x1D48B, "M", "j"),
+ (0x1D48C, "M", "k"),
+ (0x1D48D, "M", "l"),
+ (0x1D48E, "M", "m"),
+ (0x1D48F, "M", "n"),
+ (0x1D490, "M", "o"),
+ (0x1D491, "M", "p"),
+ (0x1D492, "M", "q"),
+ (0x1D493, "M", "r"),
+ (0x1D494, "M", "s"),
+ (0x1D495, "M", "t"),
+ (0x1D496, "M", "u"),
+ (0x1D497, "M", "v"),
+ (0x1D498, "M", "w"),
+ (0x1D499, "M", "x"),
+ (0x1D49A, "M", "y"),
+ (0x1D49B, "M", "z"),
+ (0x1D49C, "M", "a"),
+ (0x1D49D, "X"),
+ (0x1D49E, "M", "c"),
+ (0x1D49F, "M", "d"),
+ (0x1D4A0, "X"),
+ (0x1D4A2, "M", "g"),
+ (0x1D4A3, "X"),
+ (0x1D4A5, "M", "j"),
+ (0x1D4A6, "M", "k"),
+ (0x1D4A7, "X"),
+ (0x1D4A9, "M", "n"),
+ (0x1D4AA, "M", "o"),
+ (0x1D4AB, "M", "p"),
+ (0x1D4AC, "M", "q"),
+ (0x1D4AD, "X"),
+ (0x1D4AE, "M", "s"),
+ (0x1D4AF, "M", "t"),
+ (0x1D4B0, "M", "u"),
+ (0x1D4B1, "M", "v"),
+ (0x1D4B2, "M", "w"),
+ (0x1D4B3, "M", "x"),
+ (0x1D4B4, "M", "y"),
+ (0x1D4B5, "M", "z"),
+ (0x1D4B6, "M", "a"),
+ (0x1D4B7, "M", "b"),
+ (0x1D4B8, "M", "c"),
+ (0x1D4B9, "M", "d"),
+ (0x1D4BA, "X"),
+ (0x1D4BB, "M", "f"),
+ (0x1D4BC, "X"),
+ (0x1D4BD, "M", "h"),
+ (0x1D4BE, "M", "i"),
+ (0x1D4BF, "M", "j"),
+ (0x1D4C0, "M", "k"),
+ (0x1D4C1, "M", "l"),
+ (0x1D4C2, "M", "m"),
+ (0x1D4C3, "M", "n"),
+ (0x1D4C4, "X"),
+ (0x1D4C5, "M", "p"),
+ (0x1D4C6, "M", "q"),
+ (0x1D4C7, "M", "r"),
+ (0x1D4C8, "M", "s"),
+ (0x1D4C9, "M", "t"),
+ (0x1D4CA, "M", "u"),
+ (0x1D4CB, "M", "v"),
+ (0x1D4CC, "M", "w"),
+ (0x1D4CD, "M", "x"),
+ (0x1D4CE, "M", "y"),
+ (0x1D4CF, "M", "z"),
+ (0x1D4D0, "M", "a"),
+ (0x1D4D1, "M", "b"),
+ (0x1D4D2, "M", "c"),
+ (0x1D4D3, "M", "d"),
+ (0x1D4D4, "M", "e"),
+ (0x1D4D5, "M", "f"),
+ (0x1D4D6, "M", "g"),
+ (0x1D4D7, "M", "h"),
+ (0x1D4D8, "M", "i"),
+ (0x1D4D9, "M", "j"),
+ (0x1D4DA, "M", "k"),
+ (0x1D4DB, "M", "l"),
+ (0x1D4DC, "M", "m"),
+ (0x1D4DD, "M", "n"),
+ (0x1D4DE, "M", "o"),
+ (0x1D4DF, "M", "p"),
+ (0x1D4E0, "M", "q"),
+ (0x1D4E1, "M", "r"),
+ (0x1D4E2, "M", "s"),
+ (0x1D4E3, "M", "t"),
+ (0x1D4E4, "M", "u"),
+ (0x1D4E5, "M", "v"),
+ (0x1D4E6, "M", "w"),
+ (0x1D4E7, "M", "x"),
+ (0x1D4E8, "M", "y"),
+ (0x1D4E9, "M", "z"),
+ (0x1D4EA, "M", "a"),
+ (0x1D4EB, "M", "b"),
+ (0x1D4EC, "M", "c"),
+ (0x1D4ED, "M", "d"),
+ (0x1D4EE, "M", "e"),
+ (0x1D4EF, "M", "f"),
+ ]
+
+
+def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D4F0, "M", "g"),
+ (0x1D4F1, "M", "h"),
+ (0x1D4F2, "M", "i"),
+ (0x1D4F3, "M", "j"),
+ (0x1D4F4, "M", "k"),
+ (0x1D4F5, "M", "l"),
+ (0x1D4F6, "M", "m"),
+ (0x1D4F7, "M", "n"),
+ (0x1D4F8, "M", "o"),
+ (0x1D4F9, "M", "p"),
+ (0x1D4FA, "M", "q"),
+ (0x1D4FB, "M", "r"),
+ (0x1D4FC, "M", "s"),
+ (0x1D4FD, "M", "t"),
+ (0x1D4FE, "M", "u"),
+ (0x1D4FF, "M", "v"),
+ (0x1D500, "M", "w"),
+ (0x1D501, "M", "x"),
+ (0x1D502, "M", "y"),
+ (0x1D503, "M", "z"),
+ (0x1D504, "M", "a"),
+ (0x1D505, "M", "b"),
+ (0x1D506, "X"),
+ (0x1D507, "M", "d"),
+ (0x1D508, "M", "e"),
+ (0x1D509, "M", "f"),
+ (0x1D50A, "M", "g"),
+ (0x1D50B, "X"),
+ (0x1D50D, "M", "j"),
+ (0x1D50E, "M", "k"),
+ (0x1D50F, "M", "l"),
+ (0x1D510, "M", "m"),
+ (0x1D511, "M", "n"),
+ (0x1D512, "M", "o"),
+ (0x1D513, "M", "p"),
+ (0x1D514, "M", "q"),
+ (0x1D515, "X"),
+ (0x1D516, "M", "s"),
+ (0x1D517, "M", "t"),
+ (0x1D518, "M", "u"),
+ (0x1D519, "M", "v"),
+ (0x1D51A, "M", "w"),
+ (0x1D51B, "M", "x"),
+ (0x1D51C, "M", "y"),
+ (0x1D51D, "X"),
+ (0x1D51E, "M", "a"),
+ (0x1D51F, "M", "b"),
+ (0x1D520, "M", "c"),
+ (0x1D521, "M", "d"),
+ (0x1D522, "M", "e"),
+ (0x1D523, "M", "f"),
+ (0x1D524, "M", "g"),
+ (0x1D525, "M", "h"),
+ (0x1D526, "M", "i"),
+ (0x1D527, "M", "j"),
+ (0x1D528, "M", "k"),
+ (0x1D529, "M", "l"),
+ (0x1D52A, "M", "m"),
+ (0x1D52B, "M", "n"),
+ (0x1D52C, "M", "o"),
+ (0x1D52D, "M", "p"),
+ (0x1D52E, "M", "q"),
+ (0x1D52F, "M", "r"),
+ (0x1D530, "M", "s"),
+ (0x1D531, "M", "t"),
+ (0x1D532, "M", "u"),
+ (0x1D533, "M", "v"),
+ (0x1D534, "M", "w"),
+ (0x1D535, "M", "x"),
+ (0x1D536, "M", "y"),
+ (0x1D537, "M", "z"),
+ (0x1D538, "M", "a"),
+ (0x1D539, "M", "b"),
+ (0x1D53A, "X"),
+ (0x1D53B, "M", "d"),
+ (0x1D53C, "M", "e"),
+ (0x1D53D, "M", "f"),
+ (0x1D53E, "M", "g"),
+ (0x1D53F, "X"),
+ (0x1D540, "M", "i"),
+ (0x1D541, "M", "j"),
+ (0x1D542, "M", "k"),
+ (0x1D543, "M", "l"),
+ (0x1D544, "M", "m"),
+ (0x1D545, "X"),
+ (0x1D546, "M", "o"),
+ (0x1D547, "X"),
+ (0x1D54A, "M", "s"),
+ (0x1D54B, "M", "t"),
+ (0x1D54C, "M", "u"),
+ (0x1D54D, "M", "v"),
+ (0x1D54E, "M", "w"),
+ (0x1D54F, "M", "x"),
+ (0x1D550, "M", "y"),
+ (0x1D551, "X"),
+ (0x1D552, "M", "a"),
+ (0x1D553, "M", "b"),
+ (0x1D554, "M", "c"),
+ (0x1D555, "M", "d"),
+ (0x1D556, "M", "e"),
+ ]
+
+
+def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D557, "M", "f"),
+ (0x1D558, "M", "g"),
+ (0x1D559, "M", "h"),
+ (0x1D55A, "M", "i"),
+ (0x1D55B, "M", "j"),
+ (0x1D55C, "M", "k"),
+ (0x1D55D, "M", "l"),
+ (0x1D55E, "M", "m"),
+ (0x1D55F, "M", "n"),
+ (0x1D560, "M", "o"),
+ (0x1D561, "M", "p"),
+ (0x1D562, "M", "q"),
+ (0x1D563, "M", "r"),
+ (0x1D564, "M", "s"),
+ (0x1D565, "M", "t"),
+ (0x1D566, "M", "u"),
+ (0x1D567, "M", "v"),
+ (0x1D568, "M", "w"),
+ (0x1D569, "M", "x"),
+ (0x1D56A, "M", "y"),
+ (0x1D56B, "M", "z"),
+ (0x1D56C, "M", "a"),
+ (0x1D56D, "M", "b"),
+ (0x1D56E, "M", "c"),
+ (0x1D56F, "M", "d"),
+ (0x1D570, "M", "e"),
+ (0x1D571, "M", "f"),
+ (0x1D572, "M", "g"),
+ (0x1D573, "M", "h"),
+ (0x1D574, "M", "i"),
+ (0x1D575, "M", "j"),
+ (0x1D576, "M", "k"),
+ (0x1D577, "M", "l"),
+ (0x1D578, "M", "m"),
+ (0x1D579, "M", "n"),
+ (0x1D57A, "M", "o"),
+ (0x1D57B, "M", "p"),
+ (0x1D57C, "M", "q"),
+ (0x1D57D, "M", "r"),
+ (0x1D57E, "M", "s"),
+ (0x1D57F, "M", "t"),
+ (0x1D580, "M", "u"),
+ (0x1D581, "M", "v"),
+ (0x1D582, "M", "w"),
+ (0x1D583, "M", "x"),
+ (0x1D584, "M", "y"),
+ (0x1D585, "M", "z"),
+ (0x1D586, "M", "a"),
+ (0x1D587, "M", "b"),
+ (0x1D588, "M", "c"),
+ (0x1D589, "M", "d"),
+ (0x1D58A, "M", "e"),
+ (0x1D58B, "M", "f"),
+ (0x1D58C, "M", "g"),
+ (0x1D58D, "M", "h"),
+ (0x1D58E, "M", "i"),
+ (0x1D58F, "M", "j"),
+ (0x1D590, "M", "k"),
+ (0x1D591, "M", "l"),
+ (0x1D592, "M", "m"),
+ (0x1D593, "M", "n"),
+ (0x1D594, "M", "o"),
+ (0x1D595, "M", "p"),
+ (0x1D596, "M", "q"),
+ (0x1D597, "M", "r"),
+ (0x1D598, "M", "s"),
+ (0x1D599, "M", "t"),
+ (0x1D59A, "M", "u"),
+ (0x1D59B, "M", "v"),
+ (0x1D59C, "M", "w"),
+ (0x1D59D, "M", "x"),
+ (0x1D59E, "M", "y"),
+ (0x1D59F, "M", "z"),
+ (0x1D5A0, "M", "a"),
+ (0x1D5A1, "M", "b"),
+ (0x1D5A2, "M", "c"),
+ (0x1D5A3, "M", "d"),
+ (0x1D5A4, "M", "e"),
+ (0x1D5A5, "M", "f"),
+ (0x1D5A6, "M", "g"),
+ (0x1D5A7, "M", "h"),
+ (0x1D5A8, "M", "i"),
+ (0x1D5A9, "M", "j"),
+ (0x1D5AA, "M", "k"),
+ (0x1D5AB, "M", "l"),
+ (0x1D5AC, "M", "m"),
+ (0x1D5AD, "M", "n"),
+ (0x1D5AE, "M", "o"),
+ (0x1D5AF, "M", "p"),
+ (0x1D5B0, "M", "q"),
+ (0x1D5B1, "M", "r"),
+ (0x1D5B2, "M", "s"),
+ (0x1D5B3, "M", "t"),
+ (0x1D5B4, "M", "u"),
+ (0x1D5B5, "M", "v"),
+ (0x1D5B6, "M", "w"),
+ (0x1D5B7, "M", "x"),
+ (0x1D5B8, "M", "y"),
+ (0x1D5B9, "M", "z"),
+ (0x1D5BA, "M", "a"),
+ ]
+
+
+def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D5BB, "M", "b"),
+ (0x1D5BC, "M", "c"),
+ (0x1D5BD, "M", "d"),
+ (0x1D5BE, "M", "e"),
+ (0x1D5BF, "M", "f"),
+ (0x1D5C0, "M", "g"),
+ (0x1D5C1, "M", "h"),
+ (0x1D5C2, "M", "i"),
+ (0x1D5C3, "M", "j"),
+ (0x1D5C4, "M", "k"),
+ (0x1D5C5, "M", "l"),
+ (0x1D5C6, "M", "m"),
+ (0x1D5C7, "M", "n"),
+ (0x1D5C8, "M", "o"),
+ (0x1D5C9, "M", "p"),
+ (0x1D5CA, "M", "q"),
+ (0x1D5CB, "M", "r"),
+ (0x1D5CC, "M", "s"),
+ (0x1D5CD, "M", "t"),
+ (0x1D5CE, "M", "u"),
+ (0x1D5CF, "M", "v"),
+ (0x1D5D0, "M", "w"),
+ (0x1D5D1, "M", "x"),
+ (0x1D5D2, "M", "y"),
+ (0x1D5D3, "M", "z"),
+ (0x1D5D4, "M", "a"),
+ (0x1D5D5, "M", "b"),
+ (0x1D5D6, "M", "c"),
+ (0x1D5D7, "M", "d"),
+ (0x1D5D8, "M", "e"),
+ (0x1D5D9, "M", "f"),
+ (0x1D5DA, "M", "g"),
+ (0x1D5DB, "M", "h"),
+ (0x1D5DC, "M", "i"),
+ (0x1D5DD, "M", "j"),
+ (0x1D5DE, "M", "k"),
+ (0x1D5DF, "M", "l"),
+ (0x1D5E0, "M", "m"),
+ (0x1D5E1, "M", "n"),
+ (0x1D5E2, "M", "o"),
+ (0x1D5E3, "M", "p"),
+ (0x1D5E4, "M", "q"),
+ (0x1D5E5, "M", "r"),
+ (0x1D5E6, "M", "s"),
+ (0x1D5E7, "M", "t"),
+ (0x1D5E8, "M", "u"),
+ (0x1D5E9, "M", "v"),
+ (0x1D5EA, "M", "w"),
+ (0x1D5EB, "M", "x"),
+ (0x1D5EC, "M", "y"),
+ (0x1D5ED, "M", "z"),
+ (0x1D5EE, "M", "a"),
+ (0x1D5EF, "M", "b"),
+ (0x1D5F0, "M", "c"),
+ (0x1D5F1, "M", "d"),
+ (0x1D5F2, "M", "e"),
+ (0x1D5F3, "M", "f"),
+ (0x1D5F4, "M", "g"),
+ (0x1D5F5, "M", "h"),
+ (0x1D5F6, "M", "i"),
+ (0x1D5F7, "M", "j"),
+ (0x1D5F8, "M", "k"),
+ (0x1D5F9, "M", "l"),
+ (0x1D5FA, "M", "m"),
+ (0x1D5FB, "M", "n"),
+ (0x1D5FC, "M", "o"),
+ (0x1D5FD, "M", "p"),
+ (0x1D5FE, "M", "q"),
+ (0x1D5FF, "M", "r"),
+ (0x1D600, "M", "s"),
+ (0x1D601, "M", "t"),
+ (0x1D602, "M", "u"),
+ (0x1D603, "M", "v"),
+ (0x1D604, "M", "w"),
+ (0x1D605, "M", "x"),
+ (0x1D606, "M", "y"),
+ (0x1D607, "M", "z"),
+ (0x1D608, "M", "a"),
+ (0x1D609, "M", "b"),
+ (0x1D60A, "M", "c"),
+ (0x1D60B, "M", "d"),
+ (0x1D60C, "M", "e"),
+ (0x1D60D, "M", "f"),
+ (0x1D60E, "M", "g"),
+ (0x1D60F, "M", "h"),
+ (0x1D610, "M", "i"),
+ (0x1D611, "M", "j"),
+ (0x1D612, "M", "k"),
+ (0x1D613, "M", "l"),
+ (0x1D614, "M", "m"),
+ (0x1D615, "M", "n"),
+ (0x1D616, "M", "o"),
+ (0x1D617, "M", "p"),
+ (0x1D618, "M", "q"),
+ (0x1D619, "M", "r"),
+ (0x1D61A, "M", "s"),
+ (0x1D61B, "M", "t"),
+ (0x1D61C, "M", "u"),
+ (0x1D61D, "M", "v"),
+ (0x1D61E, "M", "w"),
+ ]
+
+
+def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D61F, "M", "x"),
+ (0x1D620, "M", "y"),
+ (0x1D621, "M", "z"),
+ (0x1D622, "M", "a"),
+ (0x1D623, "M", "b"),
+ (0x1D624, "M", "c"),
+ (0x1D625, "M", "d"),
+ (0x1D626, "M", "e"),
+ (0x1D627, "M", "f"),
+ (0x1D628, "M", "g"),
+ (0x1D629, "M", "h"),
+ (0x1D62A, "M", "i"),
+ (0x1D62B, "M", "j"),
+ (0x1D62C, "M", "k"),
+ (0x1D62D, "M", "l"),
+ (0x1D62E, "M", "m"),
+ (0x1D62F, "M", "n"),
+ (0x1D630, "M", "o"),
+ (0x1D631, "M", "p"),
+ (0x1D632, "M", "q"),
+ (0x1D633, "M", "r"),
+ (0x1D634, "M", "s"),
+ (0x1D635, "M", "t"),
+ (0x1D636, "M", "u"),
+ (0x1D637, "M", "v"),
+ (0x1D638, "M", "w"),
+ (0x1D639, "M", "x"),
+ (0x1D63A, "M", "y"),
+ (0x1D63B, "M", "z"),
+ (0x1D63C, "M", "a"),
+ (0x1D63D, "M", "b"),
+ (0x1D63E, "M", "c"),
+ (0x1D63F, "M", "d"),
+ (0x1D640, "M", "e"),
+ (0x1D641, "M", "f"),
+ (0x1D642, "M", "g"),
+ (0x1D643, "M", "h"),
+ (0x1D644, "M", "i"),
+ (0x1D645, "M", "j"),
+ (0x1D646, "M", "k"),
+ (0x1D647, "M", "l"),
+ (0x1D648, "M", "m"),
+ (0x1D649, "M", "n"),
+ (0x1D64A, "M", "o"),
+ (0x1D64B, "M", "p"),
+ (0x1D64C, "M", "q"),
+ (0x1D64D, "M", "r"),
+ (0x1D64E, "M", "s"),
+ (0x1D64F, "M", "t"),
+ (0x1D650, "M", "u"),
+ (0x1D651, "M", "v"),
+ (0x1D652, "M", "w"),
+ (0x1D653, "M", "x"),
+ (0x1D654, "M", "y"),
+ (0x1D655, "M", "z"),
+ (0x1D656, "M", "a"),
+ (0x1D657, "M", "b"),
+ (0x1D658, "M", "c"),
+ (0x1D659, "M", "d"),
+ (0x1D65A, "M", "e"),
+ (0x1D65B, "M", "f"),
+ (0x1D65C, "M", "g"),
+ (0x1D65D, "M", "h"),
+ (0x1D65E, "M", "i"),
+ (0x1D65F, "M", "j"),
+ (0x1D660, "M", "k"),
+ (0x1D661, "M", "l"),
+ (0x1D662, "M", "m"),
+ (0x1D663, "M", "n"),
+ (0x1D664, "M", "o"),
+ (0x1D665, "M", "p"),
+ (0x1D666, "M", "q"),
+ (0x1D667, "M", "r"),
+ (0x1D668, "M", "s"),
+ (0x1D669, "M", "t"),
+ (0x1D66A, "M", "u"),
+ (0x1D66B, "M", "v"),
+ (0x1D66C, "M", "w"),
+ (0x1D66D, "M", "x"),
+ (0x1D66E, "M", "y"),
+ (0x1D66F, "M", "z"),
+ (0x1D670, "M", "a"),
+ (0x1D671, "M", "b"),
+ (0x1D672, "M", "c"),
+ (0x1D673, "M", "d"),
+ (0x1D674, "M", "e"),
+ (0x1D675, "M", "f"),
+ (0x1D676, "M", "g"),
+ (0x1D677, "M", "h"),
+ (0x1D678, "M", "i"),
+ (0x1D679, "M", "j"),
+ (0x1D67A, "M", "k"),
+ (0x1D67B, "M", "l"),
+ (0x1D67C, "M", "m"),
+ (0x1D67D, "M", "n"),
+ (0x1D67E, "M", "o"),
+ (0x1D67F, "M", "p"),
+ (0x1D680, "M", "q"),
+ (0x1D681, "M", "r"),
+ (0x1D682, "M", "s"),
+ ]
+
+
+def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D683, "M", "t"),
+ (0x1D684, "M", "u"),
+ (0x1D685, "M", "v"),
+ (0x1D686, "M", "w"),
+ (0x1D687, "M", "x"),
+ (0x1D688, "M", "y"),
+ (0x1D689, "M", "z"),
+ (0x1D68A, "M", "a"),
+ (0x1D68B, "M", "b"),
+ (0x1D68C, "M", "c"),
+ (0x1D68D, "M", "d"),
+ (0x1D68E, "M", "e"),
+ (0x1D68F, "M", "f"),
+ (0x1D690, "M", "g"),
+ (0x1D691, "M", "h"),
+ (0x1D692, "M", "i"),
+ (0x1D693, "M", "j"),
+ (0x1D694, "M", "k"),
+ (0x1D695, "M", "l"),
+ (0x1D696, "M", "m"),
+ (0x1D697, "M", "n"),
+ (0x1D698, "M", "o"),
+ (0x1D699, "M", "p"),
+ (0x1D69A, "M", "q"),
+ (0x1D69B, "M", "r"),
+ (0x1D69C, "M", "s"),
+ (0x1D69D, "M", "t"),
+ (0x1D69E, "M", "u"),
+ (0x1D69F, "M", "v"),
+ (0x1D6A0, "M", "w"),
+ (0x1D6A1, "M", "x"),
+ (0x1D6A2, "M", "y"),
+ (0x1D6A3, "M", "z"),
+ (0x1D6A4, "M", "ı"),
+ (0x1D6A5, "M", "ȷ"),
+ (0x1D6A6, "X"),
+ (0x1D6A8, "M", "α"),
+ (0x1D6A9, "M", "β"),
+ (0x1D6AA, "M", "γ"),
+ (0x1D6AB, "M", "δ"),
+ (0x1D6AC, "M", "ε"),
+ (0x1D6AD, "M", "ζ"),
+ (0x1D6AE, "M", "η"),
+ (0x1D6AF, "M", "θ"),
+ (0x1D6B0, "M", "ι"),
+ (0x1D6B1, "M", "κ"),
+ (0x1D6B2, "M", "λ"),
+ (0x1D6B3, "M", "μ"),
+ (0x1D6B4, "M", "ν"),
+ (0x1D6B5, "M", "ξ"),
+ (0x1D6B6, "M", "ο"),
+ (0x1D6B7, "M", "π"),
+ (0x1D6B8, "M", "ρ"),
+ (0x1D6B9, "M", "θ"),
+ (0x1D6BA, "M", "σ"),
+ (0x1D6BB, "M", "τ"),
+ (0x1D6BC, "M", "υ"),
+ (0x1D6BD, "M", "φ"),
+ (0x1D6BE, "M", "χ"),
+ (0x1D6BF, "M", "ψ"),
+ (0x1D6C0, "M", "ω"),
+ (0x1D6C1, "M", "∇"),
+ (0x1D6C2, "M", "α"),
+ (0x1D6C3, "M", "β"),
+ (0x1D6C4, "M", "γ"),
+ (0x1D6C5, "M", "δ"),
+ (0x1D6C6, "M", "ε"),
+ (0x1D6C7, "M", "ζ"),
+ (0x1D6C8, "M", "η"),
+ (0x1D6C9, "M", "θ"),
+ (0x1D6CA, "M", "ι"),
+ (0x1D6CB, "M", "κ"),
+ (0x1D6CC, "M", "λ"),
+ (0x1D6CD, "M", "μ"),
+ (0x1D6CE, "M", "ν"),
+ (0x1D6CF, "M", "ξ"),
+ (0x1D6D0, "M", "ο"),
+ (0x1D6D1, "M", "π"),
+ (0x1D6D2, "M", "ρ"),
+ (0x1D6D3, "M", "σ"),
+ (0x1D6D5, "M", "τ"),
+ (0x1D6D6, "M", "υ"),
+ (0x1D6D7, "M", "φ"),
+ (0x1D6D8, "M", "χ"),
+ (0x1D6D9, "M", "ψ"),
+ (0x1D6DA, "M", "ω"),
+ (0x1D6DB, "M", "∂"),
+ (0x1D6DC, "M", "ε"),
+ (0x1D6DD, "M", "θ"),
+ (0x1D6DE, "M", "κ"),
+ (0x1D6DF, "M", "φ"),
+ (0x1D6E0, "M", "ρ"),
+ (0x1D6E1, "M", "π"),
+ (0x1D6E2, "M", "α"),
+ (0x1D6E3, "M", "β"),
+ (0x1D6E4, "M", "γ"),
+ (0x1D6E5, "M", "δ"),
+ (0x1D6E6, "M", "ε"),
+ (0x1D6E7, "M", "ζ"),
+ (0x1D6E8, "M", "η"),
+ ]
+
+
+def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D6E9, "M", "θ"),
+ (0x1D6EA, "M", "ι"),
+ (0x1D6EB, "M", "κ"),
+ (0x1D6EC, "M", "λ"),
+ (0x1D6ED, "M", "μ"),
+ (0x1D6EE, "M", "ν"),
+ (0x1D6EF, "M", "ξ"),
+ (0x1D6F0, "M", "ο"),
+ (0x1D6F1, "M", "π"),
+ (0x1D6F2, "M", "ρ"),
+ (0x1D6F3, "M", "θ"),
+ (0x1D6F4, "M", "σ"),
+ (0x1D6F5, "M", "τ"),
+ (0x1D6F6, "M", "υ"),
+ (0x1D6F7, "M", "φ"),
+ (0x1D6F8, "M", "χ"),
+ (0x1D6F9, "M", "ψ"),
+ (0x1D6FA, "M", "ω"),
+ (0x1D6FB, "M", "∇"),
+ (0x1D6FC, "M", "α"),
+ (0x1D6FD, "M", "β"),
+ (0x1D6FE, "M", "γ"),
+ (0x1D6FF, "M", "δ"),
+ (0x1D700, "M", "ε"),
+ (0x1D701, "M", "ζ"),
+ (0x1D702, "M", "η"),
+ (0x1D703, "M", "θ"),
+ (0x1D704, "M", "ι"),
+ (0x1D705, "M", "κ"),
+ (0x1D706, "M", "λ"),
+ (0x1D707, "M", "μ"),
+ (0x1D708, "M", "ν"),
+ (0x1D709, "M", "ξ"),
+ (0x1D70A, "M", "ο"),
+ (0x1D70B, "M", "π"),
+ (0x1D70C, "M", "ρ"),
+ (0x1D70D, "M", "σ"),
+ (0x1D70F, "M", "τ"),
+ (0x1D710, "M", "υ"),
+ (0x1D711, "M", "φ"),
+ (0x1D712, "M", "χ"),
+ (0x1D713, "M", "ψ"),
+ (0x1D714, "M", "ω"),
+ (0x1D715, "M", "∂"),
+ (0x1D716, "M", "ε"),
+ (0x1D717, "M", "θ"),
+ (0x1D718, "M", "κ"),
+ (0x1D719, "M", "φ"),
+ (0x1D71A, "M", "ρ"),
+ (0x1D71B, "M", "π"),
+ (0x1D71C, "M", "α"),
+ (0x1D71D, "M", "β"),
+ (0x1D71E, "M", "γ"),
+ (0x1D71F, "M", "δ"),
+ (0x1D720, "M", "ε"),
+ (0x1D721, "M", "ζ"),
+ (0x1D722, "M", "η"),
+ (0x1D723, "M", "θ"),
+ (0x1D724, "M", "ι"),
+ (0x1D725, "M", "κ"),
+ (0x1D726, "M", "λ"),
+ (0x1D727, "M", "μ"),
+ (0x1D728, "M", "ν"),
+ (0x1D729, "M", "ξ"),
+ (0x1D72A, "M", "ο"),
+ (0x1D72B, "M", "π"),
+ (0x1D72C, "M", "ρ"),
+ (0x1D72D, "M", "θ"),
+ (0x1D72E, "M", "σ"),
+ (0x1D72F, "M", "τ"),
+ (0x1D730, "M", "υ"),
+ (0x1D731, "M", "φ"),
+ (0x1D732, "M", "χ"),
+ (0x1D733, "M", "ψ"),
+ (0x1D734, "M", "ω"),
+ (0x1D735, "M", "∇"),
+ (0x1D736, "M", "α"),
+ (0x1D737, "M", "β"),
+ (0x1D738, "M", "γ"),
+ (0x1D739, "M", "δ"),
+ (0x1D73A, "M", "ε"),
+ (0x1D73B, "M", "ζ"),
+ (0x1D73C, "M", "η"),
+ (0x1D73D, "M", "θ"),
+ (0x1D73E, "M", "ι"),
+ (0x1D73F, "M", "κ"),
+ (0x1D740, "M", "λ"),
+ (0x1D741, "M", "μ"),
+ (0x1D742, "M", "ν"),
+ (0x1D743, "M", "ξ"),
+ (0x1D744, "M", "ο"),
+ (0x1D745, "M", "π"),
+ (0x1D746, "M", "ρ"),
+ (0x1D747, "M", "σ"),
+ (0x1D749, "M", "τ"),
+ (0x1D74A, "M", "υ"),
+ (0x1D74B, "M", "φ"),
+ (0x1D74C, "M", "χ"),
+ (0x1D74D, "M", "ψ"),
+ (0x1D74E, "M", "ω"),
+ ]
+
+
+def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D74F, "M", "∂"),
+ (0x1D750, "M", "ε"),
+ (0x1D751, "M", "θ"),
+ (0x1D752, "M", "κ"),
+ (0x1D753, "M", "φ"),
+ (0x1D754, "M", "ρ"),
+ (0x1D755, "M", "π"),
+ (0x1D756, "M", "α"),
+ (0x1D757, "M", "β"),
+ (0x1D758, "M", "γ"),
+ (0x1D759, "M", "δ"),
+ (0x1D75A, "M", "ε"),
+ (0x1D75B, "M", "ζ"),
+ (0x1D75C, "M", "η"),
+ (0x1D75D, "M", "θ"),
+ (0x1D75E, "M", "ι"),
+ (0x1D75F, "M", "κ"),
+ (0x1D760, "M", "λ"),
+ (0x1D761, "M", "μ"),
+ (0x1D762, "M", "ν"),
+ (0x1D763, "M", "ξ"),
+ (0x1D764, "M", "ο"),
+ (0x1D765, "M", "π"),
+ (0x1D766, "M", "ρ"),
+ (0x1D767, "M", "θ"),
+ (0x1D768, "M", "σ"),
+ (0x1D769, "M", "τ"),
+ (0x1D76A, "M", "υ"),
+ (0x1D76B, "M", "φ"),
+ (0x1D76C, "M", "χ"),
+ (0x1D76D, "M", "ψ"),
+ (0x1D76E, "M", "ω"),
+ (0x1D76F, "M", "∇"),
+ (0x1D770, "M", "α"),
+ (0x1D771, "M", "β"),
+ (0x1D772, "M", "γ"),
+ (0x1D773, "M", "δ"),
+ (0x1D774, "M", "ε"),
+ (0x1D775, "M", "ζ"),
+ (0x1D776, "M", "η"),
+ (0x1D777, "M", "θ"),
+ (0x1D778, "M", "ι"),
+ (0x1D779, "M", "κ"),
+ (0x1D77A, "M", "λ"),
+ (0x1D77B, "M", "μ"),
+ (0x1D77C, "M", "ν"),
+ (0x1D77D, "M", "ξ"),
+ (0x1D77E, "M", "ο"),
+ (0x1D77F, "M", "π"),
+ (0x1D780, "M", "ρ"),
+ (0x1D781, "M", "σ"),
+ (0x1D783, "M", "τ"),
+ (0x1D784, "M", "υ"),
+ (0x1D785, "M", "φ"),
+ (0x1D786, "M", "χ"),
+ (0x1D787, "M", "ψ"),
+ (0x1D788, "M", "ω"),
+ (0x1D789, "M", "∂"),
+ (0x1D78A, "M", "ε"),
+ (0x1D78B, "M", "θ"),
+ (0x1D78C, "M", "κ"),
+ (0x1D78D, "M", "φ"),
+ (0x1D78E, "M", "ρ"),
+ (0x1D78F, "M", "π"),
+ (0x1D790, "M", "α"),
+ (0x1D791, "M", "β"),
+ (0x1D792, "M", "γ"),
+ (0x1D793, "M", "δ"),
+ (0x1D794, "M", "ε"),
+ (0x1D795, "M", "ζ"),
+ (0x1D796, "M", "η"),
+ (0x1D797, "M", "θ"),
+ (0x1D798, "M", "ι"),
+ (0x1D799, "M", "κ"),
+ (0x1D79A, "M", "λ"),
+ (0x1D79B, "M", "μ"),
+ (0x1D79C, "M", "ν"),
+ (0x1D79D, "M", "ξ"),
+ (0x1D79E, "M", "ο"),
+ (0x1D79F, "M", "π"),
+ (0x1D7A0, "M", "ρ"),
+ (0x1D7A1, "M", "θ"),
+ (0x1D7A2, "M", "σ"),
+ (0x1D7A3, "M", "τ"),
+ (0x1D7A4, "M", "υ"),
+ (0x1D7A5, "M", "φ"),
+ (0x1D7A6, "M", "χ"),
+ (0x1D7A7, "M", "ψ"),
+ (0x1D7A8, "M", "ω"),
+ (0x1D7A9, "M", "∇"),
+ (0x1D7AA, "M", "α"),
+ (0x1D7AB, "M", "β"),
+ (0x1D7AC, "M", "γ"),
+ (0x1D7AD, "M", "δ"),
+ (0x1D7AE, "M", "ε"),
+ (0x1D7AF, "M", "ζ"),
+ (0x1D7B0, "M", "η"),
+ (0x1D7B1, "M", "θ"),
+ (0x1D7B2, "M", "ι"),
+ (0x1D7B3, "M", "κ"),
+ ]
+
+
+def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D7B4, "M", "λ"),
+ (0x1D7B5, "M", "μ"),
+ (0x1D7B6, "M", "ν"),
+ (0x1D7B7, "M", "ξ"),
+ (0x1D7B8, "M", "ο"),
+ (0x1D7B9, "M", "π"),
+ (0x1D7BA, "M", "ρ"),
+ (0x1D7BB, "M", "σ"),
+ (0x1D7BD, "M", "τ"),
+ (0x1D7BE, "M", "υ"),
+ (0x1D7BF, "M", "φ"),
+ (0x1D7C0, "M", "χ"),
+ (0x1D7C1, "M", "ψ"),
+ (0x1D7C2, "M", "ω"),
+ (0x1D7C3, "M", "∂"),
+ (0x1D7C4, "M", "ε"),
+ (0x1D7C5, "M", "θ"),
+ (0x1D7C6, "M", "κ"),
+ (0x1D7C7, "M", "φ"),
+ (0x1D7C8, "M", "ρ"),
+ (0x1D7C9, "M", "π"),
+ (0x1D7CA, "M", "ϝ"),
+ (0x1D7CC, "X"),
+ (0x1D7CE, "M", "0"),
+ (0x1D7CF, "M", "1"),
+ (0x1D7D0, "M", "2"),
+ (0x1D7D1, "M", "3"),
+ (0x1D7D2, "M", "4"),
+ (0x1D7D3, "M", "5"),
+ (0x1D7D4, "M", "6"),
+ (0x1D7D5, "M", "7"),
+ (0x1D7D6, "M", "8"),
+ (0x1D7D7, "M", "9"),
+ (0x1D7D8, "M", "0"),
+ (0x1D7D9, "M", "1"),
+ (0x1D7DA, "M", "2"),
+ (0x1D7DB, "M", "3"),
+ (0x1D7DC, "M", "4"),
+ (0x1D7DD, "M", "5"),
+ (0x1D7DE, "M", "6"),
+ (0x1D7DF, "M", "7"),
+ (0x1D7E0, "M", "8"),
+ (0x1D7E1, "M", "9"),
+ (0x1D7E2, "M", "0"),
+ (0x1D7E3, "M", "1"),
+ (0x1D7E4, "M", "2"),
+ (0x1D7E5, "M", "3"),
+ (0x1D7E6, "M", "4"),
+ (0x1D7E7, "M", "5"),
+ (0x1D7E8, "M", "6"),
+ (0x1D7E9, "M", "7"),
+ (0x1D7EA, "M", "8"),
+ (0x1D7EB, "M", "9"),
+ (0x1D7EC, "M", "0"),
+ (0x1D7ED, "M", "1"),
+ (0x1D7EE, "M", "2"),
+ (0x1D7EF, "M", "3"),
+ (0x1D7F0, "M", "4"),
+ (0x1D7F1, "M", "5"),
+ (0x1D7F2, "M", "6"),
+ (0x1D7F3, "M", "7"),
+ (0x1D7F4, "M", "8"),
+ (0x1D7F5, "M", "9"),
+ (0x1D7F6, "M", "0"),
+ (0x1D7F7, "M", "1"),
+ (0x1D7F8, "M", "2"),
+ (0x1D7F9, "M", "3"),
+ (0x1D7FA, "M", "4"),
+ (0x1D7FB, "M", "5"),
+ (0x1D7FC, "M", "6"),
+ (0x1D7FD, "M", "7"),
+ (0x1D7FE, "M", "8"),
+ (0x1D7FF, "M", "9"),
+ (0x1D800, "V"),
+ (0x1DA8C, "X"),
+ (0x1DA9B, "V"),
+ (0x1DAA0, "X"),
+ (0x1DAA1, "V"),
+ (0x1DAB0, "X"),
+ (0x1DF00, "V"),
+ (0x1DF1F, "X"),
+ (0x1DF25, "V"),
+ (0x1DF2B, "X"),
+ (0x1E000, "V"),
+ (0x1E007, "X"),
+ (0x1E008, "V"),
+ (0x1E019, "X"),
+ (0x1E01B, "V"),
+ (0x1E022, "X"),
+ (0x1E023, "V"),
+ (0x1E025, "X"),
+ (0x1E026, "V"),
+ (0x1E02B, "X"),
+ (0x1E030, "M", "а"),
+ (0x1E031, "M", "б"),
+ (0x1E032, "M", "в"),
+ (0x1E033, "M", "г"),
+ (0x1E034, "M", "д"),
+ (0x1E035, "M", "е"),
+ (0x1E036, "M", "ж"),
+ ]
+
+
+def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E037, "M", "з"),
+ (0x1E038, "M", "и"),
+ (0x1E039, "M", "к"),
+ (0x1E03A, "M", "л"),
+ (0x1E03B, "M", "м"),
+ (0x1E03C, "M", "о"),
+ (0x1E03D, "M", "п"),
+ (0x1E03E, "M", "р"),
+ (0x1E03F, "M", "с"),
+ (0x1E040, "M", "т"),
+ (0x1E041, "M", "у"),
+ (0x1E042, "M", "ф"),
+ (0x1E043, "M", "х"),
+ (0x1E044, "M", "ц"),
+ (0x1E045, "M", "ч"),
+ (0x1E046, "M", "ш"),
+ (0x1E047, "M", "ы"),
+ (0x1E048, "M", "э"),
+ (0x1E049, "M", "ю"),
+ (0x1E04A, "M", "ꚉ"),
+ (0x1E04B, "M", "ә"),
+ (0x1E04C, "M", "і"),
+ (0x1E04D, "M", "ј"),
+ (0x1E04E, "M", "ө"),
+ (0x1E04F, "M", "ү"),
+ (0x1E050, "M", "ӏ"),
+ (0x1E051, "M", "а"),
+ (0x1E052, "M", "б"),
+ (0x1E053, "M", "в"),
+ (0x1E054, "M", "г"),
+ (0x1E055, "M", "д"),
+ (0x1E056, "M", "е"),
+ (0x1E057, "M", "ж"),
+ (0x1E058, "M", "з"),
+ (0x1E059, "M", "и"),
+ (0x1E05A, "M", "к"),
+ (0x1E05B, "M", "л"),
+ (0x1E05C, "M", "о"),
+ (0x1E05D, "M", "п"),
+ (0x1E05E, "M", "с"),
+ (0x1E05F, "M", "у"),
+ (0x1E060, "M", "ф"),
+ (0x1E061, "M", "х"),
+ (0x1E062, "M", "ц"),
+ (0x1E063, "M", "ч"),
+ (0x1E064, "M", "ш"),
+ (0x1E065, "M", "ъ"),
+ (0x1E066, "M", "ы"),
+ (0x1E067, "M", "ґ"),
+ (0x1E068, "M", "і"),
+ (0x1E069, "M", "ѕ"),
+ (0x1E06A, "M", "џ"),
+ (0x1E06B, "M", "ҫ"),
+ (0x1E06C, "M", "ꙑ"),
+ (0x1E06D, "M", "ұ"),
+ (0x1E06E, "X"),
+ (0x1E08F, "V"),
+ (0x1E090, "X"),
+ (0x1E100, "V"),
+ (0x1E12D, "X"),
+ (0x1E130, "V"),
+ (0x1E13E, "X"),
+ (0x1E140, "V"),
+ (0x1E14A, "X"),
+ (0x1E14E, "V"),
+ (0x1E150, "X"),
+ (0x1E290, "V"),
+ (0x1E2AF, "X"),
+ (0x1E2C0, "V"),
+ (0x1E2FA, "X"),
+ (0x1E2FF, "V"),
+ (0x1E300, "X"),
+ (0x1E4D0, "V"),
+ (0x1E4FA, "X"),
+ (0x1E7E0, "V"),
+ (0x1E7E7, "X"),
+ (0x1E7E8, "V"),
+ (0x1E7EC, "X"),
+ (0x1E7ED, "V"),
+ (0x1E7EF, "X"),
+ (0x1E7F0, "V"),
+ (0x1E7FF, "X"),
+ (0x1E800, "V"),
+ (0x1E8C5, "X"),
+ (0x1E8C7, "V"),
+ (0x1E8D7, "X"),
+ (0x1E900, "M", "𞤢"),
+ (0x1E901, "M", "𞤣"),
+ (0x1E902, "M", "𞤤"),
+ (0x1E903, "M", "𞤥"),
+ (0x1E904, "M", "𞤦"),
+ (0x1E905, "M", "𞤧"),
+ (0x1E906, "M", "𞤨"),
+ (0x1E907, "M", "𞤩"),
+ (0x1E908, "M", "𞤪"),
+ (0x1E909, "M", "𞤫"),
+ (0x1E90A, "M", "𞤬"),
+ (0x1E90B, "M", "𞤭"),
+ (0x1E90C, "M", "𞤮"),
+ (0x1E90D, "M", "𞤯"),
+ ]
+
+
+def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E90E, "M", "𞤰"),
+ (0x1E90F, "M", "𞤱"),
+ (0x1E910, "M", "𞤲"),
+ (0x1E911, "M", "𞤳"),
+ (0x1E912, "M", "𞤴"),
+ (0x1E913, "M", "𞤵"),
+ (0x1E914, "M", "𞤶"),
+ (0x1E915, "M", "𞤷"),
+ (0x1E916, "M", "𞤸"),
+ (0x1E917, "M", "𞤹"),
+ (0x1E918, "M", "𞤺"),
+ (0x1E919, "M", "𞤻"),
+ (0x1E91A, "M", "𞤼"),
+ (0x1E91B, "M", "𞤽"),
+ (0x1E91C, "M", "𞤾"),
+ (0x1E91D, "M", "𞤿"),
+ (0x1E91E, "M", "𞥀"),
+ (0x1E91F, "M", "𞥁"),
+ (0x1E920, "M", "𞥂"),
+ (0x1E921, "M", "𞥃"),
+ (0x1E922, "V"),
+ (0x1E94C, "X"),
+ (0x1E950, "V"),
+ (0x1E95A, "X"),
+ (0x1E95E, "V"),
+ (0x1E960, "X"),
+ (0x1EC71, "V"),
+ (0x1ECB5, "X"),
+ (0x1ED01, "V"),
+ (0x1ED3E, "X"),
+ (0x1EE00, "M", "ا"),
+ (0x1EE01, "M", "ب"),
+ (0x1EE02, "M", "ج"),
+ (0x1EE03, "M", "د"),
+ (0x1EE04, "X"),
+ (0x1EE05, "M", "و"),
+ (0x1EE06, "M", "ز"),
+ (0x1EE07, "M", "ح"),
+ (0x1EE08, "M", "ط"),
+ (0x1EE09, "M", "ي"),
+ (0x1EE0A, "M", "ك"),
+ (0x1EE0B, "M", "ل"),
+ (0x1EE0C, "M", "م"),
+ (0x1EE0D, "M", "ن"),
+ (0x1EE0E, "M", "س"),
+ (0x1EE0F, "M", "ع"),
+ (0x1EE10, "M", "ف"),
+ (0x1EE11, "M", "ص"),
+ (0x1EE12, "M", "ق"),
+ (0x1EE13, "M", "ر"),
+ (0x1EE14, "M", "ش"),
+ (0x1EE15, "M", "ت"),
+ (0x1EE16, "M", "ث"),
+ (0x1EE17, "M", "خ"),
+ (0x1EE18, "M", "ذ"),
+ (0x1EE19, "M", "ض"),
+ (0x1EE1A, "M", "ظ"),
+ (0x1EE1B, "M", "غ"),
+ (0x1EE1C, "M", "ٮ"),
+ (0x1EE1D, "M", "ں"),
+ (0x1EE1E, "M", "ڡ"),
+ (0x1EE1F, "M", "ٯ"),
+ (0x1EE20, "X"),
+ (0x1EE21, "M", "ب"),
+ (0x1EE22, "M", "ج"),
+ (0x1EE23, "X"),
+ (0x1EE24, "M", "ه"),
+ (0x1EE25, "X"),
+ (0x1EE27, "M", "ح"),
+ (0x1EE28, "X"),
+ (0x1EE29, "M", "ي"),
+ (0x1EE2A, "M", "ك"),
+ (0x1EE2B, "M", "ل"),
+ (0x1EE2C, "M", "م"),
+ (0x1EE2D, "M", "ن"),
+ (0x1EE2E, "M", "س"),
+ (0x1EE2F, "M", "ع"),
+ (0x1EE30, "M", "ف"),
+ (0x1EE31, "M", "ص"),
+ (0x1EE32, "M", "ق"),
+ (0x1EE33, "X"),
+ (0x1EE34, "M", "ش"),
+ (0x1EE35, "M", "ت"),
+ (0x1EE36, "M", "ث"),
+ (0x1EE37, "M", "خ"),
+ (0x1EE38, "X"),
+ (0x1EE39, "M", "ض"),
+ (0x1EE3A, "X"),
+ (0x1EE3B, "M", "غ"),
+ (0x1EE3C, "X"),
+ (0x1EE42, "M", "ج"),
+ (0x1EE43, "X"),
+ (0x1EE47, "M", "ح"),
+ (0x1EE48, "X"),
+ (0x1EE49, "M", "ي"),
+ (0x1EE4A, "X"),
+ (0x1EE4B, "M", "ل"),
+ (0x1EE4C, "X"),
+ (0x1EE4D, "M", "ن"),
+ (0x1EE4E, "M", "س"),
+ ]
+
+
+def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EE4F, "M", "ع"),
+ (0x1EE50, "X"),
+ (0x1EE51, "M", "ص"),
+ (0x1EE52, "M", "ق"),
+ (0x1EE53, "X"),
+ (0x1EE54, "M", "ش"),
+ (0x1EE55, "X"),
+ (0x1EE57, "M", "خ"),
+ (0x1EE58, "X"),
+ (0x1EE59, "M", "ض"),
+ (0x1EE5A, "X"),
+ (0x1EE5B, "M", "غ"),
+ (0x1EE5C, "X"),
+ (0x1EE5D, "M", "ں"),
+ (0x1EE5E, "X"),
+ (0x1EE5F, "M", "ٯ"),
+ (0x1EE60, "X"),
+ (0x1EE61, "M", "ب"),
+ (0x1EE62, "M", "ج"),
+ (0x1EE63, "X"),
+ (0x1EE64, "M", "ه"),
+ (0x1EE65, "X"),
+ (0x1EE67, "M", "ح"),
+ (0x1EE68, "M", "ط"),
+ (0x1EE69, "M", "ي"),
+ (0x1EE6A, "M", "ك"),
+ (0x1EE6B, "X"),
+ (0x1EE6C, "M", "م"),
+ (0x1EE6D, "M", "ن"),
+ (0x1EE6E, "M", "س"),
+ (0x1EE6F, "M", "ع"),
+ (0x1EE70, "M", "ف"),
+ (0x1EE71, "M", "ص"),
+ (0x1EE72, "M", "ق"),
+ (0x1EE73, "X"),
+ (0x1EE74, "M", "ش"),
+ (0x1EE75, "M", "ت"),
+ (0x1EE76, "M", "ث"),
+ (0x1EE77, "M", "خ"),
+ (0x1EE78, "X"),
+ (0x1EE79, "M", "ض"),
+ (0x1EE7A, "M", "ظ"),
+ (0x1EE7B, "M", "غ"),
+ (0x1EE7C, "M", "ٮ"),
+ (0x1EE7D, "X"),
+ (0x1EE7E, "M", "ڡ"),
+ (0x1EE7F, "X"),
+ (0x1EE80, "M", "ا"),
+ (0x1EE81, "M", "ب"),
+ (0x1EE82, "M", "ج"),
+ (0x1EE83, "M", "د"),
+ (0x1EE84, "M", "ه"),
+ (0x1EE85, "M", "و"),
+ (0x1EE86, "M", "ز"),
+ (0x1EE87, "M", "ح"),
+ (0x1EE88, "M", "ط"),
+ (0x1EE89, "M", "ي"),
+ (0x1EE8A, "X"),
+ (0x1EE8B, "M", "ل"),
+ (0x1EE8C, "M", "م"),
+ (0x1EE8D, "M", "ن"),
+ (0x1EE8E, "M", "س"),
+ (0x1EE8F, "M", "ع"),
+ (0x1EE90, "M", "ف"),
+ (0x1EE91, "M", "ص"),
+ (0x1EE92, "M", "ق"),
+ (0x1EE93, "M", "ر"),
+ (0x1EE94, "M", "ش"),
+ (0x1EE95, "M", "ت"),
+ (0x1EE96, "M", "ث"),
+ (0x1EE97, "M", "خ"),
+ (0x1EE98, "M", "ذ"),
+ (0x1EE99, "M", "ض"),
+ (0x1EE9A, "M", "ظ"),
+ (0x1EE9B, "M", "غ"),
+ (0x1EE9C, "X"),
+ (0x1EEA1, "M", "ب"),
+ (0x1EEA2, "M", "ج"),
+ (0x1EEA3, "M", "د"),
+ (0x1EEA4, "X"),
+ (0x1EEA5, "M", "و"),
+ (0x1EEA6, "M", "ز"),
+ (0x1EEA7, "M", "ح"),
+ (0x1EEA8, "M", "ط"),
+ (0x1EEA9, "M", "ي"),
+ (0x1EEAA, "X"),
+ (0x1EEAB, "M", "ل"),
+ (0x1EEAC, "M", "م"),
+ (0x1EEAD, "M", "ن"),
+ (0x1EEAE, "M", "س"),
+ (0x1EEAF, "M", "ع"),
+ (0x1EEB0, "M", "ف"),
+ (0x1EEB1, "M", "ص"),
+ (0x1EEB2, "M", "ق"),
+ (0x1EEB3, "M", "ر"),
+ (0x1EEB4, "M", "ش"),
+ (0x1EEB5, "M", "ت"),
+ (0x1EEB6, "M", "ث"),
+ (0x1EEB7, "M", "خ"),
+ (0x1EEB8, "M", "ذ"),
+ ]
+
+
+def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EEB9, "M", "ض"),
+ (0x1EEBA, "M", "ظ"),
+ (0x1EEBB, "M", "غ"),
+ (0x1EEBC, "X"),
+ (0x1EEF0, "V"),
+ (0x1EEF2, "X"),
+ (0x1F000, "V"),
+ (0x1F02C, "X"),
+ (0x1F030, "V"),
+ (0x1F094, "X"),
+ (0x1F0A0, "V"),
+ (0x1F0AF, "X"),
+ (0x1F0B1, "V"),
+ (0x1F0C0, "X"),
+ (0x1F0C1, "V"),
+ (0x1F0D0, "X"),
+ (0x1F0D1, "V"),
+ (0x1F0F6, "X"),
+ (0x1F101, "3", "0,"),
+ (0x1F102, "3", "1,"),
+ (0x1F103, "3", "2,"),
+ (0x1F104, "3", "3,"),
+ (0x1F105, "3", "4,"),
+ (0x1F106, "3", "5,"),
+ (0x1F107, "3", "6,"),
+ (0x1F108, "3", "7,"),
+ (0x1F109, "3", "8,"),
+ (0x1F10A, "3", "9,"),
+ (0x1F10B, "V"),
+ (0x1F110, "3", "(a)"),
+ (0x1F111, "3", "(b)"),
+ (0x1F112, "3", "(c)"),
+ (0x1F113, "3", "(d)"),
+ (0x1F114, "3", "(e)"),
+ (0x1F115, "3", "(f)"),
+ (0x1F116, "3", "(g)"),
+ (0x1F117, "3", "(h)"),
+ (0x1F118, "3", "(i)"),
+ (0x1F119, "3", "(j)"),
+ (0x1F11A, "3", "(k)"),
+ (0x1F11B, "3", "(l)"),
+ (0x1F11C, "3", "(m)"),
+ (0x1F11D, "3", "(n)"),
+ (0x1F11E, "3", "(o)"),
+ (0x1F11F, "3", "(p)"),
+ (0x1F120, "3", "(q)"),
+ (0x1F121, "3", "(r)"),
+ (0x1F122, "3", "(s)"),
+ (0x1F123, "3", "(t)"),
+ (0x1F124, "3", "(u)"),
+ (0x1F125, "3", "(v)"),
+ (0x1F126, "3", "(w)"),
+ (0x1F127, "3", "(x)"),
+ (0x1F128, "3", "(y)"),
+ (0x1F129, "3", "(z)"),
+ (0x1F12A, "M", "〔s〕"),
+ (0x1F12B, "M", "c"),
+ (0x1F12C, "M", "r"),
+ (0x1F12D, "M", "cd"),
+ (0x1F12E, "M", "wz"),
+ (0x1F12F, "V"),
+ (0x1F130, "M", "a"),
+ (0x1F131, "M", "b"),
+ (0x1F132, "M", "c"),
+ (0x1F133, "M", "d"),
+ (0x1F134, "M", "e"),
+ (0x1F135, "M", "f"),
+ (0x1F136, "M", "g"),
+ (0x1F137, "M", "h"),
+ (0x1F138, "M", "i"),
+ (0x1F139, "M", "j"),
+ (0x1F13A, "M", "k"),
+ (0x1F13B, "M", "l"),
+ (0x1F13C, "M", "m"),
+ (0x1F13D, "M", "n"),
+ (0x1F13E, "M", "o"),
+ (0x1F13F, "M", "p"),
+ (0x1F140, "M", "q"),
+ (0x1F141, "M", "r"),
+ (0x1F142, "M", "s"),
+ (0x1F143, "M", "t"),
+ (0x1F144, "M", "u"),
+ (0x1F145, "M", "v"),
+ (0x1F146, "M", "w"),
+ (0x1F147, "M", "x"),
+ (0x1F148, "M", "y"),
+ (0x1F149, "M", "z"),
+ (0x1F14A, "M", "hv"),
+ (0x1F14B, "M", "mv"),
+ (0x1F14C, "M", "sd"),
+ (0x1F14D, "M", "ss"),
+ (0x1F14E, "M", "ppv"),
+ (0x1F14F, "M", "wc"),
+ (0x1F150, "V"),
+ (0x1F16A, "M", "mc"),
+ (0x1F16B, "M", "md"),
+ (0x1F16C, "M", "mr"),
+ (0x1F16D, "V"),
+ (0x1F190, "M", "dj"),
+ (0x1F191, "V"),
+ ]
+
+
+def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F1AE, "X"),
+ (0x1F1E6, "V"),
+ (0x1F200, "M", "ほか"),
+ (0x1F201, "M", "ココ"),
+ (0x1F202, "M", "サ"),
+ (0x1F203, "X"),
+ (0x1F210, "M", "手"),
+ (0x1F211, "M", "字"),
+ (0x1F212, "M", "双"),
+ (0x1F213, "M", "デ"),
+ (0x1F214, "M", "二"),
+ (0x1F215, "M", "多"),
+ (0x1F216, "M", "解"),
+ (0x1F217, "M", "天"),
+ (0x1F218, "M", "交"),
+ (0x1F219, "M", "映"),
+ (0x1F21A, "M", "無"),
+ (0x1F21B, "M", "料"),
+ (0x1F21C, "M", "前"),
+ (0x1F21D, "M", "後"),
+ (0x1F21E, "M", "再"),
+ (0x1F21F, "M", "新"),
+ (0x1F220, "M", "初"),
+ (0x1F221, "M", "終"),
+ (0x1F222, "M", "生"),
+ (0x1F223, "M", "販"),
+ (0x1F224, "M", "声"),
+ (0x1F225, "M", "吹"),
+ (0x1F226, "M", "演"),
+ (0x1F227, "M", "投"),
+ (0x1F228, "M", "捕"),
+ (0x1F229, "M", "一"),
+ (0x1F22A, "M", "三"),
+ (0x1F22B, "M", "遊"),
+ (0x1F22C, "M", "左"),
+ (0x1F22D, "M", "中"),
+ (0x1F22E, "M", "右"),
+ (0x1F22F, "M", "指"),
+ (0x1F230, "M", "走"),
+ (0x1F231, "M", "打"),
+ (0x1F232, "M", "禁"),
+ (0x1F233, "M", "空"),
+ (0x1F234, "M", "合"),
+ (0x1F235, "M", "満"),
+ (0x1F236, "M", "有"),
+ (0x1F237, "M", "月"),
+ (0x1F238, "M", "申"),
+ (0x1F239, "M", "割"),
+ (0x1F23A, "M", "営"),
+ (0x1F23B, "M", "配"),
+ (0x1F23C, "X"),
+ (0x1F240, "M", "〔本〕"),
+ (0x1F241, "M", "〔三〕"),
+ (0x1F242, "M", "〔二〕"),
+ (0x1F243, "M", "〔安〕"),
+ (0x1F244, "M", "〔点〕"),
+ (0x1F245, "M", "〔打〕"),
+ (0x1F246, "M", "〔盗〕"),
+ (0x1F247, "M", "〔勝〕"),
+ (0x1F248, "M", "〔敗〕"),
+ (0x1F249, "X"),
+ (0x1F250, "M", "得"),
+ (0x1F251, "M", "可"),
+ (0x1F252, "X"),
+ (0x1F260, "V"),
+ (0x1F266, "X"),
+ (0x1F300, "V"),
+ (0x1F6D8, "X"),
+ (0x1F6DC, "V"),
+ (0x1F6ED, "X"),
+ (0x1F6F0, "V"),
+ (0x1F6FD, "X"),
+ (0x1F700, "V"),
+ (0x1F777, "X"),
+ (0x1F77B, "V"),
+ (0x1F7DA, "X"),
+ (0x1F7E0, "V"),
+ (0x1F7EC, "X"),
+ (0x1F7F0, "V"),
+ (0x1F7F1, "X"),
+ (0x1F800, "V"),
+ (0x1F80C, "X"),
+ (0x1F810, "V"),
+ (0x1F848, "X"),
+ (0x1F850, "V"),
+ (0x1F85A, "X"),
+ (0x1F860, "V"),
+ (0x1F888, "X"),
+ (0x1F890, "V"),
+ (0x1F8AE, "X"),
+ (0x1F8B0, "V"),
+ (0x1F8B2, "X"),
+ (0x1F900, "V"),
+ (0x1FA54, "X"),
+ (0x1FA60, "V"),
+ (0x1FA6E, "X"),
+ (0x1FA70, "V"),
+ (0x1FA7D, "X"),
+ (0x1FA80, "V"),
+ (0x1FA89, "X"),
+ ]
+
+
+def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1FA90, "V"),
+ (0x1FABE, "X"),
+ (0x1FABF, "V"),
+ (0x1FAC6, "X"),
+ (0x1FACE, "V"),
+ (0x1FADC, "X"),
+ (0x1FAE0, "V"),
+ (0x1FAE9, "X"),
+ (0x1FAF0, "V"),
+ (0x1FAF9, "X"),
+ (0x1FB00, "V"),
+ (0x1FB93, "X"),
+ (0x1FB94, "V"),
+ (0x1FBCB, "X"),
+ (0x1FBF0, "M", "0"),
+ (0x1FBF1, "M", "1"),
+ (0x1FBF2, "M", "2"),
+ (0x1FBF3, "M", "3"),
+ (0x1FBF4, "M", "4"),
+ (0x1FBF5, "M", "5"),
+ (0x1FBF6, "M", "6"),
+ (0x1FBF7, "M", "7"),
+ (0x1FBF8, "M", "8"),
+ (0x1FBF9, "M", "9"),
+ (0x1FBFA, "X"),
+ (0x20000, "V"),
+ (0x2A6E0, "X"),
+ (0x2A700, "V"),
+ (0x2B73A, "X"),
+ (0x2B740, "V"),
+ (0x2B81E, "X"),
+ (0x2B820, "V"),
+ (0x2CEA2, "X"),
+ (0x2CEB0, "V"),
+ (0x2EBE1, "X"),
+ (0x2EBF0, "V"),
+ (0x2EE5E, "X"),
+ (0x2F800, "M", "丽"),
+ (0x2F801, "M", "丸"),
+ (0x2F802, "M", "乁"),
+ (0x2F803, "M", "𠄢"),
+ (0x2F804, "M", "你"),
+ (0x2F805, "M", "侮"),
+ (0x2F806, "M", "侻"),
+ (0x2F807, "M", "倂"),
+ (0x2F808, "M", "偺"),
+ (0x2F809, "M", "備"),
+ (0x2F80A, "M", "僧"),
+ (0x2F80B, "M", "像"),
+ (0x2F80C, "M", "㒞"),
+ (0x2F80D, "M", "𠘺"),
+ (0x2F80E, "M", "免"),
+ (0x2F80F, "M", "兔"),
+ (0x2F810, "M", "兤"),
+ (0x2F811, "M", "具"),
+ (0x2F812, "M", "𠔜"),
+ (0x2F813, "M", "㒹"),
+ (0x2F814, "M", "內"),
+ (0x2F815, "M", "再"),
+ (0x2F816, "M", "𠕋"),
+ (0x2F817, "M", "冗"),
+ (0x2F818, "M", "冤"),
+ (0x2F819, "M", "仌"),
+ (0x2F81A, "M", "冬"),
+ (0x2F81B, "M", "况"),
+ (0x2F81C, "M", "𩇟"),
+ (0x2F81D, "M", "凵"),
+ (0x2F81E, "M", "刃"),
+ (0x2F81F, "M", "㓟"),
+ (0x2F820, "M", "刻"),
+ (0x2F821, "M", "剆"),
+ (0x2F822, "M", "割"),
+ (0x2F823, "M", "剷"),
+ (0x2F824, "M", "㔕"),
+ (0x2F825, "M", "勇"),
+ (0x2F826, "M", "勉"),
+ (0x2F827, "M", "勤"),
+ (0x2F828, "M", "勺"),
+ (0x2F829, "M", "包"),
+ (0x2F82A, "M", "匆"),
+ (0x2F82B, "M", "北"),
+ (0x2F82C, "M", "卉"),
+ (0x2F82D, "M", "卑"),
+ (0x2F82E, "M", "博"),
+ (0x2F82F, "M", "即"),
+ (0x2F830, "M", "卽"),
+ (0x2F831, "M", "卿"),
+ (0x2F834, "M", "𠨬"),
+ (0x2F835, "M", "灰"),
+ (0x2F836, "M", "及"),
+ (0x2F837, "M", "叟"),
+ (0x2F838, "M", "𠭣"),
+ (0x2F839, "M", "叫"),
+ (0x2F83A, "M", "叱"),
+ (0x2F83B, "M", "吆"),
+ (0x2F83C, "M", "咞"),
+ (0x2F83D, "M", "吸"),
+ (0x2F83E, "M", "呈"),
+ (0x2F83F, "M", "周"),
+ (0x2F840, "M", "咢"),
+ ]
+
+
+def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F841, "M", "哶"),
+ (0x2F842, "M", "唐"),
+ (0x2F843, "M", "啓"),
+ (0x2F844, "M", "啣"),
+ (0x2F845, "M", "善"),
+ (0x2F847, "M", "喙"),
+ (0x2F848, "M", "喫"),
+ (0x2F849, "M", "喳"),
+ (0x2F84A, "M", "嗂"),
+ (0x2F84B, "M", "圖"),
+ (0x2F84C, "M", "嘆"),
+ (0x2F84D, "M", "圗"),
+ (0x2F84E, "M", "噑"),
+ (0x2F84F, "M", "噴"),
+ (0x2F850, "M", "切"),
+ (0x2F851, "M", "壮"),
+ (0x2F852, "M", "城"),
+ (0x2F853, "M", "埴"),
+ (0x2F854, "M", "堍"),
+ (0x2F855, "M", "型"),
+ (0x2F856, "M", "堲"),
+ (0x2F857, "M", "報"),
+ (0x2F858, "M", "墬"),
+ (0x2F859, "M", "𡓤"),
+ (0x2F85A, "M", "売"),
+ (0x2F85B, "M", "壷"),
+ (0x2F85C, "M", "夆"),
+ (0x2F85D, "M", "多"),
+ (0x2F85E, "M", "夢"),
+ (0x2F85F, "M", "奢"),
+ (0x2F860, "M", "𡚨"),
+ (0x2F861, "M", "𡛪"),
+ (0x2F862, "M", "姬"),
+ (0x2F863, "M", "娛"),
+ (0x2F864, "M", "娧"),
+ (0x2F865, "M", "姘"),
+ (0x2F866, "M", "婦"),
+ (0x2F867, "M", "㛮"),
+ (0x2F868, "X"),
+ (0x2F869, "M", "嬈"),
+ (0x2F86A, "M", "嬾"),
+ (0x2F86C, "M", "𡧈"),
+ (0x2F86D, "M", "寃"),
+ (0x2F86E, "M", "寘"),
+ (0x2F86F, "M", "寧"),
+ (0x2F870, "M", "寳"),
+ (0x2F871, "M", "𡬘"),
+ (0x2F872, "M", "寿"),
+ (0x2F873, "M", "将"),
+ (0x2F874, "X"),
+ (0x2F875, "M", "尢"),
+ (0x2F876, "M", "㞁"),
+ (0x2F877, "M", "屠"),
+ (0x2F878, "M", "屮"),
+ (0x2F879, "M", "峀"),
+ (0x2F87A, "M", "岍"),
+ (0x2F87B, "M", "𡷤"),
+ (0x2F87C, "M", "嵃"),
+ (0x2F87D, "M", "𡷦"),
+ (0x2F87E, "M", "嵮"),
+ (0x2F87F, "M", "嵫"),
+ (0x2F880, "M", "嵼"),
+ (0x2F881, "M", "巡"),
+ (0x2F882, "M", "巢"),
+ (0x2F883, "M", "㠯"),
+ (0x2F884, "M", "巽"),
+ (0x2F885, "M", "帨"),
+ (0x2F886, "M", "帽"),
+ (0x2F887, "M", "幩"),
+ (0x2F888, "M", "㡢"),
+ (0x2F889, "M", "𢆃"),
+ (0x2F88A, "M", "㡼"),
+ (0x2F88B, "M", "庰"),
+ (0x2F88C, "M", "庳"),
+ (0x2F88D, "M", "庶"),
+ (0x2F88E, "M", "廊"),
+ (0x2F88F, "M", "𪎒"),
+ (0x2F890, "M", "廾"),
+ (0x2F891, "M", "𢌱"),
+ (0x2F893, "M", "舁"),
+ (0x2F894, "M", "弢"),
+ (0x2F896, "M", "㣇"),
+ (0x2F897, "M", "𣊸"),
+ (0x2F898, "M", "𦇚"),
+ (0x2F899, "M", "形"),
+ (0x2F89A, "M", "彫"),
+ (0x2F89B, "M", "㣣"),
+ (0x2F89C, "M", "徚"),
+ (0x2F89D, "M", "忍"),
+ (0x2F89E, "M", "志"),
+ (0x2F89F, "M", "忹"),
+ (0x2F8A0, "M", "悁"),
+ (0x2F8A1, "M", "㤺"),
+ (0x2F8A2, "M", "㤜"),
+ (0x2F8A3, "M", "悔"),
+ (0x2F8A4, "M", "𢛔"),
+ (0x2F8A5, "M", "惇"),
+ (0x2F8A6, "M", "慈"),
+ (0x2F8A7, "M", "慌"),
+ (0x2F8A8, "M", "慎"),
+ ]
+
+
+def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F8A9, "M", "慌"),
+ (0x2F8AA, "M", "慺"),
+ (0x2F8AB, "M", "憎"),
+ (0x2F8AC, "M", "憲"),
+ (0x2F8AD, "M", "憤"),
+ (0x2F8AE, "M", "憯"),
+ (0x2F8AF, "M", "懞"),
+ (0x2F8B0, "M", "懲"),
+ (0x2F8B1, "M", "懶"),
+ (0x2F8B2, "M", "成"),
+ (0x2F8B3, "M", "戛"),
+ (0x2F8B4, "M", "扝"),
+ (0x2F8B5, "M", "抱"),
+ (0x2F8B6, "M", "拔"),
+ (0x2F8B7, "M", "捐"),
+ (0x2F8B8, "M", "𢬌"),
+ (0x2F8B9, "M", "挽"),
+ (0x2F8BA, "M", "拼"),
+ (0x2F8BB, "M", "捨"),
+ (0x2F8BC, "M", "掃"),
+ (0x2F8BD, "M", "揤"),
+ (0x2F8BE, "M", "𢯱"),
+ (0x2F8BF, "M", "搢"),
+ (0x2F8C0, "M", "揅"),
+ (0x2F8C1, "M", "掩"),
+ (0x2F8C2, "M", "㨮"),
+ (0x2F8C3, "M", "摩"),
+ (0x2F8C4, "M", "摾"),
+ (0x2F8C5, "M", "撝"),
+ (0x2F8C6, "M", "摷"),
+ (0x2F8C7, "M", "㩬"),
+ (0x2F8C8, "M", "敏"),
+ (0x2F8C9, "M", "敬"),
+ (0x2F8CA, "M", "𣀊"),
+ (0x2F8CB, "M", "旣"),
+ (0x2F8CC, "M", "書"),
+ (0x2F8CD, "M", "晉"),
+ (0x2F8CE, "M", "㬙"),
+ (0x2F8CF, "M", "暑"),
+ (0x2F8D0, "M", "㬈"),
+ (0x2F8D1, "M", "㫤"),
+ (0x2F8D2, "M", "冒"),
+ (0x2F8D3, "M", "冕"),
+ (0x2F8D4, "M", "最"),
+ (0x2F8D5, "M", "暜"),
+ (0x2F8D6, "M", "肭"),
+ (0x2F8D7, "M", "䏙"),
+ (0x2F8D8, "M", "朗"),
+ (0x2F8D9, "M", "望"),
+ (0x2F8DA, "M", "朡"),
+ (0x2F8DB, "M", "杞"),
+ (0x2F8DC, "M", "杓"),
+ (0x2F8DD, "M", "𣏃"),
+ (0x2F8DE, "M", "㭉"),
+ (0x2F8DF, "M", "柺"),
+ (0x2F8E0, "M", "枅"),
+ (0x2F8E1, "M", "桒"),
+ (0x2F8E2, "M", "梅"),
+ (0x2F8E3, "M", "𣑭"),
+ (0x2F8E4, "M", "梎"),
+ (0x2F8E5, "M", "栟"),
+ (0x2F8E6, "M", "椔"),
+ (0x2F8E7, "M", "㮝"),
+ (0x2F8E8, "M", "楂"),
+ (0x2F8E9, "M", "榣"),
+ (0x2F8EA, "M", "槪"),
+ (0x2F8EB, "M", "檨"),
+ (0x2F8EC, "M", "𣚣"),
+ (0x2F8ED, "M", "櫛"),
+ (0x2F8EE, "M", "㰘"),
+ (0x2F8EF, "M", "次"),
+ (0x2F8F0, "M", "𣢧"),
+ (0x2F8F1, "M", "歔"),
+ (0x2F8F2, "M", "㱎"),
+ (0x2F8F3, "M", "歲"),
+ (0x2F8F4, "M", "殟"),
+ (0x2F8F5, "M", "殺"),
+ (0x2F8F6, "M", "殻"),
+ (0x2F8F7, "M", "𣪍"),
+ (0x2F8F8, "M", "𡴋"),
+ (0x2F8F9, "M", "𣫺"),
+ (0x2F8FA, "M", "汎"),
+ (0x2F8FB, "M", "𣲼"),
+ (0x2F8FC, "M", "沿"),
+ (0x2F8FD, "M", "泍"),
+ (0x2F8FE, "M", "汧"),
+ (0x2F8FF, "M", "洖"),
+ (0x2F900, "M", "派"),
+ (0x2F901, "M", "海"),
+ (0x2F902, "M", "流"),
+ (0x2F903, "M", "浩"),
+ (0x2F904, "M", "浸"),
+ (0x2F905, "M", "涅"),
+ (0x2F906, "M", "𣴞"),
+ (0x2F907, "M", "洴"),
+ (0x2F908, "M", "港"),
+ (0x2F909, "M", "湮"),
+ (0x2F90A, "M", "㴳"),
+ (0x2F90B, "M", "滋"),
+ (0x2F90C, "M", "滇"),
+ ]
+
+
+def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F90D, "M", "𣻑"),
+ (0x2F90E, "M", "淹"),
+ (0x2F90F, "M", "潮"),
+ (0x2F910, "M", "𣽞"),
+ (0x2F911, "M", "𣾎"),
+ (0x2F912, "M", "濆"),
+ (0x2F913, "M", "瀹"),
+ (0x2F914, "M", "瀞"),
+ (0x2F915, "M", "瀛"),
+ (0x2F916, "M", "㶖"),
+ (0x2F917, "M", "灊"),
+ (0x2F918, "M", "災"),
+ (0x2F919, "M", "灷"),
+ (0x2F91A, "M", "炭"),
+ (0x2F91B, "M", "𠔥"),
+ (0x2F91C, "M", "煅"),
+ (0x2F91D, "M", "𤉣"),
+ (0x2F91E, "M", "熜"),
+ (0x2F91F, "X"),
+ (0x2F920, "M", "爨"),
+ (0x2F921, "M", "爵"),
+ (0x2F922, "M", "牐"),
+ (0x2F923, "M", "𤘈"),
+ (0x2F924, "M", "犀"),
+ (0x2F925, "M", "犕"),
+ (0x2F926, "M", "𤜵"),
+ (0x2F927, "M", "𤠔"),
+ (0x2F928, "M", "獺"),
+ (0x2F929, "M", "王"),
+ (0x2F92A, "M", "㺬"),
+ (0x2F92B, "M", "玥"),
+ (0x2F92C, "M", "㺸"),
+ (0x2F92E, "M", "瑇"),
+ (0x2F92F, "M", "瑜"),
+ (0x2F930, "M", "瑱"),
+ (0x2F931, "M", "璅"),
+ (0x2F932, "M", "瓊"),
+ (0x2F933, "M", "㼛"),
+ (0x2F934, "M", "甤"),
+ (0x2F935, "M", "𤰶"),
+ (0x2F936, "M", "甾"),
+ (0x2F937, "M", "𤲒"),
+ (0x2F938, "M", "異"),
+ (0x2F939, "M", "𢆟"),
+ (0x2F93A, "M", "瘐"),
+ (0x2F93B, "M", "𤾡"),
+ (0x2F93C, "M", "𤾸"),
+ (0x2F93D, "M", "𥁄"),
+ (0x2F93E, "M", "㿼"),
+ (0x2F93F, "M", "䀈"),
+ (0x2F940, "M", "直"),
+ (0x2F941, "M", "𥃳"),
+ (0x2F942, "M", "𥃲"),
+ (0x2F943, "M", "𥄙"),
+ (0x2F944, "M", "𥄳"),
+ (0x2F945, "M", "眞"),
+ (0x2F946, "M", "真"),
+ (0x2F948, "M", "睊"),
+ (0x2F949, "M", "䀹"),
+ (0x2F94A, "M", "瞋"),
+ (0x2F94B, "M", "䁆"),
+ (0x2F94C, "M", "䂖"),
+ (0x2F94D, "M", "𥐝"),
+ (0x2F94E, "M", "硎"),
+ (0x2F94F, "M", "碌"),
+ (0x2F950, "M", "磌"),
+ (0x2F951, "M", "䃣"),
+ (0x2F952, "M", "𥘦"),
+ (0x2F953, "M", "祖"),
+ (0x2F954, "M", "𥚚"),
+ (0x2F955, "M", "𥛅"),
+ (0x2F956, "M", "福"),
+ (0x2F957, "M", "秫"),
+ (0x2F958, "M", "䄯"),
+ (0x2F959, "M", "穀"),
+ (0x2F95A, "M", "穊"),
+ (0x2F95B, "M", "穏"),
+ (0x2F95C, "M", "𥥼"),
+ (0x2F95D, "M", "𥪧"),
+ (0x2F95F, "X"),
+ (0x2F960, "M", "䈂"),
+ (0x2F961, "M", "𥮫"),
+ (0x2F962, "M", "篆"),
+ (0x2F963, "M", "築"),
+ (0x2F964, "M", "䈧"),
+ (0x2F965, "M", "𥲀"),
+ (0x2F966, "M", "糒"),
+ (0x2F967, "M", "䊠"),
+ (0x2F968, "M", "糨"),
+ (0x2F969, "M", "糣"),
+ (0x2F96A, "M", "紀"),
+ (0x2F96B, "M", "𥾆"),
+ (0x2F96C, "M", "絣"),
+ (0x2F96D, "M", "䌁"),
+ (0x2F96E, "M", "緇"),
+ (0x2F96F, "M", "縂"),
+ (0x2F970, "M", "繅"),
+ (0x2F971, "M", "䌴"),
+ (0x2F972, "M", "𦈨"),
+ (0x2F973, "M", "𦉇"),
+ ]
+
+
+def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F974, "M", "䍙"),
+ (0x2F975, "M", "𦋙"),
+ (0x2F976, "M", "罺"),
+ (0x2F977, "M", "𦌾"),
+ (0x2F978, "M", "羕"),
+ (0x2F979, "M", "翺"),
+ (0x2F97A, "M", "者"),
+ (0x2F97B, "M", "𦓚"),
+ (0x2F97C, "M", "𦔣"),
+ (0x2F97D, "M", "聠"),
+ (0x2F97E, "M", "𦖨"),
+ (0x2F97F, "M", "聰"),
+ (0x2F980, "M", "𣍟"),
+ (0x2F981, "M", "䏕"),
+ (0x2F982, "M", "育"),
+ (0x2F983, "M", "脃"),
+ (0x2F984, "M", "䐋"),
+ (0x2F985, "M", "脾"),
+ (0x2F986, "M", "媵"),
+ (0x2F987, "M", "𦞧"),
+ (0x2F988, "M", "𦞵"),
+ (0x2F989, "M", "𣎓"),
+ (0x2F98A, "M", "𣎜"),
+ (0x2F98B, "M", "舁"),
+ (0x2F98C, "M", "舄"),
+ (0x2F98D, "M", "辞"),
+ (0x2F98E, "M", "䑫"),
+ (0x2F98F, "M", "芑"),
+ (0x2F990, "M", "芋"),
+ (0x2F991, "M", "芝"),
+ (0x2F992, "M", "劳"),
+ (0x2F993, "M", "花"),
+ (0x2F994, "M", "芳"),
+ (0x2F995, "M", "芽"),
+ (0x2F996, "M", "苦"),
+ (0x2F997, "M", "𦬼"),
+ (0x2F998, "M", "若"),
+ (0x2F999, "M", "茝"),
+ (0x2F99A, "M", "荣"),
+ (0x2F99B, "M", "莭"),
+ (0x2F99C, "M", "茣"),
+ (0x2F99D, "M", "莽"),
+ (0x2F99E, "M", "菧"),
+ (0x2F99F, "M", "著"),
+ (0x2F9A0, "M", "荓"),
+ (0x2F9A1, "M", "菊"),
+ (0x2F9A2, "M", "菌"),
+ (0x2F9A3, "M", "菜"),
+ (0x2F9A4, "M", "𦰶"),
+ (0x2F9A5, "M", "𦵫"),
+ (0x2F9A6, "M", "𦳕"),
+ (0x2F9A7, "M", "䔫"),
+ (0x2F9A8, "M", "蓱"),
+ (0x2F9A9, "M", "蓳"),
+ (0x2F9AA, "M", "蔖"),
+ (0x2F9AB, "M", "𧏊"),
+ (0x2F9AC, "M", "蕤"),
+ (0x2F9AD, "M", "𦼬"),
+ (0x2F9AE, "M", "䕝"),
+ (0x2F9AF, "M", "䕡"),
+ (0x2F9B0, "M", "𦾱"),
+ (0x2F9B1, "M", "𧃒"),
+ (0x2F9B2, "M", "䕫"),
+ (0x2F9B3, "M", "虐"),
+ (0x2F9B4, "M", "虜"),
+ (0x2F9B5, "M", "虧"),
+ (0x2F9B6, "M", "虩"),
+ (0x2F9B7, "M", "蚩"),
+ (0x2F9B8, "M", "蚈"),
+ (0x2F9B9, "M", "蜎"),
+ (0x2F9BA, "M", "蛢"),
+ (0x2F9BB, "M", "蝹"),
+ (0x2F9BC, "M", "蜨"),
+ (0x2F9BD, "M", "蝫"),
+ (0x2F9BE, "M", "螆"),
+ (0x2F9BF, "X"),
+ (0x2F9C0, "M", "蟡"),
+ (0x2F9C1, "M", "蠁"),
+ (0x2F9C2, "M", "䗹"),
+ (0x2F9C3, "M", "衠"),
+ (0x2F9C4, "M", "衣"),
+ (0x2F9C5, "M", "𧙧"),
+ (0x2F9C6, "M", "裗"),
+ (0x2F9C7, "M", "裞"),
+ (0x2F9C8, "M", "䘵"),
+ (0x2F9C9, "M", "裺"),
+ (0x2F9CA, "M", "㒻"),
+ (0x2F9CB, "M", "𧢮"),
+ (0x2F9CC, "M", "𧥦"),
+ (0x2F9CD, "M", "䚾"),
+ (0x2F9CE, "M", "䛇"),
+ (0x2F9CF, "M", "誠"),
+ (0x2F9D0, "M", "諭"),
+ (0x2F9D1, "M", "變"),
+ (0x2F9D2, "M", "豕"),
+ (0x2F9D3, "M", "𧲨"),
+ (0x2F9D4, "M", "貫"),
+ (0x2F9D5, "M", "賁"),
+ (0x2F9D6, "M", "贛"),
+ (0x2F9D7, "M", "起"),
+ ]
+
+
+def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F9D8, "M", "𧼯"),
+ (0x2F9D9, "M", "𠠄"),
+ (0x2F9DA, "M", "跋"),
+ (0x2F9DB, "M", "趼"),
+ (0x2F9DC, "M", "跰"),
+ (0x2F9DD, "M", "𠣞"),
+ (0x2F9DE, "M", "軔"),
+ (0x2F9DF, "M", "輸"),
+ (0x2F9E0, "M", "𨗒"),
+ (0x2F9E1, "M", "𨗭"),
+ (0x2F9E2, "M", "邔"),
+ (0x2F9E3, "M", "郱"),
+ (0x2F9E4, "M", "鄑"),
+ (0x2F9E5, "M", "𨜮"),
+ (0x2F9E6, "M", "鄛"),
+ (0x2F9E7, "M", "鈸"),
+ (0x2F9E8, "M", "鋗"),
+ (0x2F9E9, "M", "鋘"),
+ (0x2F9EA, "M", "鉼"),
+ (0x2F9EB, "M", "鏹"),
+ (0x2F9EC, "M", "鐕"),
+ (0x2F9ED, "M", "𨯺"),
+ (0x2F9EE, "M", "開"),
+ (0x2F9EF, "M", "䦕"),
+ (0x2F9F0, "M", "閷"),
+ (0x2F9F1, "M", "𨵷"),
+ (0x2F9F2, "M", "䧦"),
+ (0x2F9F3, "M", "雃"),
+ (0x2F9F4, "M", "嶲"),
+ (0x2F9F5, "M", "霣"),
+ (0x2F9F6, "M", "𩅅"),
+ (0x2F9F7, "M", "𩈚"),
+ (0x2F9F8, "M", "䩮"),
+ (0x2F9F9, "M", "䩶"),
+ (0x2F9FA, "M", "韠"),
+ (0x2F9FB, "M", "𩐊"),
+ (0x2F9FC, "M", "䪲"),
+ (0x2F9FD, "M", "𩒖"),
+ (0x2F9FE, "M", "頋"),
+ (0x2FA00, "M", "頩"),
+ (0x2FA01, "M", "𩖶"),
+ (0x2FA02, "M", "飢"),
+ (0x2FA03, "M", "䬳"),
+ (0x2FA04, "M", "餩"),
+ (0x2FA05, "M", "馧"),
+ (0x2FA06, "M", "駂"),
+ (0x2FA07, "M", "駾"),
+ (0x2FA08, "M", "䯎"),
+ (0x2FA09, "M", "𩬰"),
+ (0x2FA0A, "M", "鬒"),
+ (0x2FA0B, "M", "鱀"),
+ (0x2FA0C, "M", "鳽"),
+ (0x2FA0D, "M", "䳎"),
+ (0x2FA0E, "M", "䳭"),
+ (0x2FA0F, "M", "鵧"),
+ (0x2FA10, "M", "𪃎"),
+ (0x2FA11, "M", "䳸"),
+ (0x2FA12, "M", "𪄅"),
+ (0x2FA13, "M", "𪈎"),
+ (0x2FA14, "M", "𪊑"),
+ (0x2FA15, "M", "麻"),
+ (0x2FA16, "M", "䵖"),
+ (0x2FA17, "M", "黹"),
+ (0x2FA18, "M", "黾"),
+ (0x2FA19, "M", "鼅"),
+ (0x2FA1A, "M", "鼏"),
+ (0x2FA1B, "M", "鼖"),
+ (0x2FA1C, "M", "鼻"),
+ (0x2FA1D, "M", "𪘀"),
+ (0x2FA1E, "X"),
+ (0x30000, "V"),
+ (0x3134B, "X"),
+ (0x31350, "V"),
+ (0x323B0, "X"),
+ (0xE0100, "I"),
+ (0xE01F0, "X"),
+ ]
+
+
+uts46data = tuple(
+ _seg_0()
+ + _seg_1()
+ + _seg_2()
+ + _seg_3()
+ + _seg_4()
+ + _seg_5()
+ + _seg_6()
+ + _seg_7()
+ + _seg_8()
+ + _seg_9()
+ + _seg_10()
+ + _seg_11()
+ + _seg_12()
+ + _seg_13()
+ + _seg_14()
+ + _seg_15()
+ + _seg_16()
+ + _seg_17()
+ + _seg_18()
+ + _seg_19()
+ + _seg_20()
+ + _seg_21()
+ + _seg_22()
+ + _seg_23()
+ + _seg_24()
+ + _seg_25()
+ + _seg_26()
+ + _seg_27()
+ + _seg_28()
+ + _seg_29()
+ + _seg_30()
+ + _seg_31()
+ + _seg_32()
+ + _seg_33()
+ + _seg_34()
+ + _seg_35()
+ + _seg_36()
+ + _seg_37()
+ + _seg_38()
+ + _seg_39()
+ + _seg_40()
+ + _seg_41()
+ + _seg_42()
+ + _seg_43()
+ + _seg_44()
+ + _seg_45()
+ + _seg_46()
+ + _seg_47()
+ + _seg_48()
+ + _seg_49()
+ + _seg_50()
+ + _seg_51()
+ + _seg_52()
+ + _seg_53()
+ + _seg_54()
+ + _seg_55()
+ + _seg_56()
+ + _seg_57()
+ + _seg_58()
+ + _seg_59()
+ + _seg_60()
+ + _seg_61()
+ + _seg_62()
+ + _seg_63()
+ + _seg_64()
+ + _seg_65()
+ + _seg_66()
+ + _seg_67()
+ + _seg_68()
+ + _seg_69()
+ + _seg_70()
+ + _seg_71()
+ + _seg_72()
+ + _seg_73()
+ + _seg_74()
+ + _seg_75()
+ + _seg_76()
+ + _seg_77()
+ + _seg_78()
+ + _seg_79()
+ + _seg_80()
+ + _seg_81()
+) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...]
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/INSTALLER b/env/Lib/site-packages/packaging-24.2.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE b/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6f62d44e4ef733c0e713afcd2371fed7f2b3de67
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE
@@ -0,0 +1,3 @@
+This software is made available under the terms of *either* of the licenses
+found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
+under the terms of *both* these licenses.
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE.APACHE b/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE.APACHE
new file mode 100644
index 0000000000000000000000000000000000000000..f433b1a53f5b830a205fd2df78e2b34974656c7b
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE.APACHE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE.BSD b/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE.BSD
new file mode 100644
index 0000000000000000000000000000000000000000..42ce7b75c92fb01a3f6ed17eea363f756b7da582
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/LICENSE.BSD
@@ -0,0 +1,23 @@
+Copyright (c) Donald Stufft and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/METADATA b/env/Lib/site-packages/packaging-24.2.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..1479c8694bfbd583a896dbe9bd33cdb6d7e7371e
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/METADATA
@@ -0,0 +1,102 @@
+Metadata-Version: 2.3
+Name: packaging
+Version: 24.2
+Summary: Core utilities for Python packages
+Author-email: Donald Stufft
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Typing :: Typed
+Project-URL: Documentation, https://packaging.pypa.io/
+Project-URL: Source, https://github.com/pypa/packaging
+
+packaging
+=========
+
+.. start-intro
+
+Reusable core utilities for various Python Packaging
+`interoperability specifications `_.
+
+This library provides utilities that implement the interoperability
+specifications which have clearly one correct behaviour (eg: :pep:`440`)
+or benefit greatly from having a single shared implementation (eg: :pep:`425`).
+
+.. end-intro
+
+The ``packaging`` project includes the following: version handling, specifiers,
+markers, requirements, tags, utilities.
+
+Documentation
+-------------
+
+The `documentation`_ provides information and the API for the following:
+
+- Version Handling
+- Specifiers
+- Markers
+- Requirements
+- Tags
+- Utilities
+
+Installation
+------------
+
+Use ``pip`` to install these utilities::
+
+ pip install packaging
+
+The ``packaging`` library uses calendar-based versioning (``YY.N``).
+
+Discussion
+----------
+
+If you run into bugs, you can file them in our `issue tracker`_.
+
+You can also join ``#pypa`` on Freenode to ask questions or get involved.
+
+
+.. _`documentation`: https://packaging.pypa.io/
+.. _`issue tracker`: https://github.com/pypa/packaging/issues
+
+
+Code of Conduct
+---------------
+
+Everyone interacting in the packaging project's codebases, issue trackers, chat
+rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
+
+.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+
+Contributing
+------------
+
+The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
+well as how to report a potential security issue. The documentation for this
+project also covers information about `project development`_ and `security`_.
+
+.. _`project development`: https://packaging.pypa.io/en/latest/development/
+.. _`security`: https://packaging.pypa.io/en/latest/security/
+
+Project History
+---------------
+
+Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
+recent changes and project history.
+
+.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
+
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/RECORD b/env/Lib/site-packages/packaging-24.2.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..556139f1a1c0a05f65248bfac6f88125e1cc8084
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/RECORD
@@ -0,0 +1,40 @@
+packaging-24.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+packaging-24.2.dist-info/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
+packaging-24.2.dist-info/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
+packaging-24.2.dist-info/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
+packaging-24.2.dist-info/METADATA,sha256=ohH86s6k5mIfQxY2TS0LcSfADeOFa4BiCC-bxZV-pNs,3204
+packaging-24.2.dist-info/RECORD,,
+packaging-24.2.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
+packaging/__init__.py,sha256=dk4Ta_vmdVJxYHDcfyhvQNw8V3PgSBomKNXqg-D2JDY,494
+packaging/__pycache__/__init__.cpython-312.pyc,,
+packaging/__pycache__/_elffile.cpython-312.pyc,,
+packaging/__pycache__/_manylinux.cpython-312.pyc,,
+packaging/__pycache__/_musllinux.cpython-312.pyc,,
+packaging/__pycache__/_parser.cpython-312.pyc,,
+packaging/__pycache__/_structures.cpython-312.pyc,,
+packaging/__pycache__/_tokenizer.cpython-312.pyc,,
+packaging/__pycache__/markers.cpython-312.pyc,,
+packaging/__pycache__/metadata.cpython-312.pyc,,
+packaging/__pycache__/requirements.cpython-312.pyc,,
+packaging/__pycache__/specifiers.cpython-312.pyc,,
+packaging/__pycache__/tags.cpython-312.pyc,,
+packaging/__pycache__/utils.cpython-312.pyc,,
+packaging/__pycache__/version.cpython-312.pyc,,
+packaging/_elffile.py,sha256=cflAQAkE25tzhYmq_aCi72QfbT_tn891tPzfpbeHOwE,3306
+packaging/_manylinux.py,sha256=vl5OCoz4kx80H5rwXKeXWjl9WNISGmr4ZgTpTP9lU9c,9612
+packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
+packaging/_parser.py,sha256=s_TvTvDNK0NrM2QB3VKThdWFM4Nc0P6JnkObkl3MjpM,10236
+packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
+packaging/_tokenizer.py,sha256=J6v5H7Jzvb-g81xp_2QACKwO7LxHQA6ikryMU7zXwN8,5273
+packaging/licenses/__init__.py,sha256=1x5M1nEYjcgwEbLt0dXwz2ukjr18DiCzC0sraQqJ-Ww,5715
+packaging/licenses/__pycache__/__init__.cpython-312.pyc,,
+packaging/licenses/__pycache__/_spdx.cpython-312.pyc,,
+packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398
+packaging/markers.py,sha256=c89TNzB7ZdGYhkovm6PYmqGyHxXlYVaLW591PHUNKD8,10561
+packaging/metadata.py,sha256=YJibM7GYe4re8-0a3OlXmGS-XDgTEoO4tlBt2q25Bng,34762
+packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
+packaging/specifiers.py,sha256=GG1wPNMcL0fMJO68vF53wKMdwnfehDcaI-r9NpTfilA,40074
+packaging/tags.py,sha256=CFqrJzAzc2XNGexerH__T-Y5Iwq7WbsYXsiLERLWxY0,21014
+packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050
+packaging/version.py,sha256=olfyuk_DPbflNkJ4wBWetXQ17c74x3DB501degUv7DY,16676
diff --git a/env/Lib/site-packages/packaging-24.2.dist-info/WHEEL b/env/Lib/site-packages/packaging-24.2.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..e3c6feefa22927866e3fd5575379ea972b432aaf
--- /dev/null
+++ b/env/Lib/site-packages/packaging-24.2.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.10.1
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/env/Lib/site-packages/packaging/__init__.py b/env/Lib/site-packages/packaging/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d79f73c574ffc759ef5d2145b1ec742d85c2500b
--- /dev/null
+++ b/env/Lib/site-packages/packaging/__init__.py
@@ -0,0 +1,15 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+__title__ = "packaging"
+__summary__ = "Core utilities for Python packages"
+__uri__ = "https://github.com/pypa/packaging"
+
+__version__ = "24.2"
+
+__author__ = "Donald Stufft and individual contributors"
+__email__ = "donald@stufft.io"
+
+__license__ = "BSD-2-Clause or Apache-2.0"
+__copyright__ = f"2014 {__author__}"
diff --git a/env/Lib/site-packages/packaging/_elffile.py b/env/Lib/site-packages/packaging/_elffile.py
new file mode 100644
index 0000000000000000000000000000000000000000..25f4282cc29cb03d7be881f03dee841d7dbc215a
--- /dev/null
+++ b/env/Lib/site-packages/packaging/_elffile.py
@@ -0,0 +1,110 @@
+"""
+ELF file parser.
+
+This provides a class ``ELFFile`` that parses an ELF executable in a similar
+interface to ``ZipFile``. Only the read interface is implemented.
+
+Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
+ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
+"""
+
+from __future__ import annotations
+
+import enum
+import os
+import struct
+from typing import IO
+
+
+class ELFInvalid(ValueError):
+ pass
+
+
+class EIClass(enum.IntEnum):
+ C32 = 1
+ C64 = 2
+
+
+class EIData(enum.IntEnum):
+ Lsb = 1
+ Msb = 2
+
+
+class EMachine(enum.IntEnum):
+ I386 = 3
+ S390 = 22
+ Arm = 40
+ X8664 = 62
+ AArc64 = 183
+
+
+class ELFFile:
+ """
+ Representation of an ELF executable.
+ """
+
+ def __init__(self, f: IO[bytes]) -> None:
+ self._f = f
+
+ try:
+ ident = self._read("16B")
+ except struct.error as e:
+ raise ELFInvalid("unable to parse identification") from e
+ magic = bytes(ident[:4])
+ if magic != b"\x7fELF":
+ raise ELFInvalid(f"invalid magic: {magic!r}")
+
+ self.capacity = ident[4] # Format for program header (bitness).
+ self.encoding = ident[5] # Data structure encoding (endianness).
+
+ try:
+ # e_fmt: Format for program header.
+ # p_fmt: Format for section header.
+ # p_idx: Indexes to find p_type, p_offset, and p_filesz.
+ e_fmt, self._p_fmt, self._p_idx = {
+ (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
+ (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
+ }[(self.capacity, self.encoding)]
+ except KeyError as e:
+ raise ELFInvalid(
+ f"unrecognized capacity ({self.capacity}) or "
+ f"encoding ({self.encoding})"
+ ) from e
+
+ try:
+ (
+ _,
+ self.machine, # Architecture type.
+ _,
+ _,
+ self._e_phoff, # Offset of program header.
+ _,
+ self.flags, # Processor-specific flags.
+ _,
+ self._e_phentsize, # Size of section.
+ self._e_phnum, # Number of sections.
+ ) = self._read(e_fmt)
+ except struct.error as e:
+ raise ELFInvalid("unable to parse machine and section information") from e
+
+ def _read(self, fmt: str) -> tuple[int, ...]:
+ return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
+
+ @property
+ def interpreter(self) -> str | None:
+ """
+ The path recorded in the ``PT_INTERP`` section header.
+ """
+ for index in range(self._e_phnum):
+ self._f.seek(self._e_phoff + self._e_phentsize * index)
+ try:
+ data = self._read(self._p_fmt)
+ except struct.error:
+ continue
+ if data[self._p_idx[0]] != 3: # Not PT_INTERP.
+ continue
+ self._f.seek(data[self._p_idx[1]])
+ return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
+ return None
diff --git a/env/Lib/site-packages/packaging/_manylinux.py b/env/Lib/site-packages/packaging/_manylinux.py
new file mode 100644
index 0000000000000000000000000000000000000000..61339a6fcc1b82803136f3bf980e0c8f574b2220
--- /dev/null
+++ b/env/Lib/site-packages/packaging/_manylinux.py
@@ -0,0 +1,263 @@
+from __future__ import annotations
+
+import collections
+import contextlib
+import functools
+import os
+import re
+import sys
+import warnings
+from typing import Generator, Iterator, NamedTuple, Sequence
+
+from ._elffile import EIClass, EIData, ELFFile, EMachine
+
+EF_ARM_ABIMASK = 0xFF000000
+EF_ARM_ABI_VER5 = 0x05000000
+EF_ARM_ABI_FLOAT_HARD = 0x00000400
+
+
+# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
+# as the type for `path` until then.
+@contextlib.contextmanager
+def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
+ try:
+ with open(path, "rb") as f:
+ yield ELFFile(f)
+ except (OSError, TypeError, ValueError):
+ yield None
+
+
+def _is_linux_armhf(executable: str) -> bool:
+ # hard-float ABI can be detected from the ELF header of the running
+ # process
+ # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
+ with _parse_elf(executable) as f:
+ return (
+ f is not None
+ and f.capacity == EIClass.C32
+ and f.encoding == EIData.Lsb
+ and f.machine == EMachine.Arm
+ and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
+ and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
+ )
+
+
+def _is_linux_i686(executable: str) -> bool:
+ with _parse_elf(executable) as f:
+ return (
+ f is not None
+ and f.capacity == EIClass.C32
+ and f.encoding == EIData.Lsb
+ and f.machine == EMachine.I386
+ )
+
+
+def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
+ if "armv7l" in archs:
+ return _is_linux_armhf(executable)
+ if "i686" in archs:
+ return _is_linux_i686(executable)
+ allowed_archs = {
+ "x86_64",
+ "aarch64",
+ "ppc64",
+ "ppc64le",
+ "s390x",
+ "loongarch64",
+ "riscv64",
+ }
+ return any(arch in allowed_archs for arch in archs)
+
+
+# If glibc ever changes its major version, we need to know what the last
+# minor version was, so we can build the complete list of all versions.
+# For now, guess what the highest minor version might be, assume it will
+# be 50 for testing. Once this actually happens, update the dictionary
+# with the actual value.
+_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
+
+
+class _GLibCVersion(NamedTuple):
+ major: int
+ minor: int
+
+
+def _glibc_version_string_confstr() -> str | None:
+ """
+ Primary implementation of glibc_version_string using os.confstr.
+ """
+ # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
+ # to be broken or missing. This strategy is used in the standard library
+ # platform module.
+ # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
+ try:
+ # Should be a string like "glibc 2.17".
+ version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
+ assert version_string is not None
+ _, version = version_string.rsplit()
+ except (AssertionError, AttributeError, OSError, ValueError):
+ # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
+ return None
+ return version
+
+
+def _glibc_version_string_ctypes() -> str | None:
+ """
+ Fallback implementation of glibc_version_string using ctypes.
+ """
+ try:
+ import ctypes
+ except ImportError:
+ return None
+
+ # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
+ # manpage says, "If filename is NULL, then the returned handle is for the
+ # main program". This way we can let the linker do the work to figure out
+ # which libc our process is actually using.
+ #
+ # We must also handle the special case where the executable is not a
+ # dynamically linked executable. This can occur when using musl libc,
+ # for example. In this situation, dlopen() will error, leading to an
+ # OSError. Interestingly, at least in the case of musl, there is no
+ # errno set on the OSError. The single string argument used to construct
+ # OSError comes from libc itself and is therefore not portable to
+ # hard code here. In any case, failure to call dlopen() means we
+ # can proceed, so we bail on our attempt.
+ try:
+ process_namespace = ctypes.CDLL(None)
+ except OSError:
+ return None
+
+ try:
+ gnu_get_libc_version = process_namespace.gnu_get_libc_version
+ except AttributeError:
+ # Symbol doesn't exist -> therefore, we are not linked to
+ # glibc.
+ return None
+
+ # Call gnu_get_libc_version, which returns a string like "2.5"
+ gnu_get_libc_version.restype = ctypes.c_char_p
+ version_str: str = gnu_get_libc_version()
+ # py2 / py3 compatibility:
+ if not isinstance(version_str, str):
+ version_str = version_str.decode("ascii")
+
+ return version_str
+
+
+def _glibc_version_string() -> str | None:
+ """Returns glibc version string, or None if not using glibc."""
+ return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
+
+
+def _parse_glibc_version(version_str: str) -> tuple[int, int]:
+ """Parse glibc version.
+
+ We use a regexp instead of str.split because we want to discard any
+ random junk that might come after the minor version -- this might happen
+ in patched/forked versions of glibc (e.g. Linaro's version of glibc
+ uses version strings like "2.20-2014.11"). See gh-3588.
+ """
+ m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str)
+ if not m:
+ warnings.warn(
+ f"Expected glibc version with 2 components major.minor,"
+ f" got: {version_str}",
+ RuntimeWarning,
+ stacklevel=2,
+ )
+ return -1, -1
+ return int(m.group("major")), int(m.group("minor"))
+
+
+@functools.lru_cache
+def _get_glibc_version() -> tuple[int, int]:
+ version_str = _glibc_version_string()
+ if version_str is None:
+ return (-1, -1)
+ return _parse_glibc_version(version_str)
+
+
+# From PEP 513, PEP 600
+def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
+ sys_glibc = _get_glibc_version()
+ if sys_glibc < version:
+ return False
+ # Check for presence of _manylinux module.
+ try:
+ import _manylinux
+ except ImportError:
+ return True
+ if hasattr(_manylinux, "manylinux_compatible"):
+ result = _manylinux.manylinux_compatible(version[0], version[1], arch)
+ if result is not None:
+ return bool(result)
+ return True
+ if version == _GLibCVersion(2, 5):
+ if hasattr(_manylinux, "manylinux1_compatible"):
+ return bool(_manylinux.manylinux1_compatible)
+ if version == _GLibCVersion(2, 12):
+ if hasattr(_manylinux, "manylinux2010_compatible"):
+ return bool(_manylinux.manylinux2010_compatible)
+ if version == _GLibCVersion(2, 17):
+ if hasattr(_manylinux, "manylinux2014_compatible"):
+ return bool(_manylinux.manylinux2014_compatible)
+ return True
+
+
+_LEGACY_MANYLINUX_MAP = {
+ # CentOS 7 w/ glibc 2.17 (PEP 599)
+ (2, 17): "manylinux2014",
+ # CentOS 6 w/ glibc 2.12 (PEP 571)
+ (2, 12): "manylinux2010",
+ # CentOS 5 w/ glibc 2.5 (PEP 513)
+ (2, 5): "manylinux1",
+}
+
+
+def platform_tags(archs: Sequence[str]) -> Iterator[str]:
+ """Generate manylinux tags compatible to the current platform.
+
+ :param archs: Sequence of compatible architectures.
+ The first one shall be the closest to the actual architecture and be the part of
+ platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
+ The ``linux_`` prefix is assumed as a prerequisite for the current platform to
+ be manylinux-compatible.
+
+ :returns: An iterator of compatible manylinux tags.
+ """
+ if not _have_compatible_abi(sys.executable, archs):
+ return
+ # Oldest glibc to be supported regardless of architecture is (2, 17).
+ too_old_glibc2 = _GLibCVersion(2, 16)
+ if set(archs) & {"x86_64", "i686"}:
+ # On x86/i686 also oldest glibc to be supported is (2, 5).
+ too_old_glibc2 = _GLibCVersion(2, 4)
+ current_glibc = _GLibCVersion(*_get_glibc_version())
+ glibc_max_list = [current_glibc]
+ # We can assume compatibility across glibc major versions.
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
+ #
+ # Build a list of maximum glibc versions so that we can
+ # output the canonical list of all glibc from current_glibc
+ # down to too_old_glibc2, including all intermediary versions.
+ for glibc_major in range(current_glibc.major - 1, 1, -1):
+ glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
+ glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
+ for arch in archs:
+ for glibc_max in glibc_max_list:
+ if glibc_max.major == too_old_glibc2.major:
+ min_minor = too_old_glibc2.minor
+ else:
+ # For other glibc major versions oldest supported is (x, 0).
+ min_minor = -1
+ for glibc_minor in range(glibc_max.minor, min_minor, -1):
+ glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
+ tag = "manylinux_{}_{}".format(*glibc_version)
+ if _is_compatible(arch, glibc_version):
+ yield f"{tag}_{arch}"
+ # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
+ if glibc_version in _LEGACY_MANYLINUX_MAP:
+ legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
+ if _is_compatible(arch, glibc_version):
+ yield f"{legacy_tag}_{arch}"
diff --git a/env/Lib/site-packages/packaging/_musllinux.py b/env/Lib/site-packages/packaging/_musllinux.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2bf30b56319ba862c5c9a1a39a87c6d1cb68718
--- /dev/null
+++ b/env/Lib/site-packages/packaging/_musllinux.py
@@ -0,0 +1,85 @@
+"""PEP 656 support.
+
+This module implements logic to detect if the currently running Python is
+linked against musl, and what musl version is used.
+"""
+
+from __future__ import annotations
+
+import functools
+import re
+import subprocess
+import sys
+from typing import Iterator, NamedTuple, Sequence
+
+from ._elffile import ELFFile
+
+
+class _MuslVersion(NamedTuple):
+ major: int
+ minor: int
+
+
+def _parse_musl_version(output: str) -> _MuslVersion | None:
+ lines = [n for n in (n.strip() for n in output.splitlines()) if n]
+ if len(lines) < 2 or lines[0][:4] != "musl":
+ return None
+ m = re.match(r"Version (\d+)\.(\d+)", lines[1])
+ if not m:
+ return None
+ return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
+
+
+@functools.lru_cache
+def _get_musl_version(executable: str) -> _MuslVersion | None:
+ """Detect currently-running musl runtime version.
+
+ This is done by checking the specified executable's dynamic linking
+ information, and invoking the loader to parse its output for a version
+ string. If the loader is musl, the output would be something like::
+
+ musl libc (x86_64)
+ Version 1.2.2
+ Dynamic Program Loader
+ """
+ try:
+ with open(executable, "rb") as f:
+ ld = ELFFile(f).interpreter
+ except (OSError, TypeError, ValueError):
+ return None
+ if ld is None or "musl" not in ld:
+ return None
+ proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
+ return _parse_musl_version(proc.stderr)
+
+
+def platform_tags(archs: Sequence[str]) -> Iterator[str]:
+ """Generate musllinux tags compatible to the current platform.
+
+ :param archs: Sequence of compatible architectures.
+ The first one shall be the closest to the actual architecture and be the part of
+ platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
+ The ``linux_`` prefix is assumed as a prerequisite for the current platform to
+ be musllinux-compatible.
+
+ :returns: An iterator of compatible musllinux tags.
+ """
+ sys_musl = _get_musl_version(sys.executable)
+ if sys_musl is None: # Python not dynamically linked against musl.
+ return
+ for arch in archs:
+ for minor in range(sys_musl.minor, -1, -1):
+ yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
+
+
+if __name__ == "__main__": # pragma: no cover
+ import sysconfig
+
+ plat = sysconfig.get_platform()
+ assert plat.startswith("linux-"), "not linux"
+
+ print("plat:", plat)
+ print("musl:", _get_musl_version(sys.executable))
+ print("tags:", end=" ")
+ for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
+ print(t, end="\n ")
diff --git a/env/Lib/site-packages/packaging/_parser.py b/env/Lib/site-packages/packaging/_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1238c06eab95f8c90c393383a703aa3b8c366a5
--- /dev/null
+++ b/env/Lib/site-packages/packaging/_parser.py
@@ -0,0 +1,354 @@
+"""Handwritten parser of dependency specifiers.
+
+The docstring for each __parse_* function contains EBNF-inspired grammar representing
+the implementation.
+"""
+
+from __future__ import annotations
+
+import ast
+from typing import NamedTuple, Sequence, Tuple, Union
+
+from ._tokenizer import DEFAULT_RULES, Tokenizer
+
+
+class Node:
+ def __init__(self, value: str) -> None:
+ self.value = value
+
+ def __str__(self) -> str:
+ return self.value
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__}('{self}')>"
+
+ def serialize(self) -> str:
+ raise NotImplementedError
+
+
+class Variable(Node):
+ def serialize(self) -> str:
+ return str(self)
+
+
+class Value(Node):
+ def serialize(self) -> str:
+ return f'"{self}"'
+
+
+class Op(Node):
+ def serialize(self) -> str:
+ return str(self)
+
+
+MarkerVar = Union[Variable, Value]
+MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
+MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
+MarkerList = Sequence[Union["MarkerList", MarkerAtom, str]]
+
+
+class ParsedRequirement(NamedTuple):
+ name: str
+ url: str
+ extras: list[str]
+ specifier: str
+ marker: MarkerList | None
+
+
+# --------------------------------------------------------------------------------------
+# Recursive descent parser for dependency specifier
+# --------------------------------------------------------------------------------------
+def parse_requirement(source: str) -> ParsedRequirement:
+ return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
+
+
+def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
+ """
+ requirement = WS? IDENTIFIER WS? extras WS? requirement_details
+ """
+ tokenizer.consume("WS")
+
+ name_token = tokenizer.expect(
+ "IDENTIFIER", expected="package name at the start of dependency specifier"
+ )
+ name = name_token.text
+ tokenizer.consume("WS")
+
+ extras = _parse_extras(tokenizer)
+ tokenizer.consume("WS")
+
+ url, specifier, marker = _parse_requirement_details(tokenizer)
+ tokenizer.expect("END", expected="end of dependency specifier")
+
+ return ParsedRequirement(name, url, extras, specifier, marker)
+
+
+def _parse_requirement_details(
+ tokenizer: Tokenizer,
+) -> tuple[str, str, MarkerList | None]:
+ """
+ requirement_details = AT URL (WS requirement_marker?)?
+ | specifier WS? (requirement_marker)?
+ """
+
+ specifier = ""
+ url = ""
+ marker = None
+
+ if tokenizer.check("AT"):
+ tokenizer.read()
+ tokenizer.consume("WS")
+
+ url_start = tokenizer.position
+ url = tokenizer.expect("URL", expected="URL after @").text
+ if tokenizer.check("END", peek=True):
+ return (url, specifier, marker)
+
+ tokenizer.expect("WS", expected="whitespace after URL")
+
+ # The input might end after whitespace.
+ if tokenizer.check("END", peek=True):
+ return (url, specifier, marker)
+
+ marker = _parse_requirement_marker(
+ tokenizer, span_start=url_start, after="URL and whitespace"
+ )
+ else:
+ specifier_start = tokenizer.position
+ specifier = _parse_specifier(tokenizer)
+ tokenizer.consume("WS")
+
+ if tokenizer.check("END", peek=True):
+ return (url, specifier, marker)
+
+ marker = _parse_requirement_marker(
+ tokenizer,
+ span_start=specifier_start,
+ after=(
+ "version specifier"
+ if specifier
+ else "name and no valid version specifier"
+ ),
+ )
+
+ return (url, specifier, marker)
+
+
+def _parse_requirement_marker(
+ tokenizer: Tokenizer, *, span_start: int, after: str
+) -> MarkerList:
+ """
+ requirement_marker = SEMICOLON marker WS?
+ """
+
+ if not tokenizer.check("SEMICOLON"):
+ tokenizer.raise_syntax_error(
+ f"Expected end or semicolon (after {after})",
+ span_start=span_start,
+ )
+ tokenizer.read()
+
+ marker = _parse_marker(tokenizer)
+ tokenizer.consume("WS")
+
+ return marker
+
+
+def _parse_extras(tokenizer: Tokenizer) -> list[str]:
+ """
+ extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
+ """
+ if not tokenizer.check("LEFT_BRACKET", peek=True):
+ return []
+
+ with tokenizer.enclosing_tokens(
+ "LEFT_BRACKET",
+ "RIGHT_BRACKET",
+ around="extras",
+ ):
+ tokenizer.consume("WS")
+ extras = _parse_extras_list(tokenizer)
+ tokenizer.consume("WS")
+
+ return extras
+
+
+def _parse_extras_list(tokenizer: Tokenizer) -> list[str]:
+ """
+ extras_list = identifier (wsp* ',' wsp* identifier)*
+ """
+ extras: list[str] = []
+
+ if not tokenizer.check("IDENTIFIER"):
+ return extras
+
+ extras.append(tokenizer.read().text)
+
+ while True:
+ tokenizer.consume("WS")
+ if tokenizer.check("IDENTIFIER", peek=True):
+ tokenizer.raise_syntax_error("Expected comma between extra names")
+ elif not tokenizer.check("COMMA"):
+ break
+
+ tokenizer.read()
+ tokenizer.consume("WS")
+
+ extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
+ extras.append(extra_token.text)
+
+ return extras
+
+
+def _parse_specifier(tokenizer: Tokenizer) -> str:
+ """
+ specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
+ | WS? version_many WS?
+ """
+ with tokenizer.enclosing_tokens(
+ "LEFT_PARENTHESIS",
+ "RIGHT_PARENTHESIS",
+ around="version specifier",
+ ):
+ tokenizer.consume("WS")
+ parsed_specifiers = _parse_version_many(tokenizer)
+ tokenizer.consume("WS")
+
+ return parsed_specifiers
+
+
+def _parse_version_many(tokenizer: Tokenizer) -> str:
+ """
+ version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
+ """
+ parsed_specifiers = ""
+ while tokenizer.check("SPECIFIER"):
+ span_start = tokenizer.position
+ parsed_specifiers += tokenizer.read().text
+ if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
+ tokenizer.raise_syntax_error(
+ ".* suffix can only be used with `==` or `!=` operators",
+ span_start=span_start,
+ span_end=tokenizer.position + 1,
+ )
+ if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
+ tokenizer.raise_syntax_error(
+ "Local version label can only be used with `==` or `!=` operators",
+ span_start=span_start,
+ span_end=tokenizer.position,
+ )
+ tokenizer.consume("WS")
+ if not tokenizer.check("COMMA"):
+ break
+ parsed_specifiers += tokenizer.read().text
+ tokenizer.consume("WS")
+
+ return parsed_specifiers
+
+
+# --------------------------------------------------------------------------------------
+# Recursive descent parser for marker expression
+# --------------------------------------------------------------------------------------
+def parse_marker(source: str) -> MarkerList:
+ return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
+
+
+def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
+ retval = _parse_marker(tokenizer)
+ tokenizer.expect("END", expected="end of marker expression")
+ return retval
+
+
+def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
+ """
+ marker = marker_atom (BOOLOP marker_atom)+
+ """
+ expression = [_parse_marker_atom(tokenizer)]
+ while tokenizer.check("BOOLOP"):
+ token = tokenizer.read()
+ expr_right = _parse_marker_atom(tokenizer)
+ expression.extend((token.text, expr_right))
+ return expression
+
+
+def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
+ """
+ marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
+ | WS? marker_item WS?
+ """
+
+ tokenizer.consume("WS")
+ if tokenizer.check("LEFT_PARENTHESIS", peek=True):
+ with tokenizer.enclosing_tokens(
+ "LEFT_PARENTHESIS",
+ "RIGHT_PARENTHESIS",
+ around="marker expression",
+ ):
+ tokenizer.consume("WS")
+ marker: MarkerAtom = _parse_marker(tokenizer)
+ tokenizer.consume("WS")
+ else:
+ marker = _parse_marker_item(tokenizer)
+ tokenizer.consume("WS")
+ return marker
+
+
+def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
+ """
+ marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
+ """
+ tokenizer.consume("WS")
+ marker_var_left = _parse_marker_var(tokenizer)
+ tokenizer.consume("WS")
+ marker_op = _parse_marker_op(tokenizer)
+ tokenizer.consume("WS")
+ marker_var_right = _parse_marker_var(tokenizer)
+ tokenizer.consume("WS")
+ return (marker_var_left, marker_op, marker_var_right)
+
+
+def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
+ """
+ marker_var = VARIABLE | QUOTED_STRING
+ """
+ if tokenizer.check("VARIABLE"):
+ return process_env_var(tokenizer.read().text.replace(".", "_"))
+ elif tokenizer.check("QUOTED_STRING"):
+ return process_python_str(tokenizer.read().text)
+ else:
+ tokenizer.raise_syntax_error(
+ message="Expected a marker variable or quoted string"
+ )
+
+
+def process_env_var(env_var: str) -> Variable:
+ if env_var in ("platform_python_implementation", "python_implementation"):
+ return Variable("platform_python_implementation")
+ else:
+ return Variable(env_var)
+
+
+def process_python_str(python_str: str) -> Value:
+ value = ast.literal_eval(python_str)
+ return Value(str(value))
+
+
+def _parse_marker_op(tokenizer: Tokenizer) -> Op:
+ """
+ marker_op = IN | NOT IN | OP
+ """
+ if tokenizer.check("IN"):
+ tokenizer.read()
+ return Op("in")
+ elif tokenizer.check("NOT"):
+ tokenizer.read()
+ tokenizer.expect("WS", expected="whitespace after 'not'")
+ tokenizer.expect("IN", expected="'in' after 'not'")
+ return Op("not in")
+ elif tokenizer.check("OP"):
+ return Op(tokenizer.read().text)
+ else:
+ return tokenizer.raise_syntax_error(
+ "Expected marker operator, one of "
+ "<=, <, !=, ==, >=, >, ~=, ===, in, not in"
+ )
diff --git a/env/Lib/site-packages/packaging/_structures.py b/env/Lib/site-packages/packaging/_structures.py
new file mode 100644
index 0000000000000000000000000000000000000000..90a6465f9682c886363eea5327dac64bf623a6ff
--- /dev/null
+++ b/env/Lib/site-packages/packaging/_structures.py
@@ -0,0 +1,61 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+
+class InfinityType:
+ def __repr__(self) -> str:
+ return "Infinity"
+
+ def __hash__(self) -> int:
+ return hash(repr(self))
+
+ def __lt__(self, other: object) -> bool:
+ return False
+
+ def __le__(self, other: object) -> bool:
+ return False
+
+ def __eq__(self, other: object) -> bool:
+ return isinstance(other, self.__class__)
+
+ def __gt__(self, other: object) -> bool:
+ return True
+
+ def __ge__(self, other: object) -> bool:
+ return True
+
+ def __neg__(self: object) -> "NegativeInfinityType":
+ return NegativeInfinity
+
+
+Infinity = InfinityType()
+
+
+class NegativeInfinityType:
+ def __repr__(self) -> str:
+ return "-Infinity"
+
+ def __hash__(self) -> int:
+ return hash(repr(self))
+
+ def __lt__(self, other: object) -> bool:
+ return True
+
+ def __le__(self, other: object) -> bool:
+ return True
+
+ def __eq__(self, other: object) -> bool:
+ return isinstance(other, self.__class__)
+
+ def __gt__(self, other: object) -> bool:
+ return False
+
+ def __ge__(self, other: object) -> bool:
+ return False
+
+ def __neg__(self: object) -> InfinityType:
+ return Infinity
+
+
+NegativeInfinity = NegativeInfinityType()
diff --git a/env/Lib/site-packages/packaging/_tokenizer.py b/env/Lib/site-packages/packaging/_tokenizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..89d041605c006e326a67f399a58a1fec8eb24acf
--- /dev/null
+++ b/env/Lib/site-packages/packaging/_tokenizer.py
@@ -0,0 +1,194 @@
+from __future__ import annotations
+
+import contextlib
+import re
+from dataclasses import dataclass
+from typing import Iterator, NoReturn
+
+from .specifiers import Specifier
+
+
+@dataclass
+class Token:
+ name: str
+ text: str
+ position: int
+
+
+class ParserSyntaxError(Exception):
+ """The provided source text could not be parsed correctly."""
+
+ def __init__(
+ self,
+ message: str,
+ *,
+ source: str,
+ span: tuple[int, int],
+ ) -> None:
+ self.span = span
+ self.message = message
+ self.source = source
+
+ super().__init__()
+
+ def __str__(self) -> str:
+ marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
+ return "\n ".join([self.message, self.source, marker])
+
+
+DEFAULT_RULES: dict[str, str | re.Pattern[str]] = {
+ "LEFT_PARENTHESIS": r"\(",
+ "RIGHT_PARENTHESIS": r"\)",
+ "LEFT_BRACKET": r"\[",
+ "RIGHT_BRACKET": r"\]",
+ "SEMICOLON": r";",
+ "COMMA": r",",
+ "QUOTED_STRING": re.compile(
+ r"""
+ (
+ ('[^']*')
+ |
+ ("[^"]*")
+ )
+ """,
+ re.VERBOSE,
+ ),
+ "OP": r"(===|==|~=|!=|<=|>=|<|>)",
+ "BOOLOP": r"\b(or|and)\b",
+ "IN": r"\bin\b",
+ "NOT": r"\bnot\b",
+ "VARIABLE": re.compile(
+ r"""
+ \b(
+ python_version
+ |python_full_version
+ |os[._]name
+ |sys[._]platform
+ |platform_(release|system)
+ |platform[._](version|machine|python_implementation)
+ |python_implementation
+ |implementation_(name|version)
+ |extra
+ )\b
+ """,
+ re.VERBOSE,
+ ),
+ "SPECIFIER": re.compile(
+ Specifier._operator_regex_str + Specifier._version_regex_str,
+ re.VERBOSE | re.IGNORECASE,
+ ),
+ "AT": r"\@",
+ "URL": r"[^ \t]+",
+ "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
+ "VERSION_PREFIX_TRAIL": r"\.\*",
+ "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
+ "WS": r"[ \t]+",
+ "END": r"$",
+}
+
+
+class Tokenizer:
+ """Context-sensitive token parsing.
+
+ Provides methods to examine the input stream to check whether the next token
+ matches.
+ """
+
+ def __init__(
+ self,
+ source: str,
+ *,
+ rules: dict[str, str | re.Pattern[str]],
+ ) -> None:
+ self.source = source
+ self.rules: dict[str, re.Pattern[str]] = {
+ name: re.compile(pattern) for name, pattern in rules.items()
+ }
+ self.next_token: Token | None = None
+ self.position = 0
+
+ def consume(self, name: str) -> None:
+ """Move beyond provided token name, if at current position."""
+ if self.check(name):
+ self.read()
+
+ def check(self, name: str, *, peek: bool = False) -> bool:
+ """Check whether the next token has the provided name.
+
+ By default, if the check succeeds, the token *must* be read before
+ another check. If `peek` is set to `True`, the token is not loaded and
+ would need to be checked again.
+ """
+ assert (
+ self.next_token is None
+ ), f"Cannot check for {name!r}, already have {self.next_token!r}"
+ assert name in self.rules, f"Unknown token name: {name!r}"
+
+ expression = self.rules[name]
+
+ match = expression.match(self.source, self.position)
+ if match is None:
+ return False
+ if not peek:
+ self.next_token = Token(name, match[0], self.position)
+ return True
+
+ def expect(self, name: str, *, expected: str) -> Token:
+ """Expect a certain token name next, failing with a syntax error otherwise.
+
+ The token is *not* read.
+ """
+ if not self.check(name):
+ raise self.raise_syntax_error(f"Expected {expected}")
+ return self.read()
+
+ def read(self) -> Token:
+ """Consume the next token and return it."""
+ token = self.next_token
+ assert token is not None
+
+ self.position += len(token.text)
+ self.next_token = None
+
+ return token
+
+ def raise_syntax_error(
+ self,
+ message: str,
+ *,
+ span_start: int | None = None,
+ span_end: int | None = None,
+ ) -> NoReturn:
+ """Raise ParserSyntaxError at the given position."""
+ span = (
+ self.position if span_start is None else span_start,
+ self.position if span_end is None else span_end,
+ )
+ raise ParserSyntaxError(
+ message,
+ source=self.source,
+ span=span,
+ )
+
+ @contextlib.contextmanager
+ def enclosing_tokens(
+ self, open_token: str, close_token: str, *, around: str
+ ) -> Iterator[None]:
+ if self.check(open_token):
+ open_position = self.position
+ self.read()
+ else:
+ open_position = None
+
+ yield
+
+ if open_position is None:
+ return
+
+ if not self.check(close_token):
+ self.raise_syntax_error(
+ f"Expected matching {close_token} for {open_token}, after {around}",
+ span_start=open_position,
+ )
+
+ self.read()
diff --git a/env/Lib/site-packages/packaging/licenses/__init__.py b/env/Lib/site-packages/packaging/licenses/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..569156d6ca47719f49b753a4781a86a924de173b
--- /dev/null
+++ b/env/Lib/site-packages/packaging/licenses/__init__.py
@@ -0,0 +1,145 @@
+#######################################################################################
+#
+# Adapted from:
+# https://github.com/pypa/hatch/blob/5352e44/backend/src/hatchling/licenses/parse.py
+#
+# MIT License
+#
+# Copyright (c) 2017-present Ofek Lev
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this
+# software and associated documentation files (the "Software"), to deal in the Software
+# without restriction, including without limitation the rights to use, copy, modify,
+# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies
+# or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+#
+# With additional allowance of arbitrary `LicenseRef-` identifiers, not just
+# `LicenseRef-Public-Domain` and `LicenseRef-Proprietary`.
+#
+#######################################################################################
+from __future__ import annotations
+
+import re
+from typing import NewType, cast
+
+from packaging.licenses._spdx import EXCEPTIONS, LICENSES
+
+__all__ = [
+ "NormalizedLicenseExpression",
+ "InvalidLicenseExpression",
+ "canonicalize_license_expression",
+]
+
+license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$")
+
+NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str)
+
+
+class InvalidLicenseExpression(ValueError):
+ """Raised when a license-expression string is invalid
+
+ >>> canonicalize_license_expression("invalid")
+ Traceback (most recent call last):
+ ...
+ packaging.licenses.InvalidLicenseExpression: Invalid license expression: 'invalid'
+ """
+
+
+def canonicalize_license_expression(
+ raw_license_expression: str,
+) -> NormalizedLicenseExpression:
+ if not raw_license_expression:
+ message = f"Invalid license expression: {raw_license_expression!r}"
+ raise InvalidLicenseExpression(message)
+
+ # Pad any parentheses so tokenization can be achieved by merely splitting on
+ # whitespace.
+ license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ")
+ licenseref_prefix = "LicenseRef-"
+ license_refs = {
+ ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :]
+ for ref in license_expression.split()
+ if ref.lower().startswith(licenseref_prefix.lower())
+ }
+
+ # Normalize to lower case so we can look up licenses/exceptions
+ # and so boolean operators are Python-compatible.
+ license_expression = license_expression.lower()
+
+ tokens = license_expression.split()
+
+ # Rather than implementing boolean logic, we create an expression that Python can
+ # parse. Everything that is not involved with the grammar itself is treated as
+ # `False` and the expression should evaluate as such.
+ python_tokens = []
+ for token in tokens:
+ if token not in {"or", "and", "with", "(", ")"}:
+ python_tokens.append("False")
+ elif token == "with":
+ python_tokens.append("or")
+ elif token == "(" and python_tokens and python_tokens[-1] not in {"or", "and"}:
+ message = f"Invalid license expression: {raw_license_expression!r}"
+ raise InvalidLicenseExpression(message)
+ else:
+ python_tokens.append(token)
+
+ python_expression = " ".join(python_tokens)
+ try:
+ invalid = eval(python_expression, globals(), locals())
+ except Exception:
+ invalid = True
+
+ if invalid is not False:
+ message = f"Invalid license expression: {raw_license_expression!r}"
+ raise InvalidLicenseExpression(message) from None
+
+ # Take a final pass to check for unknown licenses/exceptions.
+ normalized_tokens = []
+ for token in tokens:
+ if token in {"or", "and", "with", "(", ")"}:
+ normalized_tokens.append(token.upper())
+ continue
+
+ if normalized_tokens and normalized_tokens[-1] == "WITH":
+ if token not in EXCEPTIONS:
+ message = f"Unknown license exception: {token!r}"
+ raise InvalidLicenseExpression(message)
+
+ normalized_tokens.append(EXCEPTIONS[token]["id"])
+ else:
+ if token.endswith("+"):
+ final_token = token[:-1]
+ suffix = "+"
+ else:
+ final_token = token
+ suffix = ""
+
+ if final_token.startswith("licenseref-"):
+ if not license_ref_allowed.match(final_token):
+ message = f"Invalid licenseref: {final_token!r}"
+ raise InvalidLicenseExpression(message)
+ normalized_tokens.append(license_refs[final_token] + suffix)
+ else:
+ if final_token not in LICENSES:
+ message = f"Unknown license: {final_token!r}"
+ raise InvalidLicenseExpression(message)
+ normalized_tokens.append(LICENSES[final_token]["id"] + suffix)
+
+ normalized_expression = " ".join(normalized_tokens)
+
+ return cast(
+ NormalizedLicenseExpression,
+ normalized_expression.replace("( ", "(").replace(" )", ")"),
+ )
diff --git a/env/Lib/site-packages/packaging/licenses/_spdx.py b/env/Lib/site-packages/packaging/licenses/_spdx.py
new file mode 100644
index 0000000000000000000000000000000000000000..eac22276a34ccd73fc9d70c67ca318a49eb11e77
--- /dev/null
+++ b/env/Lib/site-packages/packaging/licenses/_spdx.py
@@ -0,0 +1,759 @@
+
+from __future__ import annotations
+
+from typing import TypedDict
+
+class SPDXLicense(TypedDict):
+ id: str
+ deprecated: bool
+
+class SPDXException(TypedDict):
+ id: str
+ deprecated: bool
+
+
+VERSION = '3.25.0'
+
+LICENSES: dict[str, SPDXLicense] = {
+ '0bsd': {'id': '0BSD', 'deprecated': False},
+ '3d-slicer-1.0': {'id': '3D-Slicer-1.0', 'deprecated': False},
+ 'aal': {'id': 'AAL', 'deprecated': False},
+ 'abstyles': {'id': 'Abstyles', 'deprecated': False},
+ 'adacore-doc': {'id': 'AdaCore-doc', 'deprecated': False},
+ 'adobe-2006': {'id': 'Adobe-2006', 'deprecated': False},
+ 'adobe-display-postscript': {'id': 'Adobe-Display-PostScript', 'deprecated': False},
+ 'adobe-glyph': {'id': 'Adobe-Glyph', 'deprecated': False},
+ 'adobe-utopia': {'id': 'Adobe-Utopia', 'deprecated': False},
+ 'adsl': {'id': 'ADSL', 'deprecated': False},
+ 'afl-1.1': {'id': 'AFL-1.1', 'deprecated': False},
+ 'afl-1.2': {'id': 'AFL-1.2', 'deprecated': False},
+ 'afl-2.0': {'id': 'AFL-2.0', 'deprecated': False},
+ 'afl-2.1': {'id': 'AFL-2.1', 'deprecated': False},
+ 'afl-3.0': {'id': 'AFL-3.0', 'deprecated': False},
+ 'afmparse': {'id': 'Afmparse', 'deprecated': False},
+ 'agpl-1.0': {'id': 'AGPL-1.0', 'deprecated': True},
+ 'agpl-1.0-only': {'id': 'AGPL-1.0-only', 'deprecated': False},
+ 'agpl-1.0-or-later': {'id': 'AGPL-1.0-or-later', 'deprecated': False},
+ 'agpl-3.0': {'id': 'AGPL-3.0', 'deprecated': True},
+ 'agpl-3.0-only': {'id': 'AGPL-3.0-only', 'deprecated': False},
+ 'agpl-3.0-or-later': {'id': 'AGPL-3.0-or-later', 'deprecated': False},
+ 'aladdin': {'id': 'Aladdin', 'deprecated': False},
+ 'amd-newlib': {'id': 'AMD-newlib', 'deprecated': False},
+ 'amdplpa': {'id': 'AMDPLPA', 'deprecated': False},
+ 'aml': {'id': 'AML', 'deprecated': False},
+ 'aml-glslang': {'id': 'AML-glslang', 'deprecated': False},
+ 'ampas': {'id': 'AMPAS', 'deprecated': False},
+ 'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False},
+ 'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False},
+ 'any-osi': {'id': 'any-OSI', 'deprecated': False},
+ 'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False},
+ 'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False},
+ 'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False},
+ 'apafml': {'id': 'APAFML', 'deprecated': False},
+ 'apl-1.0': {'id': 'APL-1.0', 'deprecated': False},
+ 'app-s2p': {'id': 'App-s2p', 'deprecated': False},
+ 'apsl-1.0': {'id': 'APSL-1.0', 'deprecated': False},
+ 'apsl-1.1': {'id': 'APSL-1.1', 'deprecated': False},
+ 'apsl-1.2': {'id': 'APSL-1.2', 'deprecated': False},
+ 'apsl-2.0': {'id': 'APSL-2.0', 'deprecated': False},
+ 'arphic-1999': {'id': 'Arphic-1999', 'deprecated': False},
+ 'artistic-1.0': {'id': 'Artistic-1.0', 'deprecated': False},
+ 'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False},
+ 'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False},
+ 'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False},
+ 'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False},
+ 'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False},
+ 'baekmuk': {'id': 'Baekmuk', 'deprecated': False},
+ 'bahyph': {'id': 'Bahyph', 'deprecated': False},
+ 'barr': {'id': 'Barr', 'deprecated': False},
+ 'bcrypt-solar-designer': {'id': 'bcrypt-Solar-Designer', 'deprecated': False},
+ 'beerware': {'id': 'Beerware', 'deprecated': False},
+ 'bitstream-charter': {'id': 'Bitstream-Charter', 'deprecated': False},
+ 'bitstream-vera': {'id': 'Bitstream-Vera', 'deprecated': False},
+ 'bittorrent-1.0': {'id': 'BitTorrent-1.0', 'deprecated': False},
+ 'bittorrent-1.1': {'id': 'BitTorrent-1.1', 'deprecated': False},
+ 'blessing': {'id': 'blessing', 'deprecated': False},
+ 'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False},
+ 'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False},
+ 'borceux': {'id': 'Borceux', 'deprecated': False},
+ 'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False},
+ 'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False},
+ 'bsd-1-clause': {'id': 'BSD-1-Clause', 'deprecated': False},
+ 'bsd-2-clause': {'id': 'BSD-2-Clause', 'deprecated': False},
+ 'bsd-2-clause-darwin': {'id': 'BSD-2-Clause-Darwin', 'deprecated': False},
+ 'bsd-2-clause-first-lines': {'id': 'BSD-2-Clause-first-lines', 'deprecated': False},
+ 'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True},
+ 'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True},
+ 'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False},
+ 'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False},
+ 'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False},
+ 'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False},
+ 'bsd-3-clause-attribution': {'id': 'BSD-3-Clause-Attribution', 'deprecated': False},
+ 'bsd-3-clause-clear': {'id': 'BSD-3-Clause-Clear', 'deprecated': False},
+ 'bsd-3-clause-flex': {'id': 'BSD-3-Clause-flex', 'deprecated': False},
+ 'bsd-3-clause-hp': {'id': 'BSD-3-Clause-HP', 'deprecated': False},
+ 'bsd-3-clause-lbnl': {'id': 'BSD-3-Clause-LBNL', 'deprecated': False},
+ 'bsd-3-clause-modification': {'id': 'BSD-3-Clause-Modification', 'deprecated': False},
+ 'bsd-3-clause-no-military-license': {'id': 'BSD-3-Clause-No-Military-License', 'deprecated': False},
+ 'bsd-3-clause-no-nuclear-license': {'id': 'BSD-3-Clause-No-Nuclear-License', 'deprecated': False},
+ 'bsd-3-clause-no-nuclear-license-2014': {'id': 'BSD-3-Clause-No-Nuclear-License-2014', 'deprecated': False},
+ 'bsd-3-clause-no-nuclear-warranty': {'id': 'BSD-3-Clause-No-Nuclear-Warranty', 'deprecated': False},
+ 'bsd-3-clause-open-mpi': {'id': 'BSD-3-Clause-Open-MPI', 'deprecated': False},
+ 'bsd-3-clause-sun': {'id': 'BSD-3-Clause-Sun', 'deprecated': False},
+ 'bsd-4-clause': {'id': 'BSD-4-Clause', 'deprecated': False},
+ 'bsd-4-clause-shortened': {'id': 'BSD-4-Clause-Shortened', 'deprecated': False},
+ 'bsd-4-clause-uc': {'id': 'BSD-4-Clause-UC', 'deprecated': False},
+ 'bsd-4.3reno': {'id': 'BSD-4.3RENO', 'deprecated': False},
+ 'bsd-4.3tahoe': {'id': 'BSD-4.3TAHOE', 'deprecated': False},
+ 'bsd-advertising-acknowledgement': {'id': 'BSD-Advertising-Acknowledgement', 'deprecated': False},
+ 'bsd-attribution-hpnd-disclaimer': {'id': 'BSD-Attribution-HPND-disclaimer', 'deprecated': False},
+ 'bsd-inferno-nettverk': {'id': 'BSD-Inferno-Nettverk', 'deprecated': False},
+ 'bsd-protection': {'id': 'BSD-Protection', 'deprecated': False},
+ 'bsd-source-beginning-file': {'id': 'BSD-Source-beginning-file', 'deprecated': False},
+ 'bsd-source-code': {'id': 'BSD-Source-Code', 'deprecated': False},
+ 'bsd-systemics': {'id': 'BSD-Systemics', 'deprecated': False},
+ 'bsd-systemics-w3works': {'id': 'BSD-Systemics-W3Works', 'deprecated': False},
+ 'bsl-1.0': {'id': 'BSL-1.0', 'deprecated': False},
+ 'busl-1.1': {'id': 'BUSL-1.1', 'deprecated': False},
+ 'bzip2-1.0.5': {'id': 'bzip2-1.0.5', 'deprecated': True},
+ 'bzip2-1.0.6': {'id': 'bzip2-1.0.6', 'deprecated': False},
+ 'c-uda-1.0': {'id': 'C-UDA-1.0', 'deprecated': False},
+ 'cal-1.0': {'id': 'CAL-1.0', 'deprecated': False},
+ 'cal-1.0-combined-work-exception': {'id': 'CAL-1.0-Combined-Work-Exception', 'deprecated': False},
+ 'caldera': {'id': 'Caldera', 'deprecated': False},
+ 'caldera-no-preamble': {'id': 'Caldera-no-preamble', 'deprecated': False},
+ 'catharon': {'id': 'Catharon', 'deprecated': False},
+ 'catosl-1.1': {'id': 'CATOSL-1.1', 'deprecated': False},
+ 'cc-by-1.0': {'id': 'CC-BY-1.0', 'deprecated': False},
+ 'cc-by-2.0': {'id': 'CC-BY-2.0', 'deprecated': False},
+ 'cc-by-2.5': {'id': 'CC-BY-2.5', 'deprecated': False},
+ 'cc-by-2.5-au': {'id': 'CC-BY-2.5-AU', 'deprecated': False},
+ 'cc-by-3.0': {'id': 'CC-BY-3.0', 'deprecated': False},
+ 'cc-by-3.0-at': {'id': 'CC-BY-3.0-AT', 'deprecated': False},
+ 'cc-by-3.0-au': {'id': 'CC-BY-3.0-AU', 'deprecated': False},
+ 'cc-by-3.0-de': {'id': 'CC-BY-3.0-DE', 'deprecated': False},
+ 'cc-by-3.0-igo': {'id': 'CC-BY-3.0-IGO', 'deprecated': False},
+ 'cc-by-3.0-nl': {'id': 'CC-BY-3.0-NL', 'deprecated': False},
+ 'cc-by-3.0-us': {'id': 'CC-BY-3.0-US', 'deprecated': False},
+ 'cc-by-4.0': {'id': 'CC-BY-4.0', 'deprecated': False},
+ 'cc-by-nc-1.0': {'id': 'CC-BY-NC-1.0', 'deprecated': False},
+ 'cc-by-nc-2.0': {'id': 'CC-BY-NC-2.0', 'deprecated': False},
+ 'cc-by-nc-2.5': {'id': 'CC-BY-NC-2.5', 'deprecated': False},
+ 'cc-by-nc-3.0': {'id': 'CC-BY-NC-3.0', 'deprecated': False},
+ 'cc-by-nc-3.0-de': {'id': 'CC-BY-NC-3.0-DE', 'deprecated': False},
+ 'cc-by-nc-4.0': {'id': 'CC-BY-NC-4.0', 'deprecated': False},
+ 'cc-by-nc-nd-1.0': {'id': 'CC-BY-NC-ND-1.0', 'deprecated': False},
+ 'cc-by-nc-nd-2.0': {'id': 'CC-BY-NC-ND-2.0', 'deprecated': False},
+ 'cc-by-nc-nd-2.5': {'id': 'CC-BY-NC-ND-2.5', 'deprecated': False},
+ 'cc-by-nc-nd-3.0': {'id': 'CC-BY-NC-ND-3.0', 'deprecated': False},
+ 'cc-by-nc-nd-3.0-de': {'id': 'CC-BY-NC-ND-3.0-DE', 'deprecated': False},
+ 'cc-by-nc-nd-3.0-igo': {'id': 'CC-BY-NC-ND-3.0-IGO', 'deprecated': False},
+ 'cc-by-nc-nd-4.0': {'id': 'CC-BY-NC-ND-4.0', 'deprecated': False},
+ 'cc-by-nc-sa-1.0': {'id': 'CC-BY-NC-SA-1.0', 'deprecated': False},
+ 'cc-by-nc-sa-2.0': {'id': 'CC-BY-NC-SA-2.0', 'deprecated': False},
+ 'cc-by-nc-sa-2.0-de': {'id': 'CC-BY-NC-SA-2.0-DE', 'deprecated': False},
+ 'cc-by-nc-sa-2.0-fr': {'id': 'CC-BY-NC-SA-2.0-FR', 'deprecated': False},
+ 'cc-by-nc-sa-2.0-uk': {'id': 'CC-BY-NC-SA-2.0-UK', 'deprecated': False},
+ 'cc-by-nc-sa-2.5': {'id': 'CC-BY-NC-SA-2.5', 'deprecated': False},
+ 'cc-by-nc-sa-3.0': {'id': 'CC-BY-NC-SA-3.0', 'deprecated': False},
+ 'cc-by-nc-sa-3.0-de': {'id': 'CC-BY-NC-SA-3.0-DE', 'deprecated': False},
+ 'cc-by-nc-sa-3.0-igo': {'id': 'CC-BY-NC-SA-3.0-IGO', 'deprecated': False},
+ 'cc-by-nc-sa-4.0': {'id': 'CC-BY-NC-SA-4.0', 'deprecated': False},
+ 'cc-by-nd-1.0': {'id': 'CC-BY-ND-1.0', 'deprecated': False},
+ 'cc-by-nd-2.0': {'id': 'CC-BY-ND-2.0', 'deprecated': False},
+ 'cc-by-nd-2.5': {'id': 'CC-BY-ND-2.5', 'deprecated': False},
+ 'cc-by-nd-3.0': {'id': 'CC-BY-ND-3.0', 'deprecated': False},
+ 'cc-by-nd-3.0-de': {'id': 'CC-BY-ND-3.0-DE', 'deprecated': False},
+ 'cc-by-nd-4.0': {'id': 'CC-BY-ND-4.0', 'deprecated': False},
+ 'cc-by-sa-1.0': {'id': 'CC-BY-SA-1.0', 'deprecated': False},
+ 'cc-by-sa-2.0': {'id': 'CC-BY-SA-2.0', 'deprecated': False},
+ 'cc-by-sa-2.0-uk': {'id': 'CC-BY-SA-2.0-UK', 'deprecated': False},
+ 'cc-by-sa-2.1-jp': {'id': 'CC-BY-SA-2.1-JP', 'deprecated': False},
+ 'cc-by-sa-2.5': {'id': 'CC-BY-SA-2.5', 'deprecated': False},
+ 'cc-by-sa-3.0': {'id': 'CC-BY-SA-3.0', 'deprecated': False},
+ 'cc-by-sa-3.0-at': {'id': 'CC-BY-SA-3.0-AT', 'deprecated': False},
+ 'cc-by-sa-3.0-de': {'id': 'CC-BY-SA-3.0-DE', 'deprecated': False},
+ 'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False},
+ 'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False},
+ 'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False},
+ 'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False},
+ 'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False},
+ 'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False},
+ 'cdl-1.0': {'id': 'CDL-1.0', 'deprecated': False},
+ 'cdla-permissive-1.0': {'id': 'CDLA-Permissive-1.0', 'deprecated': False},
+ 'cdla-permissive-2.0': {'id': 'CDLA-Permissive-2.0', 'deprecated': False},
+ 'cdla-sharing-1.0': {'id': 'CDLA-Sharing-1.0', 'deprecated': False},
+ 'cecill-1.0': {'id': 'CECILL-1.0', 'deprecated': False},
+ 'cecill-1.1': {'id': 'CECILL-1.1', 'deprecated': False},
+ 'cecill-2.0': {'id': 'CECILL-2.0', 'deprecated': False},
+ 'cecill-2.1': {'id': 'CECILL-2.1', 'deprecated': False},
+ 'cecill-b': {'id': 'CECILL-B', 'deprecated': False},
+ 'cecill-c': {'id': 'CECILL-C', 'deprecated': False},
+ 'cern-ohl-1.1': {'id': 'CERN-OHL-1.1', 'deprecated': False},
+ 'cern-ohl-1.2': {'id': 'CERN-OHL-1.2', 'deprecated': False},
+ 'cern-ohl-p-2.0': {'id': 'CERN-OHL-P-2.0', 'deprecated': False},
+ 'cern-ohl-s-2.0': {'id': 'CERN-OHL-S-2.0', 'deprecated': False},
+ 'cern-ohl-w-2.0': {'id': 'CERN-OHL-W-2.0', 'deprecated': False},
+ 'cfitsio': {'id': 'CFITSIO', 'deprecated': False},
+ 'check-cvs': {'id': 'check-cvs', 'deprecated': False},
+ 'checkmk': {'id': 'checkmk', 'deprecated': False},
+ 'clartistic': {'id': 'ClArtistic', 'deprecated': False},
+ 'clips': {'id': 'Clips', 'deprecated': False},
+ 'cmu-mach': {'id': 'CMU-Mach', 'deprecated': False},
+ 'cmu-mach-nodoc': {'id': 'CMU-Mach-nodoc', 'deprecated': False},
+ 'cnri-jython': {'id': 'CNRI-Jython', 'deprecated': False},
+ 'cnri-python': {'id': 'CNRI-Python', 'deprecated': False},
+ 'cnri-python-gpl-compatible': {'id': 'CNRI-Python-GPL-Compatible', 'deprecated': False},
+ 'coil-1.0': {'id': 'COIL-1.0', 'deprecated': False},
+ 'community-spec-1.0': {'id': 'Community-Spec-1.0', 'deprecated': False},
+ 'condor-1.1': {'id': 'Condor-1.1', 'deprecated': False},
+ 'copyleft-next-0.3.0': {'id': 'copyleft-next-0.3.0', 'deprecated': False},
+ 'copyleft-next-0.3.1': {'id': 'copyleft-next-0.3.1', 'deprecated': False},
+ 'cornell-lossless-jpeg': {'id': 'Cornell-Lossless-JPEG', 'deprecated': False},
+ 'cpal-1.0': {'id': 'CPAL-1.0', 'deprecated': False},
+ 'cpl-1.0': {'id': 'CPL-1.0', 'deprecated': False},
+ 'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False},
+ 'cronyx': {'id': 'Cronyx', 'deprecated': False},
+ 'crossword': {'id': 'Crossword', 'deprecated': False},
+ 'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False},
+ 'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False},
+ 'cube': {'id': 'Cube', 'deprecated': False},
+ 'curl': {'id': 'curl', 'deprecated': False},
+ 'cve-tou': {'id': 'cve-tou', 'deprecated': False},
+ 'd-fsl-1.0': {'id': 'D-FSL-1.0', 'deprecated': False},
+ 'dec-3-clause': {'id': 'DEC-3-Clause', 'deprecated': False},
+ 'diffmark': {'id': 'diffmark', 'deprecated': False},
+ 'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False},
+ 'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False},
+ 'doc': {'id': 'DOC', 'deprecated': False},
+ 'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False},
+ 'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False},
+ 'dotseqn': {'id': 'Dotseqn', 'deprecated': False},
+ 'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False},
+ 'drl-1.1': {'id': 'DRL-1.1', 'deprecated': False},
+ 'dsdp': {'id': 'DSDP', 'deprecated': False},
+ 'dtoa': {'id': 'dtoa', 'deprecated': False},
+ 'dvipdfm': {'id': 'dvipdfm', 'deprecated': False},
+ 'ecl-1.0': {'id': 'ECL-1.0', 'deprecated': False},
+ 'ecl-2.0': {'id': 'ECL-2.0', 'deprecated': False},
+ 'ecos-2.0': {'id': 'eCos-2.0', 'deprecated': True},
+ 'efl-1.0': {'id': 'EFL-1.0', 'deprecated': False},
+ 'efl-2.0': {'id': 'EFL-2.0', 'deprecated': False},
+ 'egenix': {'id': 'eGenix', 'deprecated': False},
+ 'elastic-2.0': {'id': 'Elastic-2.0', 'deprecated': False},
+ 'entessa': {'id': 'Entessa', 'deprecated': False},
+ 'epics': {'id': 'EPICS', 'deprecated': False},
+ 'epl-1.0': {'id': 'EPL-1.0', 'deprecated': False},
+ 'epl-2.0': {'id': 'EPL-2.0', 'deprecated': False},
+ 'erlpl-1.1': {'id': 'ErlPL-1.1', 'deprecated': False},
+ 'etalab-2.0': {'id': 'etalab-2.0', 'deprecated': False},
+ 'eudatagrid': {'id': 'EUDatagrid', 'deprecated': False},
+ 'eupl-1.0': {'id': 'EUPL-1.0', 'deprecated': False},
+ 'eupl-1.1': {'id': 'EUPL-1.1', 'deprecated': False},
+ 'eupl-1.2': {'id': 'EUPL-1.2', 'deprecated': False},
+ 'eurosym': {'id': 'Eurosym', 'deprecated': False},
+ 'fair': {'id': 'Fair', 'deprecated': False},
+ 'fbm': {'id': 'FBM', 'deprecated': False},
+ 'fdk-aac': {'id': 'FDK-AAC', 'deprecated': False},
+ 'ferguson-twofish': {'id': 'Ferguson-Twofish', 'deprecated': False},
+ 'frameworx-1.0': {'id': 'Frameworx-1.0', 'deprecated': False},
+ 'freebsd-doc': {'id': 'FreeBSD-DOC', 'deprecated': False},
+ 'freeimage': {'id': 'FreeImage', 'deprecated': False},
+ 'fsfap': {'id': 'FSFAP', 'deprecated': False},
+ 'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False},
+ 'fsful': {'id': 'FSFUL', 'deprecated': False},
+ 'fsfullr': {'id': 'FSFULLR', 'deprecated': False},
+ 'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False},
+ 'ftl': {'id': 'FTL', 'deprecated': False},
+ 'furuseth': {'id': 'Furuseth', 'deprecated': False},
+ 'fwlw': {'id': 'fwlw', 'deprecated': False},
+ 'gcr-docs': {'id': 'GCR-docs', 'deprecated': False},
+ 'gd': {'id': 'GD', 'deprecated': False},
+ 'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True},
+ 'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False},
+ 'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False},
+ 'gfdl-1.1-no-invariants-only': {'id': 'GFDL-1.1-no-invariants-only', 'deprecated': False},
+ 'gfdl-1.1-no-invariants-or-later': {'id': 'GFDL-1.1-no-invariants-or-later', 'deprecated': False},
+ 'gfdl-1.1-only': {'id': 'GFDL-1.1-only', 'deprecated': False},
+ 'gfdl-1.1-or-later': {'id': 'GFDL-1.1-or-later', 'deprecated': False},
+ 'gfdl-1.2': {'id': 'GFDL-1.2', 'deprecated': True},
+ 'gfdl-1.2-invariants-only': {'id': 'GFDL-1.2-invariants-only', 'deprecated': False},
+ 'gfdl-1.2-invariants-or-later': {'id': 'GFDL-1.2-invariants-or-later', 'deprecated': False},
+ 'gfdl-1.2-no-invariants-only': {'id': 'GFDL-1.2-no-invariants-only', 'deprecated': False},
+ 'gfdl-1.2-no-invariants-or-later': {'id': 'GFDL-1.2-no-invariants-or-later', 'deprecated': False},
+ 'gfdl-1.2-only': {'id': 'GFDL-1.2-only', 'deprecated': False},
+ 'gfdl-1.2-or-later': {'id': 'GFDL-1.2-or-later', 'deprecated': False},
+ 'gfdl-1.3': {'id': 'GFDL-1.3', 'deprecated': True},
+ 'gfdl-1.3-invariants-only': {'id': 'GFDL-1.3-invariants-only', 'deprecated': False},
+ 'gfdl-1.3-invariants-or-later': {'id': 'GFDL-1.3-invariants-or-later', 'deprecated': False},
+ 'gfdl-1.3-no-invariants-only': {'id': 'GFDL-1.3-no-invariants-only', 'deprecated': False},
+ 'gfdl-1.3-no-invariants-or-later': {'id': 'GFDL-1.3-no-invariants-or-later', 'deprecated': False},
+ 'gfdl-1.3-only': {'id': 'GFDL-1.3-only', 'deprecated': False},
+ 'gfdl-1.3-or-later': {'id': 'GFDL-1.3-or-later', 'deprecated': False},
+ 'giftware': {'id': 'Giftware', 'deprecated': False},
+ 'gl2ps': {'id': 'GL2PS', 'deprecated': False},
+ 'glide': {'id': 'Glide', 'deprecated': False},
+ 'glulxe': {'id': 'Glulxe', 'deprecated': False},
+ 'glwtpl': {'id': 'GLWTPL', 'deprecated': False},
+ 'gnuplot': {'id': 'gnuplot', 'deprecated': False},
+ 'gpl-1.0': {'id': 'GPL-1.0', 'deprecated': True},
+ 'gpl-1.0+': {'id': 'GPL-1.0+', 'deprecated': True},
+ 'gpl-1.0-only': {'id': 'GPL-1.0-only', 'deprecated': False},
+ 'gpl-1.0-or-later': {'id': 'GPL-1.0-or-later', 'deprecated': False},
+ 'gpl-2.0': {'id': 'GPL-2.0', 'deprecated': True},
+ 'gpl-2.0+': {'id': 'GPL-2.0+', 'deprecated': True},
+ 'gpl-2.0-only': {'id': 'GPL-2.0-only', 'deprecated': False},
+ 'gpl-2.0-or-later': {'id': 'GPL-2.0-or-later', 'deprecated': False},
+ 'gpl-2.0-with-autoconf-exception': {'id': 'GPL-2.0-with-autoconf-exception', 'deprecated': True},
+ 'gpl-2.0-with-bison-exception': {'id': 'GPL-2.0-with-bison-exception', 'deprecated': True},
+ 'gpl-2.0-with-classpath-exception': {'id': 'GPL-2.0-with-classpath-exception', 'deprecated': True},
+ 'gpl-2.0-with-font-exception': {'id': 'GPL-2.0-with-font-exception', 'deprecated': True},
+ 'gpl-2.0-with-gcc-exception': {'id': 'GPL-2.0-with-GCC-exception', 'deprecated': True},
+ 'gpl-3.0': {'id': 'GPL-3.0', 'deprecated': True},
+ 'gpl-3.0+': {'id': 'GPL-3.0+', 'deprecated': True},
+ 'gpl-3.0-only': {'id': 'GPL-3.0-only', 'deprecated': False},
+ 'gpl-3.0-or-later': {'id': 'GPL-3.0-or-later', 'deprecated': False},
+ 'gpl-3.0-with-autoconf-exception': {'id': 'GPL-3.0-with-autoconf-exception', 'deprecated': True},
+ 'gpl-3.0-with-gcc-exception': {'id': 'GPL-3.0-with-GCC-exception', 'deprecated': True},
+ 'graphics-gems': {'id': 'Graphics-Gems', 'deprecated': False},
+ 'gsoap-1.3b': {'id': 'gSOAP-1.3b', 'deprecated': False},
+ 'gtkbook': {'id': 'gtkbook', 'deprecated': False},
+ 'gutmann': {'id': 'Gutmann', 'deprecated': False},
+ 'haskellreport': {'id': 'HaskellReport', 'deprecated': False},
+ 'hdparm': {'id': 'hdparm', 'deprecated': False},
+ 'hidapi': {'id': 'HIDAPI', 'deprecated': False},
+ 'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False},
+ 'hp-1986': {'id': 'HP-1986', 'deprecated': False},
+ 'hp-1989': {'id': 'HP-1989', 'deprecated': False},
+ 'hpnd': {'id': 'HPND', 'deprecated': False},
+ 'hpnd-dec': {'id': 'HPND-DEC', 'deprecated': False},
+ 'hpnd-doc': {'id': 'HPND-doc', 'deprecated': False},
+ 'hpnd-doc-sell': {'id': 'HPND-doc-sell', 'deprecated': False},
+ 'hpnd-export-us': {'id': 'HPND-export-US', 'deprecated': False},
+ 'hpnd-export-us-acknowledgement': {'id': 'HPND-export-US-acknowledgement', 'deprecated': False},
+ 'hpnd-export-us-modify': {'id': 'HPND-export-US-modify', 'deprecated': False},
+ 'hpnd-export2-us': {'id': 'HPND-export2-US', 'deprecated': False},
+ 'hpnd-fenneberg-livingston': {'id': 'HPND-Fenneberg-Livingston', 'deprecated': False},
+ 'hpnd-inria-imag': {'id': 'HPND-INRIA-IMAG', 'deprecated': False},
+ 'hpnd-intel': {'id': 'HPND-Intel', 'deprecated': False},
+ 'hpnd-kevlin-henney': {'id': 'HPND-Kevlin-Henney', 'deprecated': False},
+ 'hpnd-markus-kuhn': {'id': 'HPND-Markus-Kuhn', 'deprecated': False},
+ 'hpnd-merchantability-variant': {'id': 'HPND-merchantability-variant', 'deprecated': False},
+ 'hpnd-mit-disclaimer': {'id': 'HPND-MIT-disclaimer', 'deprecated': False},
+ 'hpnd-netrek': {'id': 'HPND-Netrek', 'deprecated': False},
+ 'hpnd-pbmplus': {'id': 'HPND-Pbmplus', 'deprecated': False},
+ 'hpnd-sell-mit-disclaimer-xserver': {'id': 'HPND-sell-MIT-disclaimer-xserver', 'deprecated': False},
+ 'hpnd-sell-regexpr': {'id': 'HPND-sell-regexpr', 'deprecated': False},
+ 'hpnd-sell-variant': {'id': 'HPND-sell-variant', 'deprecated': False},
+ 'hpnd-sell-variant-mit-disclaimer': {'id': 'HPND-sell-variant-MIT-disclaimer', 'deprecated': False},
+ 'hpnd-sell-variant-mit-disclaimer-rev': {'id': 'HPND-sell-variant-MIT-disclaimer-rev', 'deprecated': False},
+ 'hpnd-uc': {'id': 'HPND-UC', 'deprecated': False},
+ 'hpnd-uc-export-us': {'id': 'HPND-UC-export-US', 'deprecated': False},
+ 'htmltidy': {'id': 'HTMLTIDY', 'deprecated': False},
+ 'ibm-pibs': {'id': 'IBM-pibs', 'deprecated': False},
+ 'icu': {'id': 'ICU', 'deprecated': False},
+ 'iec-code-components-eula': {'id': 'IEC-Code-Components-EULA', 'deprecated': False},
+ 'ijg': {'id': 'IJG', 'deprecated': False},
+ 'ijg-short': {'id': 'IJG-short', 'deprecated': False},
+ 'imagemagick': {'id': 'ImageMagick', 'deprecated': False},
+ 'imatix': {'id': 'iMatix', 'deprecated': False},
+ 'imlib2': {'id': 'Imlib2', 'deprecated': False},
+ 'info-zip': {'id': 'Info-ZIP', 'deprecated': False},
+ 'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False},
+ 'intel': {'id': 'Intel', 'deprecated': False},
+ 'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False},
+ 'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False},
+ 'ipa': {'id': 'IPA', 'deprecated': False},
+ 'ipl-1.0': {'id': 'IPL-1.0', 'deprecated': False},
+ 'isc': {'id': 'ISC', 'deprecated': False},
+ 'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False},
+ 'jam': {'id': 'Jam', 'deprecated': False},
+ 'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False},
+ 'jpl-image': {'id': 'JPL-image', 'deprecated': False},
+ 'jpnic': {'id': 'JPNIC', 'deprecated': False},
+ 'json': {'id': 'JSON', 'deprecated': False},
+ 'kastrup': {'id': 'Kastrup', 'deprecated': False},
+ 'kazlib': {'id': 'Kazlib', 'deprecated': False},
+ 'knuth-ctan': {'id': 'Knuth-CTAN', 'deprecated': False},
+ 'lal-1.2': {'id': 'LAL-1.2', 'deprecated': False},
+ 'lal-1.3': {'id': 'LAL-1.3', 'deprecated': False},
+ 'latex2e': {'id': 'Latex2e', 'deprecated': False},
+ 'latex2e-translated-notice': {'id': 'Latex2e-translated-notice', 'deprecated': False},
+ 'leptonica': {'id': 'Leptonica', 'deprecated': False},
+ 'lgpl-2.0': {'id': 'LGPL-2.0', 'deprecated': True},
+ 'lgpl-2.0+': {'id': 'LGPL-2.0+', 'deprecated': True},
+ 'lgpl-2.0-only': {'id': 'LGPL-2.0-only', 'deprecated': False},
+ 'lgpl-2.0-or-later': {'id': 'LGPL-2.0-or-later', 'deprecated': False},
+ 'lgpl-2.1': {'id': 'LGPL-2.1', 'deprecated': True},
+ 'lgpl-2.1+': {'id': 'LGPL-2.1+', 'deprecated': True},
+ 'lgpl-2.1-only': {'id': 'LGPL-2.1-only', 'deprecated': False},
+ 'lgpl-2.1-or-later': {'id': 'LGPL-2.1-or-later', 'deprecated': False},
+ 'lgpl-3.0': {'id': 'LGPL-3.0', 'deprecated': True},
+ 'lgpl-3.0+': {'id': 'LGPL-3.0+', 'deprecated': True},
+ 'lgpl-3.0-only': {'id': 'LGPL-3.0-only', 'deprecated': False},
+ 'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False},
+ 'lgpllr': {'id': 'LGPLLR', 'deprecated': False},
+ 'libpng': {'id': 'Libpng', 'deprecated': False},
+ 'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False},
+ 'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False},
+ 'libtiff': {'id': 'libtiff', 'deprecated': False},
+ 'libutil-david-nugent': {'id': 'libutil-David-Nugent', 'deprecated': False},
+ 'liliq-p-1.1': {'id': 'LiLiQ-P-1.1', 'deprecated': False},
+ 'liliq-r-1.1': {'id': 'LiLiQ-R-1.1', 'deprecated': False},
+ 'liliq-rplus-1.1': {'id': 'LiLiQ-Rplus-1.1', 'deprecated': False},
+ 'linux-man-pages-1-para': {'id': 'Linux-man-pages-1-para', 'deprecated': False},
+ 'linux-man-pages-copyleft': {'id': 'Linux-man-pages-copyleft', 'deprecated': False},
+ 'linux-man-pages-copyleft-2-para': {'id': 'Linux-man-pages-copyleft-2-para', 'deprecated': False},
+ 'linux-man-pages-copyleft-var': {'id': 'Linux-man-pages-copyleft-var', 'deprecated': False},
+ 'linux-openib': {'id': 'Linux-OpenIB', 'deprecated': False},
+ 'loop': {'id': 'LOOP', 'deprecated': False},
+ 'lpd-document': {'id': 'LPD-document', 'deprecated': False},
+ 'lpl-1.0': {'id': 'LPL-1.0', 'deprecated': False},
+ 'lpl-1.02': {'id': 'LPL-1.02', 'deprecated': False},
+ 'lppl-1.0': {'id': 'LPPL-1.0', 'deprecated': False},
+ 'lppl-1.1': {'id': 'LPPL-1.1', 'deprecated': False},
+ 'lppl-1.2': {'id': 'LPPL-1.2', 'deprecated': False},
+ 'lppl-1.3a': {'id': 'LPPL-1.3a', 'deprecated': False},
+ 'lppl-1.3c': {'id': 'LPPL-1.3c', 'deprecated': False},
+ 'lsof': {'id': 'lsof', 'deprecated': False},
+ 'lucida-bitmap-fonts': {'id': 'Lucida-Bitmap-Fonts', 'deprecated': False},
+ 'lzma-sdk-9.11-to-9.20': {'id': 'LZMA-SDK-9.11-to-9.20', 'deprecated': False},
+ 'lzma-sdk-9.22': {'id': 'LZMA-SDK-9.22', 'deprecated': False},
+ 'mackerras-3-clause': {'id': 'Mackerras-3-Clause', 'deprecated': False},
+ 'mackerras-3-clause-acknowledgment': {'id': 'Mackerras-3-Clause-acknowledgment', 'deprecated': False},
+ 'magaz': {'id': 'magaz', 'deprecated': False},
+ 'mailprio': {'id': 'mailprio', 'deprecated': False},
+ 'makeindex': {'id': 'MakeIndex', 'deprecated': False},
+ 'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False},
+ 'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False},
+ 'metamail': {'id': 'metamail', 'deprecated': False},
+ 'minpack': {'id': 'Minpack', 'deprecated': False},
+ 'miros': {'id': 'MirOS', 'deprecated': False},
+ 'mit': {'id': 'MIT', 'deprecated': False},
+ 'mit-0': {'id': 'MIT-0', 'deprecated': False},
+ 'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False},
+ 'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False},
+ 'mit-enna': {'id': 'MIT-enna', 'deprecated': False},
+ 'mit-feh': {'id': 'MIT-feh', 'deprecated': False},
+ 'mit-festival': {'id': 'MIT-Festival', 'deprecated': False},
+ 'mit-khronos-old': {'id': 'MIT-Khronos-old', 'deprecated': False},
+ 'mit-modern-variant': {'id': 'MIT-Modern-Variant', 'deprecated': False},
+ 'mit-open-group': {'id': 'MIT-open-group', 'deprecated': False},
+ 'mit-testregex': {'id': 'MIT-testregex', 'deprecated': False},
+ 'mit-wu': {'id': 'MIT-Wu', 'deprecated': False},
+ 'mitnfa': {'id': 'MITNFA', 'deprecated': False},
+ 'mmixware': {'id': 'MMIXware', 'deprecated': False},
+ 'motosoto': {'id': 'Motosoto', 'deprecated': False},
+ 'mpeg-ssg': {'id': 'MPEG-SSG', 'deprecated': False},
+ 'mpi-permissive': {'id': 'mpi-permissive', 'deprecated': False},
+ 'mpich2': {'id': 'mpich2', 'deprecated': False},
+ 'mpl-1.0': {'id': 'MPL-1.0', 'deprecated': False},
+ 'mpl-1.1': {'id': 'MPL-1.1', 'deprecated': False},
+ 'mpl-2.0': {'id': 'MPL-2.0', 'deprecated': False},
+ 'mpl-2.0-no-copyleft-exception': {'id': 'MPL-2.0-no-copyleft-exception', 'deprecated': False},
+ 'mplus': {'id': 'mplus', 'deprecated': False},
+ 'ms-lpl': {'id': 'MS-LPL', 'deprecated': False},
+ 'ms-pl': {'id': 'MS-PL', 'deprecated': False},
+ 'ms-rl': {'id': 'MS-RL', 'deprecated': False},
+ 'mtll': {'id': 'MTLL', 'deprecated': False},
+ 'mulanpsl-1.0': {'id': 'MulanPSL-1.0', 'deprecated': False},
+ 'mulanpsl-2.0': {'id': 'MulanPSL-2.0', 'deprecated': False},
+ 'multics': {'id': 'Multics', 'deprecated': False},
+ 'mup': {'id': 'Mup', 'deprecated': False},
+ 'naist-2003': {'id': 'NAIST-2003', 'deprecated': False},
+ 'nasa-1.3': {'id': 'NASA-1.3', 'deprecated': False},
+ 'naumen': {'id': 'Naumen', 'deprecated': False},
+ 'nbpl-1.0': {'id': 'NBPL-1.0', 'deprecated': False},
+ 'ncbi-pd': {'id': 'NCBI-PD', 'deprecated': False},
+ 'ncgl-uk-2.0': {'id': 'NCGL-UK-2.0', 'deprecated': False},
+ 'ncl': {'id': 'NCL', 'deprecated': False},
+ 'ncsa': {'id': 'NCSA', 'deprecated': False},
+ 'net-snmp': {'id': 'Net-SNMP', 'deprecated': True},
+ 'netcdf': {'id': 'NetCDF', 'deprecated': False},
+ 'newsletr': {'id': 'Newsletr', 'deprecated': False},
+ 'ngpl': {'id': 'NGPL', 'deprecated': False},
+ 'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False},
+ 'nist-pd': {'id': 'NIST-PD', 'deprecated': False},
+ 'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False},
+ 'nist-software': {'id': 'NIST-Software', 'deprecated': False},
+ 'nlod-1.0': {'id': 'NLOD-1.0', 'deprecated': False},
+ 'nlod-2.0': {'id': 'NLOD-2.0', 'deprecated': False},
+ 'nlpl': {'id': 'NLPL', 'deprecated': False},
+ 'nokia': {'id': 'Nokia', 'deprecated': False},
+ 'nosl': {'id': 'NOSL', 'deprecated': False},
+ 'noweb': {'id': 'Noweb', 'deprecated': False},
+ 'npl-1.0': {'id': 'NPL-1.0', 'deprecated': False},
+ 'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False},
+ 'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False},
+ 'nrl': {'id': 'NRL', 'deprecated': False},
+ 'ntp': {'id': 'NTP', 'deprecated': False},
+ 'ntp-0': {'id': 'NTP-0', 'deprecated': False},
+ 'nunit': {'id': 'Nunit', 'deprecated': True},
+ 'o-uda-1.0': {'id': 'O-UDA-1.0', 'deprecated': False},
+ 'oar': {'id': 'OAR', 'deprecated': False},
+ 'occt-pl': {'id': 'OCCT-PL', 'deprecated': False},
+ 'oclc-2.0': {'id': 'OCLC-2.0', 'deprecated': False},
+ 'odbl-1.0': {'id': 'ODbL-1.0', 'deprecated': False},
+ 'odc-by-1.0': {'id': 'ODC-By-1.0', 'deprecated': False},
+ 'offis': {'id': 'OFFIS', 'deprecated': False},
+ 'ofl-1.0': {'id': 'OFL-1.0', 'deprecated': False},
+ 'ofl-1.0-no-rfn': {'id': 'OFL-1.0-no-RFN', 'deprecated': False},
+ 'ofl-1.0-rfn': {'id': 'OFL-1.0-RFN', 'deprecated': False},
+ 'ofl-1.1': {'id': 'OFL-1.1', 'deprecated': False},
+ 'ofl-1.1-no-rfn': {'id': 'OFL-1.1-no-RFN', 'deprecated': False},
+ 'ofl-1.1-rfn': {'id': 'OFL-1.1-RFN', 'deprecated': False},
+ 'ogc-1.0': {'id': 'OGC-1.0', 'deprecated': False},
+ 'ogdl-taiwan-1.0': {'id': 'OGDL-Taiwan-1.0', 'deprecated': False},
+ 'ogl-canada-2.0': {'id': 'OGL-Canada-2.0', 'deprecated': False},
+ 'ogl-uk-1.0': {'id': 'OGL-UK-1.0', 'deprecated': False},
+ 'ogl-uk-2.0': {'id': 'OGL-UK-2.0', 'deprecated': False},
+ 'ogl-uk-3.0': {'id': 'OGL-UK-3.0', 'deprecated': False},
+ 'ogtsl': {'id': 'OGTSL', 'deprecated': False},
+ 'oldap-1.1': {'id': 'OLDAP-1.1', 'deprecated': False},
+ 'oldap-1.2': {'id': 'OLDAP-1.2', 'deprecated': False},
+ 'oldap-1.3': {'id': 'OLDAP-1.3', 'deprecated': False},
+ 'oldap-1.4': {'id': 'OLDAP-1.4', 'deprecated': False},
+ 'oldap-2.0': {'id': 'OLDAP-2.0', 'deprecated': False},
+ 'oldap-2.0.1': {'id': 'OLDAP-2.0.1', 'deprecated': False},
+ 'oldap-2.1': {'id': 'OLDAP-2.1', 'deprecated': False},
+ 'oldap-2.2': {'id': 'OLDAP-2.2', 'deprecated': False},
+ 'oldap-2.2.1': {'id': 'OLDAP-2.2.1', 'deprecated': False},
+ 'oldap-2.2.2': {'id': 'OLDAP-2.2.2', 'deprecated': False},
+ 'oldap-2.3': {'id': 'OLDAP-2.3', 'deprecated': False},
+ 'oldap-2.4': {'id': 'OLDAP-2.4', 'deprecated': False},
+ 'oldap-2.5': {'id': 'OLDAP-2.5', 'deprecated': False},
+ 'oldap-2.6': {'id': 'OLDAP-2.6', 'deprecated': False},
+ 'oldap-2.7': {'id': 'OLDAP-2.7', 'deprecated': False},
+ 'oldap-2.8': {'id': 'OLDAP-2.8', 'deprecated': False},
+ 'olfl-1.3': {'id': 'OLFL-1.3', 'deprecated': False},
+ 'oml': {'id': 'OML', 'deprecated': False},
+ 'openpbs-2.3': {'id': 'OpenPBS-2.3', 'deprecated': False},
+ 'openssl': {'id': 'OpenSSL', 'deprecated': False},
+ 'openssl-standalone': {'id': 'OpenSSL-standalone', 'deprecated': False},
+ 'openvision': {'id': 'OpenVision', 'deprecated': False},
+ 'opl-1.0': {'id': 'OPL-1.0', 'deprecated': False},
+ 'opl-uk-3.0': {'id': 'OPL-UK-3.0', 'deprecated': False},
+ 'opubl-1.0': {'id': 'OPUBL-1.0', 'deprecated': False},
+ 'oset-pl-2.1': {'id': 'OSET-PL-2.1', 'deprecated': False},
+ 'osl-1.0': {'id': 'OSL-1.0', 'deprecated': False},
+ 'osl-1.1': {'id': 'OSL-1.1', 'deprecated': False},
+ 'osl-2.0': {'id': 'OSL-2.0', 'deprecated': False},
+ 'osl-2.1': {'id': 'OSL-2.1', 'deprecated': False},
+ 'osl-3.0': {'id': 'OSL-3.0', 'deprecated': False},
+ 'padl': {'id': 'PADL', 'deprecated': False},
+ 'parity-6.0.0': {'id': 'Parity-6.0.0', 'deprecated': False},
+ 'parity-7.0.0': {'id': 'Parity-7.0.0', 'deprecated': False},
+ 'pddl-1.0': {'id': 'PDDL-1.0', 'deprecated': False},
+ 'php-3.0': {'id': 'PHP-3.0', 'deprecated': False},
+ 'php-3.01': {'id': 'PHP-3.01', 'deprecated': False},
+ 'pixar': {'id': 'Pixar', 'deprecated': False},
+ 'pkgconf': {'id': 'pkgconf', 'deprecated': False},
+ 'plexus': {'id': 'Plexus', 'deprecated': False},
+ 'pnmstitch': {'id': 'pnmstitch', 'deprecated': False},
+ 'polyform-noncommercial-1.0.0': {'id': 'PolyForm-Noncommercial-1.0.0', 'deprecated': False},
+ 'polyform-small-business-1.0.0': {'id': 'PolyForm-Small-Business-1.0.0', 'deprecated': False},
+ 'postgresql': {'id': 'PostgreSQL', 'deprecated': False},
+ 'ppl': {'id': 'PPL', 'deprecated': False},
+ 'psf-2.0': {'id': 'PSF-2.0', 'deprecated': False},
+ 'psfrag': {'id': 'psfrag', 'deprecated': False},
+ 'psutils': {'id': 'psutils', 'deprecated': False},
+ 'python-2.0': {'id': 'Python-2.0', 'deprecated': False},
+ 'python-2.0.1': {'id': 'Python-2.0.1', 'deprecated': False},
+ 'python-ldap': {'id': 'python-ldap', 'deprecated': False},
+ 'qhull': {'id': 'Qhull', 'deprecated': False},
+ 'qpl-1.0': {'id': 'QPL-1.0', 'deprecated': False},
+ 'qpl-1.0-inria-2004': {'id': 'QPL-1.0-INRIA-2004', 'deprecated': False},
+ 'radvd': {'id': 'radvd', 'deprecated': False},
+ 'rdisc': {'id': 'Rdisc', 'deprecated': False},
+ 'rhecos-1.1': {'id': 'RHeCos-1.1', 'deprecated': False},
+ 'rpl-1.1': {'id': 'RPL-1.1', 'deprecated': False},
+ 'rpl-1.5': {'id': 'RPL-1.5', 'deprecated': False},
+ 'rpsl-1.0': {'id': 'RPSL-1.0', 'deprecated': False},
+ 'rsa-md': {'id': 'RSA-MD', 'deprecated': False},
+ 'rscpl': {'id': 'RSCPL', 'deprecated': False},
+ 'ruby': {'id': 'Ruby', 'deprecated': False},
+ 'ruby-pty': {'id': 'Ruby-pty', 'deprecated': False},
+ 'sax-pd': {'id': 'SAX-PD', 'deprecated': False},
+ 'sax-pd-2.0': {'id': 'SAX-PD-2.0', 'deprecated': False},
+ 'saxpath': {'id': 'Saxpath', 'deprecated': False},
+ 'scea': {'id': 'SCEA', 'deprecated': False},
+ 'schemereport': {'id': 'SchemeReport', 'deprecated': False},
+ 'sendmail': {'id': 'Sendmail', 'deprecated': False},
+ 'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False},
+ 'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False},
+ 'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False},
+ 'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False},
+ 'sgi-opengl': {'id': 'SGI-OpenGL', 'deprecated': False},
+ 'sgp4': {'id': 'SGP4', 'deprecated': False},
+ 'shl-0.5': {'id': 'SHL-0.5', 'deprecated': False},
+ 'shl-0.51': {'id': 'SHL-0.51', 'deprecated': False},
+ 'simpl-2.0': {'id': 'SimPL-2.0', 'deprecated': False},
+ 'sissl': {'id': 'SISSL', 'deprecated': False},
+ 'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False},
+ 'sl': {'id': 'SL', 'deprecated': False},
+ 'sleepycat': {'id': 'Sleepycat', 'deprecated': False},
+ 'smlnj': {'id': 'SMLNJ', 'deprecated': False},
+ 'smppl': {'id': 'SMPPL', 'deprecated': False},
+ 'snia': {'id': 'SNIA', 'deprecated': False},
+ 'snprintf': {'id': 'snprintf', 'deprecated': False},
+ 'softsurfer': {'id': 'softSurfer', 'deprecated': False},
+ 'soundex': {'id': 'Soundex', 'deprecated': False},
+ 'spencer-86': {'id': 'Spencer-86', 'deprecated': False},
+ 'spencer-94': {'id': 'Spencer-94', 'deprecated': False},
+ 'spencer-99': {'id': 'Spencer-99', 'deprecated': False},
+ 'spl-1.0': {'id': 'SPL-1.0', 'deprecated': False},
+ 'ssh-keyscan': {'id': 'ssh-keyscan', 'deprecated': False},
+ 'ssh-openssh': {'id': 'SSH-OpenSSH', 'deprecated': False},
+ 'ssh-short': {'id': 'SSH-short', 'deprecated': False},
+ 'ssleay-standalone': {'id': 'SSLeay-standalone', 'deprecated': False},
+ 'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False},
+ 'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True},
+ 'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False},
+ 'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False},
+ 'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False},
+ 'sunpro': {'id': 'SunPro', 'deprecated': False},
+ 'swl': {'id': 'SWL', 'deprecated': False},
+ 'swrule': {'id': 'swrule', 'deprecated': False},
+ 'symlinks': {'id': 'Symlinks', 'deprecated': False},
+ 'tapr-ohl-1.0': {'id': 'TAPR-OHL-1.0', 'deprecated': False},
+ 'tcl': {'id': 'TCL', 'deprecated': False},
+ 'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False},
+ 'termreadkey': {'id': 'TermReadKey', 'deprecated': False},
+ 'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False},
+ 'threeparttable': {'id': 'threeparttable', 'deprecated': False},
+ 'tmate': {'id': 'TMate', 'deprecated': False},
+ 'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False},
+ 'tosl': {'id': 'TOSL', 'deprecated': False},
+ 'tpdl': {'id': 'TPDL', 'deprecated': False},
+ 'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False},
+ 'ttwl': {'id': 'TTWL', 'deprecated': False},
+ 'ttyp0': {'id': 'TTYP0', 'deprecated': False},
+ 'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False},
+ 'tu-berlin-2.0': {'id': 'TU-Berlin-2.0', 'deprecated': False},
+ 'ubuntu-font-1.0': {'id': 'Ubuntu-font-1.0', 'deprecated': False},
+ 'ucar': {'id': 'UCAR', 'deprecated': False},
+ 'ucl-1.0': {'id': 'UCL-1.0', 'deprecated': False},
+ 'ulem': {'id': 'ulem', 'deprecated': False},
+ 'umich-merit': {'id': 'UMich-Merit', 'deprecated': False},
+ 'unicode-3.0': {'id': 'Unicode-3.0', 'deprecated': False},
+ 'unicode-dfs-2015': {'id': 'Unicode-DFS-2015', 'deprecated': False},
+ 'unicode-dfs-2016': {'id': 'Unicode-DFS-2016', 'deprecated': False},
+ 'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False},
+ 'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False},
+ 'unlicense': {'id': 'Unlicense', 'deprecated': False},
+ 'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False},
+ 'urt-rle': {'id': 'URT-RLE', 'deprecated': False},
+ 'vim': {'id': 'Vim', 'deprecated': False},
+ 'vostrom': {'id': 'VOSTROM', 'deprecated': False},
+ 'vsl-1.0': {'id': 'VSL-1.0', 'deprecated': False},
+ 'w3c': {'id': 'W3C', 'deprecated': False},
+ 'w3c-19980720': {'id': 'W3C-19980720', 'deprecated': False},
+ 'w3c-20150513': {'id': 'W3C-20150513', 'deprecated': False},
+ 'w3m': {'id': 'w3m', 'deprecated': False},
+ 'watcom-1.0': {'id': 'Watcom-1.0', 'deprecated': False},
+ 'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False},
+ 'wsuipa': {'id': 'Wsuipa', 'deprecated': False},
+ 'wtfpl': {'id': 'WTFPL', 'deprecated': False},
+ 'wxwindows': {'id': 'wxWindows', 'deprecated': True},
+ 'x11': {'id': 'X11', 'deprecated': False},
+ 'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False},
+ 'x11-swapped': {'id': 'X11-swapped', 'deprecated': False},
+ 'xdebug-1.03': {'id': 'Xdebug-1.03', 'deprecated': False},
+ 'xerox': {'id': 'Xerox', 'deprecated': False},
+ 'xfig': {'id': 'Xfig', 'deprecated': False},
+ 'xfree86-1.1': {'id': 'XFree86-1.1', 'deprecated': False},
+ 'xinetd': {'id': 'xinetd', 'deprecated': False},
+ 'xkeyboard-config-zinoviev': {'id': 'xkeyboard-config-Zinoviev', 'deprecated': False},
+ 'xlock': {'id': 'xlock', 'deprecated': False},
+ 'xnet': {'id': 'Xnet', 'deprecated': False},
+ 'xpp': {'id': 'xpp', 'deprecated': False},
+ 'xskat': {'id': 'XSkat', 'deprecated': False},
+ 'xzoom': {'id': 'xzoom', 'deprecated': False},
+ 'ypl-1.0': {'id': 'YPL-1.0', 'deprecated': False},
+ 'ypl-1.1': {'id': 'YPL-1.1', 'deprecated': False},
+ 'zed': {'id': 'Zed', 'deprecated': False},
+ 'zeeff': {'id': 'Zeeff', 'deprecated': False},
+ 'zend-2.0': {'id': 'Zend-2.0', 'deprecated': False},
+ 'zimbra-1.3': {'id': 'Zimbra-1.3', 'deprecated': False},
+ 'zimbra-1.4': {'id': 'Zimbra-1.4', 'deprecated': False},
+ 'zlib': {'id': 'Zlib', 'deprecated': False},
+ 'zlib-acknowledgement': {'id': 'zlib-acknowledgement', 'deprecated': False},
+ 'zpl-1.1': {'id': 'ZPL-1.1', 'deprecated': False},
+ 'zpl-2.0': {'id': 'ZPL-2.0', 'deprecated': False},
+ 'zpl-2.1': {'id': 'ZPL-2.1', 'deprecated': False},
+}
+
+EXCEPTIONS: dict[str, SPDXException] = {
+ '389-exception': {'id': '389-exception', 'deprecated': False},
+ 'asterisk-exception': {'id': 'Asterisk-exception', 'deprecated': False},
+ 'asterisk-linking-protocols-exception': {'id': 'Asterisk-linking-protocols-exception', 'deprecated': False},
+ 'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0', 'deprecated': False},
+ 'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0', 'deprecated': False},
+ 'autoconf-exception-generic': {'id': 'Autoconf-exception-generic', 'deprecated': False},
+ 'autoconf-exception-generic-3.0': {'id': 'Autoconf-exception-generic-3.0', 'deprecated': False},
+ 'autoconf-exception-macro': {'id': 'Autoconf-exception-macro', 'deprecated': False},
+ 'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False},
+ 'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False},
+ 'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False},
+ 'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False},
+ 'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False},
+ 'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False},
+ 'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False},
+ 'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False},
+ 'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False},
+ 'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception', 'deprecated': False},
+ 'fltk-exception': {'id': 'FLTK-exception', 'deprecated': False},
+ 'fmt-exception': {'id': 'fmt-exception', 'deprecated': False},
+ 'font-exception-2.0': {'id': 'Font-exception-2.0', 'deprecated': False},
+ 'freertos-exception-2.0': {'id': 'freertos-exception-2.0', 'deprecated': False},
+ 'gcc-exception-2.0': {'id': 'GCC-exception-2.0', 'deprecated': False},
+ 'gcc-exception-2.0-note': {'id': 'GCC-exception-2.0-note', 'deprecated': False},
+ 'gcc-exception-3.1': {'id': 'GCC-exception-3.1', 'deprecated': False},
+ 'gmsh-exception': {'id': 'Gmsh-exception', 'deprecated': False},
+ 'gnat-exception': {'id': 'GNAT-exception', 'deprecated': False},
+ 'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False},
+ 'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False},
+ 'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False},
+ 'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False},
+ 'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False},
+ 'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False},
+ 'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False},
+ 'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False},
+ 'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False},
+ 'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False},
+ 'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False},
+ 'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False},
+ 'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False},
+ 'libtool-exception': {'id': 'Libtool-exception', 'deprecated': False},
+ 'linux-syscall-note': {'id': 'Linux-syscall-note', 'deprecated': False},
+ 'llgpl': {'id': 'LLGPL', 'deprecated': False},
+ 'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False},
+ 'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False},
+ 'mif-exception': {'id': 'mif-exception', 'deprecated': False},
+ 'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True},
+ 'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False},
+ 'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False},
+ 'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False},
+ 'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False},
+ 'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False},
+ 'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False},
+ 'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False},
+ 'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False},
+ 'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1', 'deprecated': False},
+ 'qwt-exception-1.0': {'id': 'Qwt-exception-1.0', 'deprecated': False},
+ 'romic-exception': {'id': 'romic-exception', 'deprecated': False},
+ 'rrdtool-floss-exception-2.0': {'id': 'RRDtool-FLOSS-exception-2.0', 'deprecated': False},
+ 'sane-exception': {'id': 'SANE-exception', 'deprecated': False},
+ 'shl-2.0': {'id': 'SHL-2.0', 'deprecated': False},
+ 'shl-2.1': {'id': 'SHL-2.1', 'deprecated': False},
+ 'stunnel-exception': {'id': 'stunnel-exception', 'deprecated': False},
+ 'swi-exception': {'id': 'SWI-exception', 'deprecated': False},
+ 'swift-exception': {'id': 'Swift-exception', 'deprecated': False},
+ 'texinfo-exception': {'id': 'Texinfo-exception', 'deprecated': False},
+ 'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0', 'deprecated': False},
+ 'ubdl-exception': {'id': 'UBDL-exception', 'deprecated': False},
+ 'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0', 'deprecated': False},
+ 'vsftpd-openssl-exception': {'id': 'vsftpd-openssl-exception', 'deprecated': False},
+ 'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1', 'deprecated': False},
+ 'x11vnc-openssl-exception': {'id': 'x11vnc-openssl-exception', 'deprecated': False},
+}
diff --git a/env/Lib/site-packages/packaging/markers.py b/env/Lib/site-packages/packaging/markers.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb7f49cf8cd43ffae71e3e8d15174d7536f9da02
--- /dev/null
+++ b/env/Lib/site-packages/packaging/markers.py
@@ -0,0 +1,331 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import operator
+import os
+import platform
+import sys
+from typing import Any, Callable, TypedDict, cast
+
+from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
+from ._parser import parse_marker as _parse_marker
+from ._tokenizer import ParserSyntaxError
+from .specifiers import InvalidSpecifier, Specifier
+from .utils import canonicalize_name
+
+__all__ = [
+ "InvalidMarker",
+ "Marker",
+ "UndefinedComparison",
+ "UndefinedEnvironmentName",
+ "default_environment",
+]
+
+Operator = Callable[[str, str], bool]
+
+
+class InvalidMarker(ValueError):
+ """
+ An invalid marker was found, users should refer to PEP 508.
+ """
+
+
+class UndefinedComparison(ValueError):
+ """
+ An invalid operation was attempted on a value that doesn't support it.
+ """
+
+
+class UndefinedEnvironmentName(ValueError):
+ """
+ A name was attempted to be used that does not exist inside of the
+ environment.
+ """
+
+
+class Environment(TypedDict):
+ implementation_name: str
+ """The implementation's identifier, e.g. ``'cpython'``."""
+
+ implementation_version: str
+ """
+ The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or
+ ``'7.3.13'`` for PyPy3.10 v7.3.13.
+ """
+
+ os_name: str
+ """
+ The value of :py:data:`os.name`. The name of the operating system dependent module
+ imported, e.g. ``'posix'``.
+ """
+
+ platform_machine: str
+ """
+ Returns the machine type, e.g. ``'i386'``.
+
+ An empty string if the value cannot be determined.
+ """
+
+ platform_release: str
+ """
+ The system's release, e.g. ``'2.2.0'`` or ``'NT'``.
+
+ An empty string if the value cannot be determined.
+ """
+
+ platform_system: str
+ """
+ The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``.
+
+ An empty string if the value cannot be determined.
+ """
+
+ platform_version: str
+ """
+ The system's release version, e.g. ``'#3 on degas'``.
+
+ An empty string if the value cannot be determined.
+ """
+
+ python_full_version: str
+ """
+ The Python version as string ``'major.minor.patchlevel'``.
+
+ Note that unlike the Python :py:data:`sys.version`, this value will always include
+ the patchlevel (it defaults to 0).
+ """
+
+ platform_python_implementation: str
+ """
+ A string identifying the Python implementation, e.g. ``'CPython'``.
+ """
+
+ python_version: str
+ """The Python version as string ``'major.minor'``."""
+
+ sys_platform: str
+ """
+ This string contains a platform identifier that can be used to append
+ platform-specific components to :py:data:`sys.path`, for instance.
+
+ For Unix systems, except on Linux and AIX, this is the lowercased OS name as
+ returned by ``uname -s`` with the first part of the version as returned by
+ ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python
+ was built.
+ """
+
+
+def _normalize_extra_values(results: Any) -> Any:
+ """
+ Normalize extra values.
+ """
+ if isinstance(results[0], tuple):
+ lhs, op, rhs = results[0]
+ if isinstance(lhs, Variable) and lhs.value == "extra":
+ normalized_extra = canonicalize_name(rhs.value)
+ rhs = Value(normalized_extra)
+ elif isinstance(rhs, Variable) and rhs.value == "extra":
+ normalized_extra = canonicalize_name(lhs.value)
+ lhs = Value(normalized_extra)
+ results[0] = lhs, op, rhs
+ return results
+
+
+def _format_marker(
+ marker: list[str] | MarkerAtom | str, first: bool | None = True
+) -> str:
+ assert isinstance(marker, (list, tuple, str))
+
+ # Sometimes we have a structure like [[...]] which is a single item list
+ # where the single item is itself it's own list. In that case we want skip
+ # the rest of this function so that we don't get extraneous () on the
+ # outside.
+ if (
+ isinstance(marker, list)
+ and len(marker) == 1
+ and isinstance(marker[0], (list, tuple))
+ ):
+ return _format_marker(marker[0])
+
+ if isinstance(marker, list):
+ inner = (_format_marker(m, first=False) for m in marker)
+ if first:
+ return " ".join(inner)
+ else:
+ return "(" + " ".join(inner) + ")"
+ elif isinstance(marker, tuple):
+ return " ".join([m.serialize() for m in marker])
+ else:
+ return marker
+
+
+_operators: dict[str, Operator] = {
+ "in": lambda lhs, rhs: lhs in rhs,
+ "not in": lambda lhs, rhs: lhs not in rhs,
+ "<": operator.lt,
+ "<=": operator.le,
+ "==": operator.eq,
+ "!=": operator.ne,
+ ">=": operator.ge,
+ ">": operator.gt,
+}
+
+
+def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
+ try:
+ spec = Specifier("".join([op.serialize(), rhs]))
+ except InvalidSpecifier:
+ pass
+ else:
+ return spec.contains(lhs, prereleases=True)
+
+ oper: Operator | None = _operators.get(op.serialize())
+ if oper is None:
+ raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
+
+ return oper(lhs, rhs)
+
+
+def _normalize(*values: str, key: str) -> tuple[str, ...]:
+ # PEP 685 – Comparison of extra names for optional distribution dependencies
+ # https://peps.python.org/pep-0685/
+ # > When comparing extra names, tools MUST normalize the names being
+ # > compared using the semantics outlined in PEP 503 for names
+ if key == "extra":
+ return tuple(canonicalize_name(v) for v in values)
+
+ # other environment markers don't have such standards
+ return values
+
+
+def _evaluate_markers(markers: MarkerList, environment: dict[str, str]) -> bool:
+ groups: list[list[bool]] = [[]]
+
+ for marker in markers:
+ assert isinstance(marker, (list, tuple, str))
+
+ if isinstance(marker, list):
+ groups[-1].append(_evaluate_markers(marker, environment))
+ elif isinstance(marker, tuple):
+ lhs, op, rhs = marker
+
+ if isinstance(lhs, Variable):
+ environment_key = lhs.value
+ lhs_value = environment[environment_key]
+ rhs_value = rhs.value
+ else:
+ lhs_value = lhs.value
+ environment_key = rhs.value
+ rhs_value = environment[environment_key]
+
+ lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
+ groups[-1].append(_eval_op(lhs_value, op, rhs_value))
+ else:
+ assert marker in ["and", "or"]
+ if marker == "or":
+ groups.append([])
+
+ return any(all(item) for item in groups)
+
+
+def format_full_version(info: sys._version_info) -> str:
+ version = f"{info.major}.{info.minor}.{info.micro}"
+ kind = info.releaselevel
+ if kind != "final":
+ version += kind[0] + str(info.serial)
+ return version
+
+
+def default_environment() -> Environment:
+ iver = format_full_version(sys.implementation.version)
+ implementation_name = sys.implementation.name
+ return {
+ "implementation_name": implementation_name,
+ "implementation_version": iver,
+ "os_name": os.name,
+ "platform_machine": platform.machine(),
+ "platform_release": platform.release(),
+ "platform_system": platform.system(),
+ "platform_version": platform.version(),
+ "python_full_version": platform.python_version(),
+ "platform_python_implementation": platform.python_implementation(),
+ "python_version": ".".join(platform.python_version_tuple()[:2]),
+ "sys_platform": sys.platform,
+ }
+
+
+class Marker:
+ def __init__(self, marker: str) -> None:
+ # Note: We create a Marker object without calling this constructor in
+ # packaging.requirements.Requirement. If any additional logic is
+ # added here, make sure to mirror/adapt Requirement.
+ try:
+ self._markers = _normalize_extra_values(_parse_marker(marker))
+ # The attribute `_markers` can be described in terms of a recursive type:
+ # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
+ #
+ # For example, the following expression:
+ # python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
+ #
+ # is parsed into:
+ # [
+ # (, ')>, ),
+ # 'and',
+ # [
+ # (, , ),
+ # 'or',
+ # (, , )
+ # ]
+ # ]
+ except ParserSyntaxError as e:
+ raise InvalidMarker(str(e)) from e
+
+ def __str__(self) -> str:
+ return _format_marker(self._markers)
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __hash__(self) -> int:
+ return hash((self.__class__.__name__, str(self)))
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, Marker):
+ return NotImplemented
+
+ return str(self) == str(other)
+
+ def evaluate(self, environment: dict[str, str] | None = None) -> bool:
+ """Evaluate a marker.
+
+ Return the boolean from evaluating the given marker against the
+ environment. environment is an optional argument to override all or
+ part of the determined environment.
+
+ The environment is determined from the current Python process.
+ """
+ current_environment = cast("dict[str, str]", default_environment())
+ current_environment["extra"] = ""
+ if environment is not None:
+ current_environment.update(environment)
+ # The API used to allow setting extra to None. We need to handle this
+ # case for backwards compatibility.
+ if current_environment["extra"] is None:
+ current_environment["extra"] = ""
+
+ return _evaluate_markers(
+ self._markers, _repair_python_full_version(current_environment)
+ )
+
+
+def _repair_python_full_version(env: dict[str, str]) -> dict[str, str]:
+ """
+ Work around platform.python_version() returning something that is not PEP 440
+ compliant for non-tagged Python builds.
+ """
+ if env["python_full_version"].endswith("+"):
+ env["python_full_version"] += "local"
+ return env
diff --git a/env/Lib/site-packages/packaging/metadata.py b/env/Lib/site-packages/packaging/metadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..721f411cfc44f6d24c13112e4246b5ad776a5e0b
--- /dev/null
+++ b/env/Lib/site-packages/packaging/metadata.py
@@ -0,0 +1,863 @@
+from __future__ import annotations
+
+import email.feedparser
+import email.header
+import email.message
+import email.parser
+import email.policy
+import pathlib
+import sys
+import typing
+from typing import (
+ Any,
+ Callable,
+ Generic,
+ Literal,
+ TypedDict,
+ cast,
+)
+
+from . import licenses, requirements, specifiers, utils
+from . import version as version_module
+from .licenses import NormalizedLicenseExpression
+
+T = typing.TypeVar("T")
+
+
+if sys.version_info >= (3, 11): # pragma: no cover
+ ExceptionGroup = ExceptionGroup
+else: # pragma: no cover
+
+ class ExceptionGroup(Exception):
+ """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
+
+ If :external:exc:`ExceptionGroup` is already defined by Python itself,
+ that version is used instead.
+ """
+
+ message: str
+ exceptions: list[Exception]
+
+ def __init__(self, message: str, exceptions: list[Exception]) -> None:
+ self.message = message
+ self.exceptions = exceptions
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
+
+
+class InvalidMetadata(ValueError):
+ """A metadata field contains invalid data."""
+
+ field: str
+ """The name of the field that contains invalid data."""
+
+ def __init__(self, field: str, message: str) -> None:
+ self.field = field
+ super().__init__(message)
+
+
+# The RawMetadata class attempts to make as few assumptions about the underlying
+# serialization formats as possible. The idea is that as long as a serialization
+# formats offer some very basic primitives in *some* way then we can support
+# serializing to and from that format.
+class RawMetadata(TypedDict, total=False):
+ """A dictionary of raw core metadata.
+
+ Each field in core metadata maps to a key of this dictionary (when data is
+ provided). The key is lower-case and underscores are used instead of dashes
+ compared to the equivalent core metadata field. Any core metadata field that
+ can be specified multiple times or can hold multiple values in a single
+ field have a key with a plural name. See :class:`Metadata` whose attributes
+ match the keys of this dictionary.
+
+ Core metadata fields that can be specified multiple times are stored as a
+ list or dict depending on which is appropriate for the field. Any fields
+ which hold multiple values in a single field are stored as a list.
+
+ """
+
+ # Metadata 1.0 - PEP 241
+ metadata_version: str
+ name: str
+ version: str
+ platforms: list[str]
+ summary: str
+ description: str
+ keywords: list[str]
+ home_page: str
+ author: str
+ author_email: str
+ license: str
+
+ # Metadata 1.1 - PEP 314
+ supported_platforms: list[str]
+ download_url: str
+ classifiers: list[str]
+ requires: list[str]
+ provides: list[str]
+ obsoletes: list[str]
+
+ # Metadata 1.2 - PEP 345
+ maintainer: str
+ maintainer_email: str
+ requires_dist: list[str]
+ provides_dist: list[str]
+ obsoletes_dist: list[str]
+ requires_python: str
+ requires_external: list[str]
+ project_urls: dict[str, str]
+
+ # Metadata 2.0
+ # PEP 426 attempted to completely revamp the metadata format
+ # but got stuck without ever being able to build consensus on
+ # it and ultimately ended up withdrawn.
+ #
+ # However, a number of tools had started emitting METADATA with
+ # `2.0` Metadata-Version, so for historical reasons, this version
+ # was skipped.
+
+ # Metadata 2.1 - PEP 566
+ description_content_type: str
+ provides_extra: list[str]
+
+ # Metadata 2.2 - PEP 643
+ dynamic: list[str]
+
+ # Metadata 2.3 - PEP 685
+ # No new fields were added in PEP 685, just some edge case were
+ # tightened up to provide better interoptability.
+
+ # Metadata 2.4 - PEP 639
+ license_expression: str
+ license_files: list[str]
+
+
+_STRING_FIELDS = {
+ "author",
+ "author_email",
+ "description",
+ "description_content_type",
+ "download_url",
+ "home_page",
+ "license",
+ "license_expression",
+ "maintainer",
+ "maintainer_email",
+ "metadata_version",
+ "name",
+ "requires_python",
+ "summary",
+ "version",
+}
+
+_LIST_FIELDS = {
+ "classifiers",
+ "dynamic",
+ "license_files",
+ "obsoletes",
+ "obsoletes_dist",
+ "platforms",
+ "provides",
+ "provides_dist",
+ "provides_extra",
+ "requires",
+ "requires_dist",
+ "requires_external",
+ "supported_platforms",
+}
+
+_DICT_FIELDS = {
+ "project_urls",
+}
+
+
+def _parse_keywords(data: str) -> list[str]:
+ """Split a string of comma-separated keywords into a list of keywords."""
+ return [k.strip() for k in data.split(",")]
+
+
+def _parse_project_urls(data: list[str]) -> dict[str, str]:
+ """Parse a list of label/URL string pairings separated by a comma."""
+ urls = {}
+ for pair in data:
+ # Our logic is slightly tricky here as we want to try and do
+ # *something* reasonable with malformed data.
+ #
+ # The main thing that we have to worry about, is data that does
+ # not have a ',' at all to split the label from the Value. There
+ # isn't a singular right answer here, and we will fail validation
+ # later on (if the caller is validating) so it doesn't *really*
+ # matter, but since the missing value has to be an empty str
+ # and our return value is dict[str, str], if we let the key
+ # be the missing value, then they'd have multiple '' values that
+ # overwrite each other in a accumulating dict.
+ #
+ # The other potentional issue is that it's possible to have the
+ # same label multiple times in the metadata, with no solid "right"
+ # answer with what to do in that case. As such, we'll do the only
+ # thing we can, which is treat the field as unparseable and add it
+ # to our list of unparsed fields.
+ parts = [p.strip() for p in pair.split(",", 1)]
+ parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
+
+ # TODO: The spec doesn't say anything about if the keys should be
+ # considered case sensitive or not... logically they should
+ # be case-preserving and case-insensitive, but doing that
+ # would open up more cases where we might have duplicate
+ # entries.
+ label, url = parts
+ if label in urls:
+ # The label already exists in our set of urls, so this field
+ # is unparseable, and we can just add the whole thing to our
+ # unparseable data and stop processing it.
+ raise KeyError("duplicate labels in project urls")
+ urls[label] = url
+
+ return urls
+
+
+def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
+ """Get the body of the message."""
+ # If our source is a str, then our caller has managed encodings for us,
+ # and we don't need to deal with it.
+ if isinstance(source, str):
+ payload = msg.get_payload()
+ assert isinstance(payload, str)
+ return payload
+ # If our source is a bytes, then we're managing the encoding and we need
+ # to deal with it.
+ else:
+ bpayload = msg.get_payload(decode=True)
+ assert isinstance(bpayload, bytes)
+ try:
+ return bpayload.decode("utf8", "strict")
+ except UnicodeDecodeError as exc:
+ raise ValueError("payload in an invalid encoding") from exc
+
+
+# The various parse_FORMAT functions here are intended to be as lenient as
+# possible in their parsing, while still returning a correctly typed
+# RawMetadata.
+#
+# To aid in this, we also generally want to do as little touching of the
+# data as possible, except where there are possibly some historic holdovers
+# that make valid data awkward to work with.
+#
+# While this is a lower level, intermediate format than our ``Metadata``
+# class, some light touch ups can make a massive difference in usability.
+
+# Map METADATA fields to RawMetadata.
+_EMAIL_TO_RAW_MAPPING = {
+ "author": "author",
+ "author-email": "author_email",
+ "classifier": "classifiers",
+ "description": "description",
+ "description-content-type": "description_content_type",
+ "download-url": "download_url",
+ "dynamic": "dynamic",
+ "home-page": "home_page",
+ "keywords": "keywords",
+ "license": "license",
+ "license-expression": "license_expression",
+ "license-file": "license_files",
+ "maintainer": "maintainer",
+ "maintainer-email": "maintainer_email",
+ "metadata-version": "metadata_version",
+ "name": "name",
+ "obsoletes": "obsoletes",
+ "obsoletes-dist": "obsoletes_dist",
+ "platform": "platforms",
+ "project-url": "project_urls",
+ "provides": "provides",
+ "provides-dist": "provides_dist",
+ "provides-extra": "provides_extra",
+ "requires": "requires",
+ "requires-dist": "requires_dist",
+ "requires-external": "requires_external",
+ "requires-python": "requires_python",
+ "summary": "summary",
+ "supported-platform": "supported_platforms",
+ "version": "version",
+}
+_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
+
+
+def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
+ """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
+
+ This function returns a two-item tuple of dicts. The first dict is of
+ recognized fields from the core metadata specification. Fields that can be
+ parsed and translated into Python's built-in types are converted
+ appropriately. All other fields are left as-is. Fields that are allowed to
+ appear multiple times are stored as lists.
+
+ The second dict contains all other fields from the metadata. This includes
+ any unrecognized fields. It also includes any fields which are expected to
+ be parsed into a built-in type but were not formatted appropriately. Finally,
+ any fields that are expected to appear only once but are repeated are
+ included in this dict.
+
+ """
+ raw: dict[str, str | list[str] | dict[str, str]] = {}
+ unparsed: dict[str, list[str]] = {}
+
+ if isinstance(data, str):
+ parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
+ else:
+ parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
+
+ # We have to wrap parsed.keys() in a set, because in the case of multiple
+ # values for a key (a list), the key will appear multiple times in the
+ # list of keys, but we're avoiding that by using get_all().
+ for name in frozenset(parsed.keys()):
+ # Header names in RFC are case insensitive, so we'll normalize to all
+ # lower case to make comparisons easier.
+ name = name.lower()
+
+ # We use get_all() here, even for fields that aren't multiple use,
+ # because otherwise someone could have e.g. two Name fields, and we
+ # would just silently ignore it rather than doing something about it.
+ headers = parsed.get_all(name) or []
+
+ # The way the email module works when parsing bytes is that it
+ # unconditionally decodes the bytes as ascii using the surrogateescape
+ # handler. When you pull that data back out (such as with get_all() ),
+ # it looks to see if the str has any surrogate escapes, and if it does
+ # it wraps it in a Header object instead of returning the string.
+ #
+ # As such, we'll look for those Header objects, and fix up the encoding.
+ value = []
+ # Flag if we have run into any issues processing the headers, thus
+ # signalling that the data belongs in 'unparsed'.
+ valid_encoding = True
+ for h in headers:
+ # It's unclear if this can return more types than just a Header or
+ # a str, so we'll just assert here to make sure.
+ assert isinstance(h, (email.header.Header, str))
+
+ # If it's a header object, we need to do our little dance to get
+ # the real data out of it. In cases where there is invalid data
+ # we're going to end up with mojibake, but there's no obvious, good
+ # way around that without reimplementing parts of the Header object
+ # ourselves.
+ #
+ # That should be fine since, if mojibacked happens, this key is
+ # going into the unparsed dict anyways.
+ if isinstance(h, email.header.Header):
+ # The Header object stores it's data as chunks, and each chunk
+ # can be independently encoded, so we'll need to check each
+ # of them.
+ chunks: list[tuple[bytes, str | None]] = []
+ for bin, encoding in email.header.decode_header(h):
+ try:
+ bin.decode("utf8", "strict")
+ except UnicodeDecodeError:
+ # Enable mojibake.
+ encoding = "latin1"
+ valid_encoding = False
+ else:
+ encoding = "utf8"
+ chunks.append((bin, encoding))
+
+ # Turn our chunks back into a Header object, then let that
+ # Header object do the right thing to turn them into a
+ # string for us.
+ value.append(str(email.header.make_header(chunks)))
+ # This is already a string, so just add it.
+ else:
+ value.append(h)
+
+ # We've processed all of our values to get them into a list of str,
+ # but we may have mojibake data, in which case this is an unparsed
+ # field.
+ if not valid_encoding:
+ unparsed[name] = value
+ continue
+
+ raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
+ if raw_name is None:
+ # This is a bit of a weird situation, we've encountered a key that
+ # we don't know what it means, so we don't know whether it's meant
+ # to be a list or not.
+ #
+ # Since we can't really tell one way or another, we'll just leave it
+ # as a list, even though it may be a single item list, because that's
+ # what makes the most sense for email headers.
+ unparsed[name] = value
+ continue
+
+ # If this is one of our string fields, then we'll check to see if our
+ # value is a list of a single item. If it is then we'll assume that
+ # it was emitted as a single string, and unwrap the str from inside
+ # the list.
+ #
+ # If it's any other kind of data, then we haven't the faintest clue
+ # what we should parse it as, and we have to just add it to our list
+ # of unparsed stuff.
+ if raw_name in _STRING_FIELDS and len(value) == 1:
+ raw[raw_name] = value[0]
+ # If this is one of our list of string fields, then we can just assign
+ # the value, since email *only* has strings, and our get_all() call
+ # above ensures that this is a list.
+ elif raw_name in _LIST_FIELDS:
+ raw[raw_name] = value
+ # Special Case: Keywords
+ # The keywords field is implemented in the metadata spec as a str,
+ # but it conceptually is a list of strings, and is serialized using
+ # ", ".join(keywords), so we'll do some light data massaging to turn
+ # this into what it logically is.
+ elif raw_name == "keywords" and len(value) == 1:
+ raw[raw_name] = _parse_keywords(value[0])
+ # Special Case: Project-URL
+ # The project urls is implemented in the metadata spec as a list of
+ # specially-formatted strings that represent a key and a value, which
+ # is fundamentally a mapping, however the email format doesn't support
+ # mappings in a sane way, so it was crammed into a list of strings
+ # instead.
+ #
+ # We will do a little light data massaging to turn this into a map as
+ # it logically should be.
+ elif raw_name == "project_urls":
+ try:
+ raw[raw_name] = _parse_project_urls(value)
+ except KeyError:
+ unparsed[name] = value
+ # Nothing that we've done has managed to parse this, so it'll just
+ # throw it in our unparseable data and move on.
+ else:
+ unparsed[name] = value
+
+ # We need to support getting the Description from the message payload in
+ # addition to getting it from the the headers. This does mean, though, there
+ # is the possibility of it being set both ways, in which case we put both
+ # in 'unparsed' since we don't know which is right.
+ try:
+ payload = _get_payload(parsed, data)
+ except ValueError:
+ unparsed.setdefault("description", []).append(
+ parsed.get_payload(decode=isinstance(data, bytes)) # type: ignore[call-overload]
+ )
+ else:
+ if payload:
+ # Check to see if we've already got a description, if so then both
+ # it, and this body move to unparseable.
+ if "description" in raw:
+ description_header = cast(str, raw.pop("description"))
+ unparsed.setdefault("description", []).extend(
+ [description_header, payload]
+ )
+ elif "description" in unparsed:
+ unparsed["description"].append(payload)
+ else:
+ raw["description"] = payload
+
+ # We need to cast our `raw` to a metadata, because a TypedDict only support
+ # literal key names, but we're computing our key names on purpose, but the
+ # way this function is implemented, our `TypedDict` can only have valid key
+ # names.
+ return cast(RawMetadata, raw), unparsed
+
+
+_NOT_FOUND = object()
+
+
+# Keep the two values in sync.
+_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"]
+_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"]
+
+_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
+
+
+class _Validator(Generic[T]):
+ """Validate a metadata field.
+
+ All _process_*() methods correspond to a core metadata field. The method is
+ called with the field's raw value. If the raw value is valid it is returned
+ in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field).
+ If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause
+ as appropriate).
+ """
+
+ name: str
+ raw_name: str
+ added: _MetadataVersion
+
+ def __init__(
+ self,
+ *,
+ added: _MetadataVersion = "1.0",
+ ) -> None:
+ self.added = added
+
+ def __set_name__(self, _owner: Metadata, name: str) -> None:
+ self.name = name
+ self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
+
+ def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T:
+ # With Python 3.8, the caching can be replaced with functools.cached_property().
+ # No need to check the cache as attribute lookup will resolve into the
+ # instance's __dict__ before __get__ is called.
+ cache = instance.__dict__
+ value = instance._raw.get(self.name)
+
+ # To make the _process_* methods easier, we'll check if the value is None
+ # and if this field is NOT a required attribute, and if both of those
+ # things are true, we'll skip the the converter. This will mean that the
+ # converters never have to deal with the None union.
+ if self.name in _REQUIRED_ATTRS or value is not None:
+ try:
+ converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}")
+ except AttributeError:
+ pass
+ else:
+ value = converter(value)
+
+ cache[self.name] = value
+ try:
+ del instance._raw[self.name] # type: ignore[misc]
+ except KeyError:
+ pass
+
+ return cast(T, value)
+
+ def _invalid_metadata(
+ self, msg: str, cause: Exception | None = None
+ ) -> InvalidMetadata:
+ exc = InvalidMetadata(
+ self.raw_name, msg.format_map({"field": repr(self.raw_name)})
+ )
+ exc.__cause__ = cause
+ return exc
+
+ def _process_metadata_version(self, value: str) -> _MetadataVersion:
+ # Implicitly makes Metadata-Version required.
+ if value not in _VALID_METADATA_VERSIONS:
+ raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
+ return cast(_MetadataVersion, value)
+
+ def _process_name(self, value: str) -> str:
+ if not value:
+ raise self._invalid_metadata("{field} is a required field")
+ # Validate the name as a side-effect.
+ try:
+ utils.canonicalize_name(value, validate=True)
+ except utils.InvalidName as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ ) from exc
+ else:
+ return value
+
+ def _process_version(self, value: str) -> version_module.Version:
+ if not value:
+ raise self._invalid_metadata("{field} is a required field")
+ try:
+ return version_module.parse(value)
+ except version_module.InvalidVersion as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ ) from exc
+
+ def _process_summary(self, value: str) -> str:
+ """Check the field contains no newlines."""
+ if "\n" in value:
+ raise self._invalid_metadata("{field} must be a single line")
+ return value
+
+ def _process_description_content_type(self, value: str) -> str:
+ content_types = {"text/plain", "text/x-rst", "text/markdown"}
+ message = email.message.EmailMessage()
+ message["content-type"] = value
+
+ content_type, parameters = (
+ # Defaults to `text/plain` if parsing failed.
+ message.get_content_type().lower(),
+ message["content-type"].params,
+ )
+ # Check if content-type is valid or defaulted to `text/plain` and thus was
+ # not parseable.
+ if content_type not in content_types or content_type not in value.lower():
+ raise self._invalid_metadata(
+ f"{{field}} must be one of {list(content_types)}, not {value!r}"
+ )
+
+ charset = parameters.get("charset", "UTF-8")
+ if charset != "UTF-8":
+ raise self._invalid_metadata(
+ f"{{field}} can only specify the UTF-8 charset, not {list(charset)}"
+ )
+
+ markdown_variants = {"GFM", "CommonMark"}
+ variant = parameters.get("variant", "GFM") # Use an acceptable default.
+ if content_type == "text/markdown" and variant not in markdown_variants:
+ raise self._invalid_metadata(
+ f"valid Markdown variants for {{field}} are {list(markdown_variants)}, "
+ f"not {variant!r}",
+ )
+ return value
+
+ def _process_dynamic(self, value: list[str]) -> list[str]:
+ for dynamic_field in map(str.lower, value):
+ if dynamic_field in {"name", "version", "metadata-version"}:
+ raise self._invalid_metadata(
+ f"{dynamic_field!r} is not allowed as a dynamic field"
+ )
+ elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
+ raise self._invalid_metadata(
+ f"{dynamic_field!r} is not a valid dynamic field"
+ )
+ return list(map(str.lower, value))
+
+ def _process_provides_extra(
+ self,
+ value: list[str],
+ ) -> list[utils.NormalizedName]:
+ normalized_names = []
+ try:
+ for name in value:
+ normalized_names.append(utils.canonicalize_name(name, validate=True))
+ except utils.InvalidName as exc:
+ raise self._invalid_metadata(
+ f"{name!r} is invalid for {{field}}", cause=exc
+ ) from exc
+ else:
+ return normalized_names
+
+ def _process_requires_python(self, value: str) -> specifiers.SpecifierSet:
+ try:
+ return specifiers.SpecifierSet(value)
+ except specifiers.InvalidSpecifier as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ ) from exc
+
+ def _process_requires_dist(
+ self,
+ value: list[str],
+ ) -> list[requirements.Requirement]:
+ reqs = []
+ try:
+ for req in value:
+ reqs.append(requirements.Requirement(req))
+ except requirements.InvalidRequirement as exc:
+ raise self._invalid_metadata(
+ f"{req!r} is invalid for {{field}}", cause=exc
+ ) from exc
+ else:
+ return reqs
+
+ def _process_license_expression(
+ self, value: str
+ ) -> NormalizedLicenseExpression | None:
+ try:
+ return licenses.canonicalize_license_expression(value)
+ except ValueError as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ ) from exc
+
+ def _process_license_files(self, value: list[str]) -> list[str]:
+ paths = []
+ for path in value:
+ if ".." in path:
+ raise self._invalid_metadata(
+ f"{path!r} is invalid for {{field}}, "
+ "parent directory indicators are not allowed"
+ )
+ if "*" in path:
+ raise self._invalid_metadata(
+ f"{path!r} is invalid for {{field}}, paths must be resolved"
+ )
+ if (
+ pathlib.PurePosixPath(path).is_absolute()
+ or pathlib.PureWindowsPath(path).is_absolute()
+ ):
+ raise self._invalid_metadata(
+ f"{path!r} is invalid for {{field}}, paths must be relative"
+ )
+ if pathlib.PureWindowsPath(path).as_posix() != path:
+ raise self._invalid_metadata(
+ f"{path!r} is invalid for {{field}}, "
+ "paths must use '/' delimiter"
+ )
+ paths.append(path)
+ return paths
+
+
+class Metadata:
+ """Representation of distribution metadata.
+
+ Compared to :class:`RawMetadata`, this class provides objects representing
+ metadata fields instead of only using built-in types. Any invalid metadata
+ will cause :exc:`InvalidMetadata` to be raised (with a
+ :py:attr:`~BaseException.__cause__` attribute as appropriate).
+ """
+
+ _raw: RawMetadata
+
+ @classmethod
+ def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata:
+ """Create an instance from :class:`RawMetadata`.
+
+ If *validate* is true, all metadata will be validated. All exceptions
+ related to validation will be gathered and raised as an :class:`ExceptionGroup`.
+ """
+ ins = cls()
+ ins._raw = data.copy() # Mutations occur due to caching enriched values.
+
+ if validate:
+ exceptions: list[Exception] = []
+ try:
+ metadata_version = ins.metadata_version
+ metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
+ except InvalidMetadata as metadata_version_exc:
+ exceptions.append(metadata_version_exc)
+ metadata_version = None
+
+ # Make sure to check for the fields that are present, the required
+ # fields (so their absence can be reported).
+ fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS
+ # Remove fields that have already been checked.
+ fields_to_check -= {"metadata_version"}
+
+ for key in fields_to_check:
+ try:
+ if metadata_version:
+ # Can't use getattr() as that triggers descriptor protocol which
+ # will fail due to no value for the instance argument.
+ try:
+ field_metadata_version = cls.__dict__[key].added
+ except KeyError:
+ exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
+ exceptions.append(exc)
+ continue
+ field_age = _VALID_METADATA_VERSIONS.index(
+ field_metadata_version
+ )
+ if field_age > metadata_age:
+ field = _RAW_TO_EMAIL_MAPPING[key]
+ exc = InvalidMetadata(
+ field,
+ f"{field} introduced in metadata version "
+ f"{field_metadata_version}, not {metadata_version}",
+ )
+ exceptions.append(exc)
+ continue
+ getattr(ins, key)
+ except InvalidMetadata as exc:
+ exceptions.append(exc)
+
+ if exceptions:
+ raise ExceptionGroup("invalid metadata", exceptions)
+
+ return ins
+
+ @classmethod
+ def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
+ """Parse metadata from email headers.
+
+ If *validate* is true, the metadata will be validated. All exceptions
+ related to validation will be gathered and raised as an :class:`ExceptionGroup`.
+ """
+ raw, unparsed = parse_email(data)
+
+ if validate:
+ exceptions: list[Exception] = []
+ for unparsed_key in unparsed:
+ if unparsed_key in _EMAIL_TO_RAW_MAPPING:
+ message = f"{unparsed_key!r} has invalid data"
+ else:
+ message = f"unrecognized field: {unparsed_key!r}"
+ exceptions.append(InvalidMetadata(unparsed_key, message))
+
+ if exceptions:
+ raise ExceptionGroup("unparsed", exceptions)
+
+ try:
+ return cls.from_raw(raw, validate=validate)
+ except ExceptionGroup as exc_group:
+ raise ExceptionGroup(
+ "invalid or unparsed metadata", exc_group.exceptions
+ ) from None
+
+ metadata_version: _Validator[_MetadataVersion] = _Validator()
+ """:external:ref:`core-metadata-metadata-version`
+ (required; validated to be a valid metadata version)"""
+ # `name` is not normalized/typed to NormalizedName so as to provide access to
+ # the original/raw name.
+ name: _Validator[str] = _Validator()
+ """:external:ref:`core-metadata-name`
+ (required; validated using :func:`~packaging.utils.canonicalize_name` and its
+ *validate* parameter)"""
+ version: _Validator[version_module.Version] = _Validator()
+ """:external:ref:`core-metadata-version` (required)"""
+ dynamic: _Validator[list[str] | None] = _Validator(
+ added="2.2",
+ )
+ """:external:ref:`core-metadata-dynamic`
+ (validated against core metadata field names and lowercased)"""
+ platforms: _Validator[list[str] | None] = _Validator()
+ """:external:ref:`core-metadata-platform`"""
+ supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1")
+ """:external:ref:`core-metadata-supported-platform`"""
+ summary: _Validator[str | None] = _Validator()
+ """:external:ref:`core-metadata-summary` (validated to contain no newlines)"""
+ description: _Validator[str | None] = _Validator() # TODO 2.1: can be in body
+ """:external:ref:`core-metadata-description`"""
+ description_content_type: _Validator[str | None] = _Validator(added="2.1")
+ """:external:ref:`core-metadata-description-content-type` (validated)"""
+ keywords: _Validator[list[str] | None] = _Validator()
+ """:external:ref:`core-metadata-keywords`"""
+ home_page: _Validator[str | None] = _Validator()
+ """:external:ref:`core-metadata-home-page`"""
+ download_url: _Validator[str | None] = _Validator(added="1.1")
+ """:external:ref:`core-metadata-download-url`"""
+ author: _Validator[str | None] = _Validator()
+ """:external:ref:`core-metadata-author`"""
+ author_email: _Validator[str | None] = _Validator()
+ """:external:ref:`core-metadata-author-email`"""
+ maintainer: _Validator[str | None] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-maintainer`"""
+ maintainer_email: _Validator[str | None] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-maintainer-email`"""
+ license: _Validator[str | None] = _Validator()
+ """:external:ref:`core-metadata-license`"""
+ license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator(
+ added="2.4"
+ )
+ """:external:ref:`core-metadata-license-expression`"""
+ license_files: _Validator[list[str] | None] = _Validator(added="2.4")
+ """:external:ref:`core-metadata-license-file`"""
+ classifiers: _Validator[list[str] | None] = _Validator(added="1.1")
+ """:external:ref:`core-metadata-classifier`"""
+ requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator(
+ added="1.2"
+ )
+ """:external:ref:`core-metadata-requires-dist`"""
+ requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator(
+ added="1.2"
+ )
+ """:external:ref:`core-metadata-requires-python`"""
+ # Because `Requires-External` allows for non-PEP 440 version specifiers, we
+ # don't do any processing on the values.
+ requires_external: _Validator[list[str] | None] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-requires-external`"""
+ project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-project-url`"""
+ # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
+ # regardless of metadata version.
+ provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator(
+ added="2.1",
+ )
+ """:external:ref:`core-metadata-provides-extra`"""
+ provides_dist: _Validator[list[str] | None] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-provides-dist`"""
+ obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-obsoletes-dist`"""
+ requires: _Validator[list[str] | None] = _Validator(added="1.1")
+ """``Requires`` (deprecated)"""
+ provides: _Validator[list[str] | None] = _Validator(added="1.1")
+ """``Provides`` (deprecated)"""
+ obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
+ """``Obsoletes`` (deprecated)"""
diff --git a/env/Lib/site-packages/packaging/py.typed b/env/Lib/site-packages/packaging/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/packaging/requirements.py b/env/Lib/site-packages/packaging/requirements.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e068c9567def3564f238a76fe7ab46b569f33e5
--- /dev/null
+++ b/env/Lib/site-packages/packaging/requirements.py
@@ -0,0 +1,91 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+from __future__ import annotations
+
+from typing import Any, Iterator
+
+from ._parser import parse_requirement as _parse_requirement
+from ._tokenizer import ParserSyntaxError
+from .markers import Marker, _normalize_extra_values
+from .specifiers import SpecifierSet
+from .utils import canonicalize_name
+
+
+class InvalidRequirement(ValueError):
+ """
+ An invalid requirement was found, users should refer to PEP 508.
+ """
+
+
+class Requirement:
+ """Parse a requirement.
+
+ Parse a given requirement string into its parts, such as name, specifier,
+ URL, and extras. Raises InvalidRequirement on a badly-formed requirement
+ string.
+ """
+
+ # TODO: Can we test whether something is contained within a requirement?
+ # If so how do we do that? Do we need to test against the _name_ of
+ # the thing as well as the version? What about the markers?
+ # TODO: Can we normalize the name and extra name?
+
+ def __init__(self, requirement_string: str) -> None:
+ try:
+ parsed = _parse_requirement(requirement_string)
+ except ParserSyntaxError as e:
+ raise InvalidRequirement(str(e)) from e
+
+ self.name: str = parsed.name
+ self.url: str | None = parsed.url or None
+ self.extras: set[str] = set(parsed.extras or [])
+ self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
+ self.marker: Marker | None = None
+ if parsed.marker is not None:
+ self.marker = Marker.__new__(Marker)
+ self.marker._markers = _normalize_extra_values(parsed.marker)
+
+ def _iter_parts(self, name: str) -> Iterator[str]:
+ yield name
+
+ if self.extras:
+ formatted_extras = ",".join(sorted(self.extras))
+ yield f"[{formatted_extras}]"
+
+ if self.specifier:
+ yield str(self.specifier)
+
+ if self.url:
+ yield f"@ {self.url}"
+ if self.marker:
+ yield " "
+
+ if self.marker:
+ yield f"; {self.marker}"
+
+ def __str__(self) -> str:
+ return "".join(self._iter_parts(self.name))
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __hash__(self) -> int:
+ return hash(
+ (
+ self.__class__.__name__,
+ *self._iter_parts(canonicalize_name(self.name)),
+ )
+ )
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, Requirement):
+ return NotImplemented
+
+ return (
+ canonicalize_name(self.name) == canonicalize_name(other.name)
+ and self.extras == other.extras
+ and self.specifier == other.specifier
+ and self.url == other.url
+ and self.marker == other.marker
+ )
diff --git a/env/Lib/site-packages/packaging/specifiers.py b/env/Lib/site-packages/packaging/specifiers.py
new file mode 100644
index 0000000000000000000000000000000000000000..b30926af8bf4f47efe98eea44d5ded4cb6f7e07d
--- /dev/null
+++ b/env/Lib/site-packages/packaging/specifiers.py
@@ -0,0 +1,1020 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+"""
+.. testsetup::
+
+ from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
+ from packaging.version import Version
+"""
+
+from __future__ import annotations
+
+import abc
+import itertools
+import re
+from typing import Callable, Iterable, Iterator, TypeVar, Union
+
+from .utils import canonicalize_version
+from .version import Version
+
+UnparsedVersion = Union[Version, str]
+UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
+CallableOperator = Callable[[Version, str], bool]
+
+
+def _coerce_version(version: UnparsedVersion) -> Version:
+ if not isinstance(version, Version):
+ version = Version(version)
+ return version
+
+
+class InvalidSpecifier(ValueError):
+ """
+ Raised when attempting to create a :class:`Specifier` with a specifier
+ string that is invalid.
+
+ >>> Specifier("lolwat")
+ Traceback (most recent call last):
+ ...
+ packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
+ """
+
+
+class BaseSpecifier(metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ def __str__(self) -> str:
+ """
+ Returns the str representation of this Specifier-like object. This
+ should be representative of the Specifier itself.
+ """
+
+ @abc.abstractmethod
+ def __hash__(self) -> int:
+ """
+ Returns a hash value for this Specifier-like object.
+ """
+
+ @abc.abstractmethod
+ def __eq__(self, other: object) -> bool:
+ """
+ Returns a boolean representing whether or not the two Specifier-like
+ objects are equal.
+
+ :param other: The other object to check against.
+ """
+
+ @property
+ @abc.abstractmethod
+ def prereleases(self) -> bool | None:
+ """Whether or not pre-releases as a whole are allowed.
+
+ This can be set to either ``True`` or ``False`` to explicitly enable or disable
+ prereleases or it can be set to ``None`` (the default) to use default semantics.
+ """
+
+ @prereleases.setter
+ def prereleases(self, value: bool) -> None:
+ """Setter for :attr:`prereleases`.
+
+ :param value: The value to set.
+ """
+
+ @abc.abstractmethod
+ def contains(self, item: str, prereleases: bool | None = None) -> bool:
+ """
+ Determines if the given item is contained within this specifier.
+ """
+
+ @abc.abstractmethod
+ def filter(
+ self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
+ ) -> Iterator[UnparsedVersionVar]:
+ """
+ Takes an iterable of items and filters them so that only items which
+ are contained within this specifier are allowed in it.
+ """
+
+
+class Specifier(BaseSpecifier):
+ """This class abstracts handling of version specifiers.
+
+ .. tip::
+
+ It is generally not required to instantiate this manually. You should instead
+ prefer to work with :class:`SpecifierSet` instead, which can parse
+ comma-separated version specifiers (which is what package metadata contains).
+ """
+
+ _operator_regex_str = r"""
+ (?P(~=|==|!=|<=|>=|<|>|===))
+ """
+ _version_regex_str = r"""
+ (?P
+ (?:
+ # The identity operators allow for an escape hatch that will
+ # do an exact string match of the version you wish to install.
+ # This will not be parsed by PEP 440 and we cannot determine
+ # any semantic meaning from it. This operator is discouraged
+ # but included entirely as an escape hatch.
+ (?<====) # Only match for the identity operator
+ \s*
+ [^\s;)]* # The arbitrary version can be just about anything,
+ # we match everything except for whitespace, a
+ # semi-colon for marker support, and a closing paren
+ # since versions can be enclosed in them.
+ )
+ |
+ (?:
+ # The (non)equality operators allow for wild card and local
+ # versions to be specified so we have to define these two
+ # operators separately to enable that.
+ (?<===|!=) # Only match for equals and not equals
+
+ \s*
+ v?
+ (?:[0-9]+!)? # epoch
+ [0-9]+(?:\.[0-9]+)* # release
+
+ # You cannot use a wild card and a pre-release, post-release, a dev or
+ # local version together so group them with a | and make them optional.
+ (?:
+ \.\* # Wild card syntax of .*
+ |
+ (?: # pre release
+ [-_\.]?
+ (alpha|beta|preview|pre|a|b|c|rc)
+ [-_\.]?
+ [0-9]*
+ )?
+ (?: # post release
+ (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
+ )?
+ (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
+ (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
+ )?
+ )
+ |
+ (?:
+ # The compatible operator requires at least two digits in the
+ # release segment.
+ (?<=~=) # Only match for the compatible operator
+
+ \s*
+ v?
+ (?:[0-9]+!)? # epoch
+ [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
+ (?: # pre release
+ [-_\.]?
+ (alpha|beta|preview|pre|a|b|c|rc)
+ [-_\.]?
+ [0-9]*
+ )?
+ (?: # post release
+ (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
+ )?
+ (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
+ )
+ |
+ (?:
+ # All other operators only allow a sub set of what the
+ # (non)equality operators do. Specifically they do not allow
+ # local versions to be specified nor do they allow the prefix
+ # matching wild cards.
+ (?=": "greater_than_equal",
+ "<": "less_than",
+ ">": "greater_than",
+ "===": "arbitrary",
+ }
+
+ def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
+ """Initialize a Specifier instance.
+
+ :param spec:
+ The string representation of a specifier which will be parsed and
+ normalized before use.
+ :param prereleases:
+ This tells the specifier if it should accept prerelease versions if
+ applicable or not. The default of ``None`` will autodetect it from the
+ given specifiers.
+ :raises InvalidSpecifier:
+ If the given specifier is invalid (i.e. bad syntax).
+ """
+ match = self._regex.search(spec)
+ if not match:
+ raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
+
+ self._spec: tuple[str, str] = (
+ match.group("operator").strip(),
+ match.group("version").strip(),
+ )
+
+ # Store whether or not this Specifier should accept prereleases
+ self._prereleases = prereleases
+
+ # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
+ @property # type: ignore[override]
+ def prereleases(self) -> bool:
+ # If there is an explicit prereleases set for this, then we'll just
+ # blindly use that.
+ if self._prereleases is not None:
+ return self._prereleases
+
+ # Look at all of our specifiers and determine if they are inclusive
+ # operators, and if they are if they are including an explicit
+ # prerelease.
+ operator, version = self._spec
+ if operator in ["==", ">=", "<=", "~=", "===", ">", "<"]:
+ # The == specifier can include a trailing .*, if it does we
+ # want to remove before parsing.
+ if operator == "==" and version.endswith(".*"):
+ version = version[:-2]
+
+ # Parse the version, and if it is a pre-release than this
+ # specifier allows pre-releases.
+ if Version(version).is_prerelease:
+ return True
+
+ return False
+
+ @prereleases.setter
+ def prereleases(self, value: bool) -> None:
+ self._prereleases = value
+
+ @property
+ def operator(self) -> str:
+ """The operator of this specifier.
+
+ >>> Specifier("==1.2.3").operator
+ '=='
+ """
+ return self._spec[0]
+
+ @property
+ def version(self) -> str:
+ """The version of this specifier.
+
+ >>> Specifier("==1.2.3").version
+ '1.2.3'
+ """
+ return self._spec[1]
+
+ def __repr__(self) -> str:
+ """A representation of the Specifier that shows all internal state.
+
+ >>> Specifier('>=1.0.0')
+ =1.0.0')>
+ >>> Specifier('>=1.0.0', prereleases=False)
+ =1.0.0', prereleases=False)>
+ >>> Specifier('>=1.0.0', prereleases=True)
+ =1.0.0', prereleases=True)>
+ """
+ pre = (
+ f", prereleases={self.prereleases!r}"
+ if self._prereleases is not None
+ else ""
+ )
+
+ return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
+
+ def __str__(self) -> str:
+ """A string representation of the Specifier that can be round-tripped.
+
+ >>> str(Specifier('>=1.0.0'))
+ '>=1.0.0'
+ >>> str(Specifier('>=1.0.0', prereleases=False))
+ '>=1.0.0'
+ """
+ return "{}{}".format(*self._spec)
+
+ @property
+ def _canonical_spec(self) -> tuple[str, str]:
+ canonical_version = canonicalize_version(
+ self._spec[1],
+ strip_trailing_zero=(self._spec[0] != "~="),
+ )
+ return self._spec[0], canonical_version
+
+ def __hash__(self) -> int:
+ return hash(self._canonical_spec)
+
+ def __eq__(self, other: object) -> bool:
+ """Whether or not the two Specifier-like objects are equal.
+
+ :param other: The other object to check against.
+
+ The value of :attr:`prereleases` is ignored.
+
+ >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
+ True
+ >>> (Specifier("==1.2.3", prereleases=False) ==
+ ... Specifier("==1.2.3", prereleases=True))
+ True
+ >>> Specifier("==1.2.3") == "==1.2.3"
+ True
+ >>> Specifier("==1.2.3") == Specifier("==1.2.4")
+ False
+ >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
+ False
+ """
+ if isinstance(other, str):
+ try:
+ other = self.__class__(str(other))
+ except InvalidSpecifier:
+ return NotImplemented
+ elif not isinstance(other, self.__class__):
+ return NotImplemented
+
+ return self._canonical_spec == other._canonical_spec
+
+ def _get_operator(self, op: str) -> CallableOperator:
+ operator_callable: CallableOperator = getattr(
+ self, f"_compare_{self._operators[op]}"
+ )
+ return operator_callable
+
+ def _compare_compatible(self, prospective: Version, spec: str) -> bool:
+ # Compatible releases have an equivalent combination of >= and ==. That
+ # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
+ # implement this in terms of the other specifiers instead of
+ # implementing it ourselves. The only thing we need to do is construct
+ # the other specifiers.
+
+ # We want everything but the last item in the version, but we want to
+ # ignore suffix segments.
+ prefix = _version_join(
+ list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
+ )
+
+ # Add the prefix notation to the end of our string
+ prefix += ".*"
+
+ return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
+ prospective, prefix
+ )
+
+ def _compare_equal(self, prospective: Version, spec: str) -> bool:
+ # We need special logic to handle prefix matching
+ if spec.endswith(".*"):
+ # In the case of prefix matching we want to ignore local segment.
+ normalized_prospective = canonicalize_version(
+ prospective.public, strip_trailing_zero=False
+ )
+ # Get the normalized version string ignoring the trailing .*
+ normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
+ # Split the spec out by bangs and dots, and pretend that there is
+ # an implicit dot in between a release segment and a pre-release segment.
+ split_spec = _version_split(normalized_spec)
+
+ # Split the prospective version out by bangs and dots, and pretend
+ # that there is an implicit dot in between a release segment and
+ # a pre-release segment.
+ split_prospective = _version_split(normalized_prospective)
+
+ # 0-pad the prospective version before shortening it to get the correct
+ # shortened version.
+ padded_prospective, _ = _pad_version(split_prospective, split_spec)
+
+ # Shorten the prospective version to be the same length as the spec
+ # so that we can determine if the specifier is a prefix of the
+ # prospective version or not.
+ shortened_prospective = padded_prospective[: len(split_spec)]
+
+ return shortened_prospective == split_spec
+ else:
+ # Convert our spec string into a Version
+ spec_version = Version(spec)
+
+ # If the specifier does not have a local segment, then we want to
+ # act as if the prospective version also does not have a local
+ # segment.
+ if not spec_version.local:
+ prospective = Version(prospective.public)
+
+ return prospective == spec_version
+
+ def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
+ return not self._compare_equal(prospective, spec)
+
+ def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
+ # NB: Local version identifiers are NOT permitted in the version
+ # specifier, so local version labels can be universally removed from
+ # the prospective version.
+ return Version(prospective.public) <= Version(spec)
+
+ def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
+ # NB: Local version identifiers are NOT permitted in the version
+ # specifier, so local version labels can be universally removed from
+ # the prospective version.
+ return Version(prospective.public) >= Version(spec)
+
+ def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
+ # Convert our spec to a Version instance, since we'll want to work with
+ # it as a version.
+ spec = Version(spec_str)
+
+ # Check to see if the prospective version is less than the spec
+ # version. If it's not we can short circuit and just return False now
+ # instead of doing extra unneeded work.
+ if not prospective < spec:
+ return False
+
+ # This special case is here so that, unless the specifier itself
+ # includes is a pre-release version, that we do not accept pre-release
+ # versions for the version mentioned in the specifier (e.g. <3.1 should
+ # not match 3.1.dev0, but should match 3.0.dev0).
+ if not spec.is_prerelease and prospective.is_prerelease:
+ if Version(prospective.base_version) == Version(spec.base_version):
+ return False
+
+ # If we've gotten to here, it means that prospective version is both
+ # less than the spec version *and* it's not a pre-release of the same
+ # version in the spec.
+ return True
+
+ def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
+ # Convert our spec to a Version instance, since we'll want to work with
+ # it as a version.
+ spec = Version(spec_str)
+
+ # Check to see if the prospective version is greater than the spec
+ # version. If it's not we can short circuit and just return False now
+ # instead of doing extra unneeded work.
+ if not prospective > spec:
+ return False
+
+ # This special case is here so that, unless the specifier itself
+ # includes is a post-release version, that we do not accept
+ # post-release versions for the version mentioned in the specifier
+ # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
+ if not spec.is_postrelease and prospective.is_postrelease:
+ if Version(prospective.base_version) == Version(spec.base_version):
+ return False
+
+ # Ensure that we do not allow a local version of the version mentioned
+ # in the specifier, which is technically greater than, to match.
+ if prospective.local is not None:
+ if Version(prospective.base_version) == Version(spec.base_version):
+ return False
+
+ # If we've gotten to here, it means that prospective version is both
+ # greater than the spec version *and* it's not a pre-release of the
+ # same version in the spec.
+ return True
+
+ def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
+ return str(prospective).lower() == str(spec).lower()
+
+ def __contains__(self, item: str | Version) -> bool:
+ """Return whether or not the item is contained in this specifier.
+
+ :param item: The item to check for.
+
+ This is used for the ``in`` operator and behaves the same as
+ :meth:`contains` with no ``prereleases`` argument passed.
+
+ >>> "1.2.3" in Specifier(">=1.2.3")
+ True
+ >>> Version("1.2.3") in Specifier(">=1.2.3")
+ True
+ >>> "1.0.0" in Specifier(">=1.2.3")
+ False
+ >>> "1.3.0a1" in Specifier(">=1.2.3")
+ False
+ >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
+ True
+ """
+ return self.contains(item)
+
+ def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
+ """Return whether or not the item is contained in this specifier.
+
+ :param item:
+ The item to check for, which can be a version string or a
+ :class:`Version` instance.
+ :param prereleases:
+ Whether or not to match prereleases with this Specifier. If set to
+ ``None`` (the default), it uses :attr:`prereleases` to determine
+ whether or not prereleases are allowed.
+
+ >>> Specifier(">=1.2.3").contains("1.2.3")
+ True
+ >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
+ True
+ >>> Specifier(">=1.2.3").contains("1.0.0")
+ False
+ >>> Specifier(">=1.2.3").contains("1.3.0a1")
+ False
+ >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
+ True
+ >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
+ True
+ """
+
+ # Determine if prereleases are to be allowed or not.
+ if prereleases is None:
+ prereleases = self.prereleases
+
+ # Normalize item to a Version, this allows us to have a shortcut for
+ # "2.0" in Specifier(">=2")
+ normalized_item = _coerce_version(item)
+
+ # Determine if we should be supporting prereleases in this specifier
+ # or not, if we do not support prereleases than we can short circuit
+ # logic if this version is a prereleases.
+ if normalized_item.is_prerelease and not prereleases:
+ return False
+
+ # Actually do the comparison to determine if this item is contained
+ # within this Specifier or not.
+ operator_callable: CallableOperator = self._get_operator(self.operator)
+ return operator_callable(normalized_item, self.version)
+
+ def filter(
+ self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
+ ) -> Iterator[UnparsedVersionVar]:
+ """Filter items in the given iterable, that match the specifier.
+
+ :param iterable:
+ An iterable that can contain version strings and :class:`Version` instances.
+ The items in the iterable will be filtered according to the specifier.
+ :param prereleases:
+ Whether or not to allow prereleases in the returned iterator. If set to
+ ``None`` (the default), it will be intelligently decide whether to allow
+ prereleases or not (based on the :attr:`prereleases` attribute, and
+ whether the only versions matching are prereleases).
+
+ This method is smarter than just ``filter(Specifier().contains, [...])``
+ because it implements the rule from :pep:`440` that a prerelease item
+ SHOULD be accepted if no other versions match the given specifier.
+
+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
+ ['1.3']
+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
+ ['1.2.3', '1.3', ]
+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
+ ['1.5a1']
+ >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
+ ['1.3', '1.5a1']
+ >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
+ ['1.3', '1.5a1']
+ """
+
+ yielded = False
+ found_prereleases = []
+
+ kw = {"prereleases": prereleases if prereleases is not None else True}
+
+ # Attempt to iterate over all the values in the iterable and if any of
+ # them match, yield them.
+ for version in iterable:
+ parsed_version = _coerce_version(version)
+
+ if self.contains(parsed_version, **kw):
+ # If our version is a prerelease, and we were not set to allow
+ # prereleases, then we'll store it for later in case nothing
+ # else matches this specifier.
+ if parsed_version.is_prerelease and not (
+ prereleases or self.prereleases
+ ):
+ found_prereleases.append(version)
+ # Either this is not a prerelease, or we should have been
+ # accepting prereleases from the beginning.
+ else:
+ yielded = True
+ yield version
+
+ # Now that we've iterated over everything, determine if we've yielded
+ # any values, and if we have not and we have any prereleases stored up
+ # then we will go ahead and yield the prereleases.
+ if not yielded and found_prereleases:
+ for version in found_prereleases:
+ yield version
+
+
+_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
+
+
+def _version_split(version: str) -> list[str]:
+ """Split version into components.
+
+ The split components are intended for version comparison. The logic does
+ not attempt to retain the original version string, so joining the
+ components back with :func:`_version_join` may not produce the original
+ version string.
+ """
+ result: list[str] = []
+
+ epoch, _, rest = version.rpartition("!")
+ result.append(epoch or "0")
+
+ for item in rest.split("."):
+ match = _prefix_regex.search(item)
+ if match:
+ result.extend(match.groups())
+ else:
+ result.append(item)
+ return result
+
+
+def _version_join(components: list[str]) -> str:
+ """Join split version components into a version string.
+
+ This function assumes the input came from :func:`_version_split`, where the
+ first component must be the epoch (either empty or numeric), and all other
+ components numeric.
+ """
+ epoch, *rest = components
+ return f"{epoch}!{'.'.join(rest)}"
+
+
+def _is_not_suffix(segment: str) -> bool:
+ return not any(
+ segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
+ )
+
+
+def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:
+ left_split, right_split = [], []
+
+ # Get the release segment of our versions
+ left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
+ right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
+
+ # Get the rest of our versions
+ left_split.append(left[len(left_split[0]) :])
+ right_split.append(right[len(right_split[0]) :])
+
+ # Insert our padding
+ left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
+ right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
+
+ return (
+ list(itertools.chain.from_iterable(left_split)),
+ list(itertools.chain.from_iterable(right_split)),
+ )
+
+
+class SpecifierSet(BaseSpecifier):
+ """This class abstracts handling of a set of version specifiers.
+
+ It can be passed a single specifier (``>=3.0``), a comma-separated list of
+ specifiers (``>=3.0,!=3.1``), or no specifier at all.
+ """
+
+ def __init__(
+ self,
+ specifiers: str | Iterable[Specifier] = "",
+ prereleases: bool | None = None,
+ ) -> None:
+ """Initialize a SpecifierSet instance.
+
+ :param specifiers:
+ The string representation of a specifier or a comma-separated list of
+ specifiers which will be parsed and normalized before use.
+ May also be an iterable of ``Specifier`` instances, which will be used
+ as is.
+ :param prereleases:
+ This tells the SpecifierSet if it should accept prerelease versions if
+ applicable or not. The default of ``None`` will autodetect it from the
+ given specifiers.
+
+ :raises InvalidSpecifier:
+ If the given ``specifiers`` are not parseable than this exception will be
+ raised.
+ """
+
+ if isinstance(specifiers, str):
+ # Split on `,` to break each individual specifier into its own item, and
+ # strip each item to remove leading/trailing whitespace.
+ split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
+
+ # Make each individual specifier a Specifier and save in a frozen set
+ # for later.
+ self._specs = frozenset(map(Specifier, split_specifiers))
+ else:
+ # Save the supplied specifiers in a frozen set.
+ self._specs = frozenset(specifiers)
+
+ # Store our prereleases value so we can use it later to determine if
+ # we accept prereleases or not.
+ self._prereleases = prereleases
+
+ @property
+ def prereleases(self) -> bool | None:
+ # If we have been given an explicit prerelease modifier, then we'll
+ # pass that through here.
+ if self._prereleases is not None:
+ return self._prereleases
+
+ # If we don't have any specifiers, and we don't have a forced value,
+ # then we'll just return None since we don't know if this should have
+ # pre-releases or not.
+ if not self._specs:
+ return None
+
+ # Otherwise we'll see if any of the given specifiers accept
+ # prereleases, if any of them do we'll return True, otherwise False.
+ return any(s.prereleases for s in self._specs)
+
+ @prereleases.setter
+ def prereleases(self, value: bool) -> None:
+ self._prereleases = value
+
+ def __repr__(self) -> str:
+ """A representation of the specifier set that shows all internal state.
+
+ Note that the ordering of the individual specifiers within the set may not
+ match the input string.
+
+ >>> SpecifierSet('>=1.0.0,!=2.0.0')
+ =1.0.0')>
+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
+ =1.0.0', prereleases=False)>
+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
+ =1.0.0', prereleases=True)>
+ """
+ pre = (
+ f", prereleases={self.prereleases!r}"
+ if self._prereleases is not None
+ else ""
+ )
+
+ return f""
+
+ def __str__(self) -> str:
+ """A string representation of the specifier set that can be round-tripped.
+
+ Note that the ordering of the individual specifiers within the set may not
+ match the input string.
+
+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
+ '!=1.0.1,>=1.0.0'
+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
+ '!=1.0.1,>=1.0.0'
+ """
+ return ",".join(sorted(str(s) for s in self._specs))
+
+ def __hash__(self) -> int:
+ return hash(self._specs)
+
+ def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
+ """Return a SpecifierSet which is a combination of the two sets.
+
+ :param other: The other object to combine with.
+
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
+ =1.0.0')>
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
+ =1.0.0')>
+ """
+ if isinstance(other, str):
+ other = SpecifierSet(other)
+ elif not isinstance(other, SpecifierSet):
+ return NotImplemented
+
+ specifier = SpecifierSet()
+ specifier._specs = frozenset(self._specs | other._specs)
+
+ if self._prereleases is None and other._prereleases is not None:
+ specifier._prereleases = other._prereleases
+ elif self._prereleases is not None and other._prereleases is None:
+ specifier._prereleases = self._prereleases
+ elif self._prereleases == other._prereleases:
+ specifier._prereleases = self._prereleases
+ else:
+ raise ValueError(
+ "Cannot combine SpecifierSets with True and False prerelease "
+ "overrides."
+ )
+
+ return specifier
+
+ def __eq__(self, other: object) -> bool:
+ """Whether or not the two SpecifierSet-like objects are equal.
+
+ :param other: The other object to check against.
+
+ The value of :attr:`prereleases` is ignored.
+
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
+ True
+ >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
+ ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
+ False
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
+ False
+ """
+ if isinstance(other, (str, Specifier)):
+ other = SpecifierSet(str(other))
+ elif not isinstance(other, SpecifierSet):
+ return NotImplemented
+
+ return self._specs == other._specs
+
+ def __len__(self) -> int:
+ """Returns the number of specifiers in this specifier set."""
+ return len(self._specs)
+
+ def __iter__(self) -> Iterator[Specifier]:
+ """
+ Returns an iterator over all the underlying :class:`Specifier` instances
+ in this specifier set.
+
+ >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
+ [, =1.0.0')>]
+ """
+ return iter(self._specs)
+
+ def __contains__(self, item: UnparsedVersion) -> bool:
+ """Return whether or not the item is contained in this specifier.
+
+ :param item: The item to check for.
+
+ This is used for the ``in`` operator and behaves the same as
+ :meth:`contains` with no ``prereleases`` argument passed.
+
+ >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
+ True
+ >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
+ True
+ >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
+ False
+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
+ False
+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
+ True
+ """
+ return self.contains(item)
+
+ def contains(
+ self,
+ item: UnparsedVersion,
+ prereleases: bool | None = None,
+ installed: bool | None = None,
+ ) -> bool:
+ """Return whether or not the item is contained in this SpecifierSet.
+
+ :param item:
+ The item to check for, which can be a version string or a
+ :class:`Version` instance.
+ :param prereleases:
+ Whether or not to match prereleases with this SpecifierSet. If set to
+ ``None`` (the default), it uses :attr:`prereleases` to determine
+ whether or not prereleases are allowed.
+
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
+ False
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
+ False
+ >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1")
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
+ True
+ """
+ # Ensure that our item is a Version instance.
+ if not isinstance(item, Version):
+ item = Version(item)
+
+ # Determine if we're forcing a prerelease or not, if we're not forcing
+ # one for this particular filter call, then we'll use whatever the
+ # SpecifierSet thinks for whether or not we should support prereleases.
+ if prereleases is None:
+ prereleases = self.prereleases
+
+ # We can determine if we're going to allow pre-releases by looking to
+ # see if any of the underlying items supports them. If none of them do
+ # and this item is a pre-release then we do not allow it and we can
+ # short circuit that here.
+ # Note: This means that 1.0.dev1 would not be contained in something
+ # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
+ if not prereleases and item.is_prerelease:
+ return False
+
+ if installed and item.is_prerelease:
+ item = Version(item.base_version)
+
+ # We simply dispatch to the underlying specs here to make sure that the
+ # given version is contained within all of them.
+ # Note: This use of all() here means that an empty set of specifiers
+ # will always return True, this is an explicit design decision.
+ return all(s.contains(item, prereleases=prereleases) for s in self._specs)
+
+ def filter(
+ self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
+ ) -> Iterator[UnparsedVersionVar]:
+ """Filter items in the given iterable, that match the specifiers in this set.
+
+ :param iterable:
+ An iterable that can contain version strings and :class:`Version` instances.
+ The items in the iterable will be filtered according to the specifier.
+ :param prereleases:
+ Whether or not to allow prereleases in the returned iterator. If set to
+ ``None`` (the default), it will be intelligently decide whether to allow
+ prereleases or not (based on the :attr:`prereleases` attribute, and
+ whether the only versions matching are prereleases).
+
+ This method is smarter than just ``filter(SpecifierSet(...).contains, [...])``
+ because it implements the rule from :pep:`440` that a prerelease item
+ SHOULD be accepted if no other versions match the given specifier.
+
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
+ ['1.3']
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
+ ['1.3', ]
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
+ []
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
+ ['1.3', '1.5a1']
+ >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
+ ['1.3', '1.5a1']
+
+ An "empty" SpecifierSet will filter items based on the presence of prerelease
+ versions in the set.
+
+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
+ ['1.3']
+ >>> list(SpecifierSet("").filter(["1.5a1"]))
+ ['1.5a1']
+ >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
+ ['1.3', '1.5a1']
+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
+ ['1.3', '1.5a1']
+ """
+ # Determine if we're forcing a prerelease or not, if we're not forcing
+ # one for this particular filter call, then we'll use whatever the
+ # SpecifierSet thinks for whether or not we should support prereleases.
+ if prereleases is None:
+ prereleases = self.prereleases
+
+ # If we have any specifiers, then we want to wrap our iterable in the
+ # filter method for each one, this will act as a logical AND amongst
+ # each specifier.
+ if self._specs:
+ for spec in self._specs:
+ iterable = spec.filter(iterable, prereleases=bool(prereleases))
+ return iter(iterable)
+ # If we do not have any specifiers, then we need to have a rough filter
+ # which will filter out any pre-releases, unless there are no final
+ # releases.
+ else:
+ filtered: list[UnparsedVersionVar] = []
+ found_prereleases: list[UnparsedVersionVar] = []
+
+ for item in iterable:
+ parsed_version = _coerce_version(item)
+
+ # Store any item which is a pre-release for later unless we've
+ # already found a final version or we are accepting prereleases
+ if parsed_version.is_prerelease and not prereleases:
+ if not filtered:
+ found_prereleases.append(item)
+ else:
+ filtered.append(item)
+
+ # If we've found no items except for pre-releases, then we'll go
+ # ahead and use the pre-releases
+ if not filtered and found_prereleases and prereleases is None:
+ return iter(found_prereleases)
+
+ return iter(filtered)
diff --git a/env/Lib/site-packages/packaging/tags.py b/env/Lib/site-packages/packaging/tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5903402abb5a0aed37bb23914f678ef7e34a554
--- /dev/null
+++ b/env/Lib/site-packages/packaging/tags.py
@@ -0,0 +1,617 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import logging
+import platform
+import re
+import struct
+import subprocess
+import sys
+import sysconfig
+from importlib.machinery import EXTENSION_SUFFIXES
+from typing import (
+ Iterable,
+ Iterator,
+ Sequence,
+ Tuple,
+ cast,
+)
+
+from . import _manylinux, _musllinux
+
+logger = logging.getLogger(__name__)
+
+PythonVersion = Sequence[int]
+AppleVersion = Tuple[int, int]
+
+INTERPRETER_SHORT_NAMES: dict[str, str] = {
+ "python": "py", # Generic.
+ "cpython": "cp",
+ "pypy": "pp",
+ "ironpython": "ip",
+ "jython": "jy",
+}
+
+
+_32_BIT_INTERPRETER = struct.calcsize("P") == 4
+
+
+class Tag:
+ """
+ A representation of the tag triple for a wheel.
+
+ Instances are considered immutable and thus are hashable. Equality checking
+ is also supported.
+ """
+
+ __slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
+
+ def __init__(self, interpreter: str, abi: str, platform: str) -> None:
+ self._interpreter = interpreter.lower()
+ self._abi = abi.lower()
+ self._platform = platform.lower()
+ # The __hash__ of every single element in a Set[Tag] will be evaluated each time
+ # that a set calls its `.disjoint()` method, which may be called hundreds of
+ # times when scanning a page of links for packages with tags matching that
+ # Set[Tag]. Pre-computing the value here produces significant speedups for
+ # downstream consumers.
+ self._hash = hash((self._interpreter, self._abi, self._platform))
+
+ @property
+ def interpreter(self) -> str:
+ return self._interpreter
+
+ @property
+ def abi(self) -> str:
+ return self._abi
+
+ @property
+ def platform(self) -> str:
+ return self._platform
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Tag):
+ return NotImplemented
+
+ return (
+ (self._hash == other._hash) # Short-circuit ASAP for perf reasons.
+ and (self._platform == other._platform)
+ and (self._abi == other._abi)
+ and (self._interpreter == other._interpreter)
+ )
+
+ def __hash__(self) -> int:
+ return self._hash
+
+ def __str__(self) -> str:
+ return f"{self._interpreter}-{self._abi}-{self._platform}"
+
+ def __repr__(self) -> str:
+ return f"<{self} @ {id(self)}>"
+
+
+def parse_tag(tag: str) -> frozenset[Tag]:
+ """
+ Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
+
+ Returning a set is required due to the possibility that the tag is a
+ compressed tag set.
+ """
+ tags = set()
+ interpreters, abis, platforms = tag.split("-")
+ for interpreter in interpreters.split("."):
+ for abi in abis.split("."):
+ for platform_ in platforms.split("."):
+ tags.add(Tag(interpreter, abi, platform_))
+ return frozenset(tags)
+
+
+def _get_config_var(name: str, warn: bool = False) -> int | str | None:
+ value: int | str | None = sysconfig.get_config_var(name)
+ if value is None and warn:
+ logger.debug(
+ "Config variable '%s' is unset, Python ABI tag may be incorrect", name
+ )
+ return value
+
+
+def _normalize_string(string: str) -> str:
+ return string.replace(".", "_").replace("-", "_").replace(" ", "_")
+
+
+def _is_threaded_cpython(abis: list[str]) -> bool:
+ """
+ Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
+
+ The threaded builds are indicated by a "t" in the abiflags.
+ """
+ if len(abis) == 0:
+ return False
+ # expect e.g., cp313
+ m = re.match(r"cp\d+(.*)", abis[0])
+ if not m:
+ return False
+ abiflags = m.group(1)
+ return "t" in abiflags
+
+
+def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
+ """
+ Determine if the Python version supports abi3.
+
+ PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
+ builds do not support abi3.
+ """
+ return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
+
+
+def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
+ py_version = tuple(py_version) # To allow for version comparison.
+ abis = []
+ version = _version_nodot(py_version[:2])
+ threading = debug = pymalloc = ucs4 = ""
+ with_debug = _get_config_var("Py_DEBUG", warn)
+ has_refcount = hasattr(sys, "gettotalrefcount")
+ # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
+ # extension modules is the best option.
+ # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
+ has_ext = "_d.pyd" in EXTENSION_SUFFIXES
+ if with_debug or (with_debug is None and (has_refcount or has_ext)):
+ debug = "d"
+ if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
+ threading = "t"
+ if py_version < (3, 8):
+ with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
+ if with_pymalloc or with_pymalloc is None:
+ pymalloc = "m"
+ if py_version < (3, 3):
+ unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
+ if unicode_size == 4 or (
+ unicode_size is None and sys.maxunicode == 0x10FFFF
+ ):
+ ucs4 = "u"
+ elif debug:
+ # Debug builds can also load "normal" extension modules.
+ # We can also assume no UCS-4 or pymalloc requirement.
+ abis.append(f"cp{version}{threading}")
+ abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
+ return abis
+
+
+def cpython_tags(
+ python_version: PythonVersion | None = None,
+ abis: Iterable[str] | None = None,
+ platforms: Iterable[str] | None = None,
+ *,
+ warn: bool = False,
+) -> Iterator[Tag]:
+ """
+ Yields the tags for a CPython interpreter.
+
+ The tags consist of:
+ - cp--
+ - cp-abi3-
+ - cp-none-
+ - cp-abi3- # Older Python versions down to 3.2.
+
+ If python_version only specifies a major version then user-provided ABIs and
+ the 'none' ABItag will be used.
+
+ If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
+ their normal position and not at the beginning.
+ """
+ if not python_version:
+ python_version = sys.version_info[:2]
+
+ interpreter = f"cp{_version_nodot(python_version[:2])}"
+
+ if abis is None:
+ if len(python_version) > 1:
+ abis = _cpython_abis(python_version, warn)
+ else:
+ abis = []
+ abis = list(abis)
+ # 'abi3' and 'none' are explicitly handled later.
+ for explicit_abi in ("abi3", "none"):
+ try:
+ abis.remove(explicit_abi)
+ except ValueError:
+ pass
+
+ platforms = list(platforms or platform_tags())
+ for abi in abis:
+ for platform_ in platforms:
+ yield Tag(interpreter, abi, platform_)
+
+ threading = _is_threaded_cpython(abis)
+ use_abi3 = _abi3_applies(python_version, threading)
+ if use_abi3:
+ yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
+ yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
+
+ if use_abi3:
+ for minor_version in range(python_version[1] - 1, 1, -1):
+ for platform_ in platforms:
+ version = _version_nodot((python_version[0], minor_version))
+ interpreter = f"cp{version}"
+ yield Tag(interpreter, "abi3", platform_)
+
+
+def _generic_abi() -> list[str]:
+ """
+ Return the ABI tag based on EXT_SUFFIX.
+ """
+ # The following are examples of `EXT_SUFFIX`.
+ # We want to keep the parts which are related to the ABI and remove the
+ # parts which are related to the platform:
+ # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
+ # - mac: '.cpython-310-darwin.so' => cp310
+ # - win: '.cp310-win_amd64.pyd' => cp310
+ # - win: '.pyd' => cp37 (uses _cpython_abis())
+ # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
+ # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
+ # => graalpy_38_native
+
+ ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
+ if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
+ raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
+ parts = ext_suffix.split(".")
+ if len(parts) < 3:
+ # CPython3.7 and earlier uses ".pyd" on Windows.
+ return _cpython_abis(sys.version_info[:2])
+ soabi = parts[1]
+ if soabi.startswith("cpython"):
+ # non-windows
+ abi = "cp" + soabi.split("-")[1]
+ elif soabi.startswith("cp"):
+ # windows
+ abi = soabi.split("-")[0]
+ elif soabi.startswith("pypy"):
+ abi = "-".join(soabi.split("-")[:2])
+ elif soabi.startswith("graalpy"):
+ abi = "-".join(soabi.split("-")[:3])
+ elif soabi:
+ # pyston, ironpython, others?
+ abi = soabi
+ else:
+ return []
+ return [_normalize_string(abi)]
+
+
+def generic_tags(
+ interpreter: str | None = None,
+ abis: Iterable[str] | None = None,
+ platforms: Iterable[str] | None = None,
+ *,
+ warn: bool = False,
+) -> Iterator[Tag]:
+ """
+ Yields the tags for a generic interpreter.
+
+ The tags consist of:
+ - --
+
+ The "none" ABI will be added if it was not explicitly provided.
+ """
+ if not interpreter:
+ interp_name = interpreter_name()
+ interp_version = interpreter_version(warn=warn)
+ interpreter = "".join([interp_name, interp_version])
+ if abis is None:
+ abis = _generic_abi()
+ else:
+ abis = list(abis)
+ platforms = list(platforms or platform_tags())
+ if "none" not in abis:
+ abis.append("none")
+ for abi in abis:
+ for platform_ in platforms:
+ yield Tag(interpreter, abi, platform_)
+
+
+def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
+ """
+ Yields Python versions in descending order.
+
+ After the latest version, the major-only version will be yielded, and then
+ all previous versions of that major version.
+ """
+ if len(py_version) > 1:
+ yield f"py{_version_nodot(py_version[:2])}"
+ yield f"py{py_version[0]}"
+ if len(py_version) > 1:
+ for minor in range(py_version[1] - 1, -1, -1):
+ yield f"py{_version_nodot((py_version[0], minor))}"
+
+
+def compatible_tags(
+ python_version: PythonVersion | None = None,
+ interpreter: str | None = None,
+ platforms: Iterable[str] | None = None,
+) -> Iterator[Tag]:
+ """
+ Yields the sequence of tags that are compatible with a specific version of Python.
+
+ The tags consist of:
+ - py*-none-
+ - -none-any # ... if `interpreter` is provided.
+ - py*-none-any
+ """
+ if not python_version:
+ python_version = sys.version_info[:2]
+ platforms = list(platforms or platform_tags())
+ for version in _py_interpreter_range(python_version):
+ for platform_ in platforms:
+ yield Tag(version, "none", platform_)
+ if interpreter:
+ yield Tag(interpreter, "none", "any")
+ for version in _py_interpreter_range(python_version):
+ yield Tag(version, "none", "any")
+
+
+def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
+ if not is_32bit:
+ return arch
+
+ if arch.startswith("ppc"):
+ return "ppc"
+
+ return "i386"
+
+
+def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
+ formats = [cpu_arch]
+ if cpu_arch == "x86_64":
+ if version < (10, 4):
+ return []
+ formats.extend(["intel", "fat64", "fat32"])
+
+ elif cpu_arch == "i386":
+ if version < (10, 4):
+ return []
+ formats.extend(["intel", "fat32", "fat"])
+
+ elif cpu_arch == "ppc64":
+ # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
+ if version > (10, 5) or version < (10, 4):
+ return []
+ formats.append("fat64")
+
+ elif cpu_arch == "ppc":
+ if version > (10, 6):
+ return []
+ formats.extend(["fat32", "fat"])
+
+ if cpu_arch in {"arm64", "x86_64"}:
+ formats.append("universal2")
+
+ if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
+ formats.append("universal")
+
+ return formats
+
+
+def mac_platforms(
+ version: AppleVersion | None = None, arch: str | None = None
+) -> Iterator[str]:
+ """
+ Yields the platform tags for a macOS system.
+
+ The `version` parameter is a two-item tuple specifying the macOS version to
+ generate platform tags for. The `arch` parameter is the CPU architecture to
+ generate platform tags for. Both parameters default to the appropriate value
+ for the current system.
+ """
+ version_str, _, cpu_arch = platform.mac_ver()
+ if version is None:
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
+ if version == (10, 16):
+ # When built against an older macOS SDK, Python will report macOS 10.16
+ # instead of the real version.
+ version_str = subprocess.run(
+ [
+ sys.executable,
+ "-sS",
+ "-c",
+ "import platform; print(platform.mac_ver()[0])",
+ ],
+ check=True,
+ env={"SYSTEM_VERSION_COMPAT": "0"},
+ stdout=subprocess.PIPE,
+ text=True,
+ ).stdout
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
+ else:
+ version = version
+ if arch is None:
+ arch = _mac_arch(cpu_arch)
+ else:
+ arch = arch
+
+ if (10, 0) <= version and version < (11, 0):
+ # Prior to Mac OS 11, each yearly release of Mac OS bumped the
+ # "minor" version number. The major version was always 10.
+ major_version = 10
+ for minor_version in range(version[1], -1, -1):
+ compat_version = major_version, minor_version
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
+
+ if version >= (11, 0):
+ # Starting with Mac OS 11, each yearly release bumps the major version
+ # number. The minor versions are now the midyear updates.
+ minor_version = 0
+ for major_version in range(version[0], 10, -1):
+ compat_version = major_version, minor_version
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
+
+ if version >= (11, 0):
+ # Mac OS 11 on x86_64 is compatible with binaries from previous releases.
+ # Arm64 support was introduced in 11.0, so no Arm binaries from previous
+ # releases exist.
+ #
+ # However, the "universal2" binary format can have a
+ # macOS version earlier than 11.0 when the x86_64 part of the binary supports
+ # that version of macOS.
+ major_version = 10
+ if arch == "x86_64":
+ for minor_version in range(16, 3, -1):
+ compat_version = major_version, minor_version
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
+ else:
+ for minor_version in range(16, 3, -1):
+ compat_version = major_version, minor_version
+ binary_format = "universal2"
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
+
+
+def ios_platforms(
+ version: AppleVersion | None = None, multiarch: str | None = None
+) -> Iterator[str]:
+ """
+ Yields the platform tags for an iOS system.
+
+ :param version: A two-item tuple specifying the iOS version to generate
+ platform tags for. Defaults to the current iOS version.
+ :param multiarch: The CPU architecture+ABI to generate platform tags for -
+ (the value used by `sys.implementation._multiarch` e.g.,
+ `arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
+ multiarch value.
+ """
+ if version is None:
+ # if iOS is the current platform, ios_ver *must* be defined. However,
+ # it won't exist for CPython versions before 3.13, which causes a mypy
+ # error.
+ _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore]
+ version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
+
+ if multiarch is None:
+ multiarch = sys.implementation._multiarch
+ multiarch = multiarch.replace("-", "_")
+
+ ios_platform_template = "ios_{major}_{minor}_{multiarch}"
+
+ # Consider any iOS major.minor version from the version requested, down to
+ # 12.0. 12.0 is the first iOS version that is known to have enough features
+ # to support CPython. Consider every possible minor release up to X.9. There
+ # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
+ # candidates that won't ever match doesn't really hurt, and it saves us from
+ # having to keep an explicit list of known iOS versions in the code. Return
+ # the results descending order of version number.
+
+ # If the requested major version is less than 12, there won't be any matches.
+ if version[0] < 12:
+ return
+
+ # Consider the actual X.Y version that was requested.
+ yield ios_platform_template.format(
+ major=version[0], minor=version[1], multiarch=multiarch
+ )
+
+ # Consider every minor version from X.0 to the minor version prior to the
+ # version requested by the platform.
+ for minor in range(version[1] - 1, -1, -1):
+ yield ios_platform_template.format(
+ major=version[0], minor=minor, multiarch=multiarch
+ )
+
+ for major in range(version[0] - 1, 11, -1):
+ for minor in range(9, -1, -1):
+ yield ios_platform_template.format(
+ major=major, minor=minor, multiarch=multiarch
+ )
+
+
+def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
+ linux = _normalize_string(sysconfig.get_platform())
+ if not linux.startswith("linux_"):
+ # we should never be here, just yield the sysconfig one and return
+ yield linux
+ return
+ if is_32bit:
+ if linux == "linux_x86_64":
+ linux = "linux_i686"
+ elif linux == "linux_aarch64":
+ linux = "linux_armv8l"
+ _, arch = linux.split("_", 1)
+ archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
+ yield from _manylinux.platform_tags(archs)
+ yield from _musllinux.platform_tags(archs)
+ for arch in archs:
+ yield f"linux_{arch}"
+
+
+def _generic_platforms() -> Iterator[str]:
+ yield _normalize_string(sysconfig.get_platform())
+
+
+def platform_tags() -> Iterator[str]:
+ """
+ Provides the platform tags for this installation.
+ """
+ if platform.system() == "Darwin":
+ return mac_platforms()
+ elif platform.system() == "iOS":
+ return ios_platforms()
+ elif platform.system() == "Linux":
+ return _linux_platforms()
+ else:
+ return _generic_platforms()
+
+
+def interpreter_name() -> str:
+ """
+ Returns the name of the running interpreter.
+
+ Some implementations have a reserved, two-letter abbreviation which will
+ be returned when appropriate.
+ """
+ name = sys.implementation.name
+ return INTERPRETER_SHORT_NAMES.get(name) or name
+
+
+def interpreter_version(*, warn: bool = False) -> str:
+ """
+ Returns the version of the running interpreter.
+ """
+ version = _get_config_var("py_version_nodot", warn=warn)
+ if version:
+ version = str(version)
+ else:
+ version = _version_nodot(sys.version_info[:2])
+ return version
+
+
+def _version_nodot(version: PythonVersion) -> str:
+ return "".join(map(str, version))
+
+
+def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
+ """
+ Returns the sequence of tag triples for the running interpreter.
+
+ The order of the sequence corresponds to priority order for the
+ interpreter, from most to least important.
+ """
+
+ interp_name = interpreter_name()
+ if interp_name == "cp":
+ yield from cpython_tags(warn=warn)
+ else:
+ yield from generic_tags()
+
+ if interp_name == "pp":
+ interp = "pp3"
+ elif interp_name == "cp":
+ interp = "cp" + interpreter_version(warn=warn)
+ else:
+ interp = None
+ yield from compatible_tags(interpreter=interp)
diff --git a/env/Lib/site-packages/packaging/utils.py b/env/Lib/site-packages/packaging/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..23450953df74eccd9c13cd2a955ce09d1f968565
--- /dev/null
+++ b/env/Lib/site-packages/packaging/utils.py
@@ -0,0 +1,163 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import annotations
+
+import functools
+import re
+from typing import NewType, Tuple, Union, cast
+
+from .tags import Tag, parse_tag
+from .version import InvalidVersion, Version, _TrimmedRelease
+
+BuildTag = Union[Tuple[()], Tuple[int, str]]
+NormalizedName = NewType("NormalizedName", str)
+
+
+class InvalidName(ValueError):
+ """
+ An invalid distribution name; users should refer to the packaging user guide.
+ """
+
+
+class InvalidWheelFilename(ValueError):
+ """
+ An invalid wheel filename was found, users should refer to PEP 427.
+ """
+
+
+class InvalidSdistFilename(ValueError):
+ """
+ An invalid sdist filename was found, users should refer to the packaging user guide.
+ """
+
+
+# Core metadata spec for `Name`
+_validate_regex = re.compile(
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
+)
+_canonicalize_regex = re.compile(r"[-_.]+")
+_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$")
+# PEP 427: The build number must start with a digit.
+_build_tag_regex = re.compile(r"(\d+)(.*)")
+
+
+def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
+ if validate and not _validate_regex.match(name):
+ raise InvalidName(f"name is invalid: {name!r}")
+ # This is taken from PEP 503.
+ value = _canonicalize_regex.sub("-", name).lower()
+ return cast(NormalizedName, value)
+
+
+def is_normalized_name(name: str) -> bool:
+ return _normalized_regex.match(name) is not None
+
+
+@functools.singledispatch
+def canonicalize_version(
+ version: Version | str, *, strip_trailing_zero: bool = True
+) -> str:
+ """
+ Return a canonical form of a version as a string.
+
+ >>> canonicalize_version('1.0.1')
+ '1.0.1'
+
+ Per PEP 625, versions may have multiple canonical forms, differing
+ only by trailing zeros.
+
+ >>> canonicalize_version('1.0.0')
+ '1'
+ >>> canonicalize_version('1.0.0', strip_trailing_zero=False)
+ '1.0.0'
+
+ Invalid versions are returned unaltered.
+
+ >>> canonicalize_version('foo bar baz')
+ 'foo bar baz'
+ """
+ return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version)
+
+
+@canonicalize_version.register
+def _(version: str, *, strip_trailing_zero: bool = True) -> str:
+ try:
+ parsed = Version(version)
+ except InvalidVersion:
+ # Legacy versions cannot be normalized
+ return version
+ return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero)
+
+
+def parse_wheel_filename(
+ filename: str,
+) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
+ if not filename.endswith(".whl"):
+ raise InvalidWheelFilename(
+ f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
+ )
+
+ filename = filename[:-4]
+ dashes = filename.count("-")
+ if dashes not in (4, 5):
+ raise InvalidWheelFilename(
+ f"Invalid wheel filename (wrong number of parts): {filename!r}"
+ )
+
+ parts = filename.split("-", dashes - 2)
+ name_part = parts[0]
+ # See PEP 427 for the rules on escaping the project name.
+ if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
+ raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
+ name = canonicalize_name(name_part)
+
+ try:
+ version = Version(parts[1])
+ except InvalidVersion as e:
+ raise InvalidWheelFilename(
+ f"Invalid wheel filename (invalid version): {filename!r}"
+ ) from e
+
+ if dashes == 5:
+ build_part = parts[2]
+ build_match = _build_tag_regex.match(build_part)
+ if build_match is None:
+ raise InvalidWheelFilename(
+ f"Invalid build number: {build_part} in {filename!r}"
+ )
+ build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
+ else:
+ build = ()
+ tags = parse_tag(parts[-1])
+ return (name, version, build, tags)
+
+
+def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
+ if filename.endswith(".tar.gz"):
+ file_stem = filename[: -len(".tar.gz")]
+ elif filename.endswith(".zip"):
+ file_stem = filename[: -len(".zip")]
+ else:
+ raise InvalidSdistFilename(
+ f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
+ f" {filename!r}"
+ )
+
+ # We are requiring a PEP 440 version, which cannot contain dashes,
+ # so we split on the last dash.
+ name_part, sep, version_part = file_stem.rpartition("-")
+ if not sep:
+ raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
+
+ name = canonicalize_name(name_part)
+
+ try:
+ version = Version(version_part)
+ except InvalidVersion as e:
+ raise InvalidSdistFilename(
+ f"Invalid sdist filename (invalid version): {filename!r}"
+ ) from e
+
+ return (name, version)
diff --git a/env/Lib/site-packages/packaging/version.py b/env/Lib/site-packages/packaging/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9bbda20e463b8d9389ecd65f74af33810a02bdd
--- /dev/null
+++ b/env/Lib/site-packages/packaging/version.py
@@ -0,0 +1,582 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+"""
+.. testsetup::
+
+ from packaging.version import parse, Version
+"""
+
+from __future__ import annotations
+
+import itertools
+import re
+from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union
+
+from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
+
+__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
+
+LocalType = Tuple[Union[int, str], ...]
+
+CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
+CmpLocalType = Union[
+ NegativeInfinityType,
+ Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
+]
+CmpKey = Tuple[
+ int,
+ Tuple[int, ...],
+ CmpPrePostDevType,
+ CmpPrePostDevType,
+ CmpPrePostDevType,
+ CmpLocalType,
+]
+VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
+
+
+class _Version(NamedTuple):
+ epoch: int
+ release: tuple[int, ...]
+ dev: tuple[str, int] | None
+ pre: tuple[str, int] | None
+ post: tuple[str, int] | None
+ local: LocalType | None
+
+
+def parse(version: str) -> Version:
+ """Parse the given version string.
+
+ >>> parse('1.0.dev1')
+
+
+ :param version: The version string to parse.
+ :raises InvalidVersion: When the version string is not a valid version.
+ """
+ return Version(version)
+
+
+class InvalidVersion(ValueError):
+ """Raised when a version string is not a valid version.
+
+ >>> Version("invalid")
+ Traceback (most recent call last):
+ ...
+ packaging.version.InvalidVersion: Invalid version: 'invalid'
+ """
+
+
+class _BaseVersion:
+ _key: tuple[Any, ...]
+
+ def __hash__(self) -> int:
+ return hash(self._key)
+
+ # Please keep the duplicated `isinstance` check
+ # in the six comparisons hereunder
+ # unless you find a way to avoid adding overhead function calls.
+ def __lt__(self, other: _BaseVersion) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key < other._key
+
+ def __le__(self, other: _BaseVersion) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key <= other._key
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key == other._key
+
+ def __ge__(self, other: _BaseVersion) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key >= other._key
+
+ def __gt__(self, other: _BaseVersion) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key > other._key
+
+ def __ne__(self, other: object) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key != other._key
+
+
+# Deliberately not anchored to the start and end of the string, to make it
+# easier for 3rd party code to reuse
+_VERSION_PATTERN = r"""
+ v?
+ (?:
+ (?:(?P[0-9]+)!)? # epoch
+ (?P[0-9]+(?:\.[0-9]+)*) # release segment
+ (?P
# pre-release
+ [-_\.]?
+ (?Palpha|a|beta|b|preview|pre|c|rc)
+ [-_\.]?
+ (?P[0-9]+)?
+ )?
+ (?P # post release
+ (?:-(?P[0-9]+))
+ |
+ (?:
+ [-_\.]?
+ (?Ppost|rev|r)
+ [-_\.]?
+ (?P[0-9]+)?
+ )
+ )?
+ (?P # dev release
+ [-_\.]?
+ (?Pdev)
+ [-_\.]?
+ (?P[0-9]+)?
+ )?
+ )
+ (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
+"""
+
+VERSION_PATTERN = _VERSION_PATTERN
+"""
+A string containing the regular expression used to match a valid version.
+
+The pattern is not anchored at either end, and is intended for embedding in larger
+expressions (for example, matching a version number as part of a file name). The
+regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
+flags set.
+
+:meta hide-value:
+"""
+
+
+class Version(_BaseVersion):
+ """This class abstracts handling of a project's versions.
+
+ A :class:`Version` instance is comparison aware and can be compared and
+ sorted using the standard Python interfaces.
+
+ >>> v1 = Version("1.0a5")
+ >>> v2 = Version("1.0")
+ >>> v1
+
+ >>> v2
+
+ >>> v1 < v2
+ True
+ >>> v1 == v2
+ False
+ >>> v1 > v2
+ False
+ >>> v1 >= v2
+ False
+ >>> v1 <= v2
+ True
+ """
+
+ _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _key: CmpKey
+
+ def __init__(self, version: str) -> None:
+ """Initialize a Version object.
+
+ :param version:
+ The string representation of a version which will be parsed and normalized
+ before use.
+ :raises InvalidVersion:
+ If the ``version`` does not conform to PEP 440 in any way then this
+ exception will be raised.
+ """
+
+ # Validate the version and parse it into pieces
+ match = self._regex.search(version)
+ if not match:
+ raise InvalidVersion(f"Invalid version: {version!r}")
+
+ # Store the parsed out pieces of the version
+ self._version = _Version(
+ epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+ release=tuple(int(i) for i in match.group("release").split(".")),
+ pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+ post=_parse_letter_version(
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+ ),
+ dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+ local=_parse_local_version(match.group("local")),
+ )
+
+ # Generate a key which will be used for sorting
+ self._key = _cmpkey(
+ self._version.epoch,
+ self._version.release,
+ self._version.pre,
+ self._version.post,
+ self._version.dev,
+ self._version.local,
+ )
+
+ def __repr__(self) -> str:
+ """A representation of the Version that shows all internal state.
+
+ >>> Version('1.0.0')
+
+ """
+ return f""
+
+ def __str__(self) -> str:
+ """A string representation of the version that can be round-tripped.
+
+ >>> str(Version("1.0a5"))
+ '1.0a5'
+ """
+ parts = []
+
+ # Epoch
+ if self.epoch != 0:
+ parts.append(f"{self.epoch}!")
+
+ # Release segment
+ parts.append(".".join(str(x) for x in self.release))
+
+ # Pre-release
+ if self.pre is not None:
+ parts.append("".join(str(x) for x in self.pre))
+
+ # Post-release
+ if self.post is not None:
+ parts.append(f".post{self.post}")
+
+ # Development release
+ if self.dev is not None:
+ parts.append(f".dev{self.dev}")
+
+ # Local version segment
+ if self.local is not None:
+ parts.append(f"+{self.local}")
+
+ return "".join(parts)
+
+ @property
+ def epoch(self) -> int:
+ """The epoch of the version.
+
+ >>> Version("2.0.0").epoch
+ 0
+ >>> Version("1!2.0.0").epoch
+ 1
+ """
+ return self._version.epoch
+
+ @property
+ def release(self) -> tuple[int, ...]:
+ """The components of the "release" segment of the version.
+
+ >>> Version("1.2.3").release
+ (1, 2, 3)
+ >>> Version("2.0.0").release
+ (2, 0, 0)
+ >>> Version("1!2.0.0.post0").release
+ (2, 0, 0)
+
+ Includes trailing zeroes but not the epoch or any pre-release / development /
+ post-release suffixes.
+ """
+ return self._version.release
+
+ @property
+ def pre(self) -> tuple[str, int] | None:
+ """The pre-release segment of the version.
+
+ >>> print(Version("1.2.3").pre)
+ None
+ >>> Version("1.2.3a1").pre
+ ('a', 1)
+ >>> Version("1.2.3b1").pre
+ ('b', 1)
+ >>> Version("1.2.3rc1").pre
+ ('rc', 1)
+ """
+ return self._version.pre
+
+ @property
+ def post(self) -> int | None:
+ """The post-release number of the version.
+
+ >>> print(Version("1.2.3").post)
+ None
+ >>> Version("1.2.3.post1").post
+ 1
+ """
+ return self._version.post[1] if self._version.post else None
+
+ @property
+ def dev(self) -> int | None:
+ """The development number of the version.
+
+ >>> print(Version("1.2.3").dev)
+ None
+ >>> Version("1.2.3.dev1").dev
+ 1
+ """
+ return self._version.dev[1] if self._version.dev else None
+
+ @property
+ def local(self) -> str | None:
+ """The local version segment of the version.
+
+ >>> print(Version("1.2.3").local)
+ None
+ >>> Version("1.2.3+abc").local
+ 'abc'
+ """
+ if self._version.local:
+ return ".".join(str(x) for x in self._version.local)
+ else:
+ return None
+
+ @property
+ def public(self) -> str:
+ """The public portion of the version.
+
+ >>> Version("1.2.3").public
+ '1.2.3'
+ >>> Version("1.2.3+abc").public
+ '1.2.3'
+ >>> Version("1!1.2.3dev1+abc").public
+ '1!1.2.3.dev1'
+ """
+ return str(self).split("+", 1)[0]
+
+ @property
+ def base_version(self) -> str:
+ """The "base version" of the version.
+
+ >>> Version("1.2.3").base_version
+ '1.2.3'
+ >>> Version("1.2.3+abc").base_version
+ '1.2.3'
+ >>> Version("1!1.2.3dev1+abc").base_version
+ '1!1.2.3'
+
+ The "base version" is the public version of the project without any pre or post
+ release markers.
+ """
+ parts = []
+
+ # Epoch
+ if self.epoch != 0:
+ parts.append(f"{self.epoch}!")
+
+ # Release segment
+ parts.append(".".join(str(x) for x in self.release))
+
+ return "".join(parts)
+
+ @property
+ def is_prerelease(self) -> bool:
+ """Whether this version is a pre-release.
+
+ >>> Version("1.2.3").is_prerelease
+ False
+ >>> Version("1.2.3a1").is_prerelease
+ True
+ >>> Version("1.2.3b1").is_prerelease
+ True
+ >>> Version("1.2.3rc1").is_prerelease
+ True
+ >>> Version("1.2.3dev1").is_prerelease
+ True
+ """
+ return self.dev is not None or self.pre is not None
+
+ @property
+ def is_postrelease(self) -> bool:
+ """Whether this version is a post-release.
+
+ >>> Version("1.2.3").is_postrelease
+ False
+ >>> Version("1.2.3.post1").is_postrelease
+ True
+ """
+ return self.post is not None
+
+ @property
+ def is_devrelease(self) -> bool:
+ """Whether this version is a development release.
+
+ >>> Version("1.2.3").is_devrelease
+ False
+ >>> Version("1.2.3.dev1").is_devrelease
+ True
+ """
+ return self.dev is not None
+
+ @property
+ def major(self) -> int:
+ """The first item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").major
+ 1
+ """
+ return self.release[0] if len(self.release) >= 1 else 0
+
+ @property
+ def minor(self) -> int:
+ """The second item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").minor
+ 2
+ >>> Version("1").minor
+ 0
+ """
+ return self.release[1] if len(self.release) >= 2 else 0
+
+ @property
+ def micro(self) -> int:
+ """The third item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").micro
+ 3
+ >>> Version("1").micro
+ 0
+ """
+ return self.release[2] if len(self.release) >= 3 else 0
+
+
+class _TrimmedRelease(Version):
+ @property
+ def release(self) -> tuple[int, ...]:
+ """
+ Release segment without any trailing zeros.
+
+ >>> _TrimmedRelease('1.0.0').release
+ (1,)
+ >>> _TrimmedRelease('0.0').release
+ (0,)
+ """
+ rel = super().release
+ nonzeros = (index for index, val in enumerate(rel) if val)
+ last_nonzero = max(nonzeros, default=0)
+ return rel[: last_nonzero + 1]
+
+
+def _parse_letter_version(
+ letter: str | None, number: str | bytes | SupportsInt | None
+) -> tuple[str, int] | None:
+ if letter:
+ # We consider there to be an implicit 0 in a pre-release if there is
+ # not a numeral associated with it.
+ if number is None:
+ number = 0
+
+ # We normalize any letters to their lower case form
+ letter = letter.lower()
+
+ # We consider some words to be alternate spellings of other words and
+ # in those cases we want to normalize the spellings to our preferred
+ # spelling.
+ if letter == "alpha":
+ letter = "a"
+ elif letter == "beta":
+ letter = "b"
+ elif letter in ["c", "pre", "preview"]:
+ letter = "rc"
+ elif letter in ["rev", "r"]:
+ letter = "post"
+
+ return letter, int(number)
+
+ assert not letter
+ if number:
+ # We assume if we are given a number, but we are not given a letter
+ # then this is using the implicit post release syntax (e.g. 1.0-1)
+ letter = "post"
+
+ return letter, int(number)
+
+ return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: str | None) -> LocalType | None:
+ """
+ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+ """
+ if local is not None:
+ return tuple(
+ part.lower() if not part.isdigit() else int(part)
+ for part in _local_version_separators.split(local)
+ )
+ return None
+
+
+def _cmpkey(
+ epoch: int,
+ release: tuple[int, ...],
+ pre: tuple[str, int] | None,
+ post: tuple[str, int] | None,
+ dev: tuple[str, int] | None,
+ local: LocalType | None,
+) -> CmpKey:
+ # When we compare a release version, we want to compare it with all of the
+ # trailing zeros removed. So we'll use a reverse the list, drop all the now
+ # leading zeros until we come to something non zero, then take the rest
+ # re-reverse it back into the correct order and make it a tuple and use
+ # that for our sorting key.
+ _release = tuple(
+ reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+ )
+
+ # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+ # We'll do this by abusing the pre segment, but we _only_ want to do this
+ # if there is not a pre or a post segment. If we have one of those then
+ # the normal sorting rules will handle this case correctly.
+ if pre is None and post is None and dev is not None:
+ _pre: CmpPrePostDevType = NegativeInfinity
+ # Versions without a pre-release (except as noted above) should sort after
+ # those with one.
+ elif pre is None:
+ _pre = Infinity
+ else:
+ _pre = pre
+
+ # Versions without a post segment should sort before those with one.
+ if post is None:
+ _post: CmpPrePostDevType = NegativeInfinity
+
+ else:
+ _post = post
+
+ # Versions without a development segment should sort after those with one.
+ if dev is None:
+ _dev: CmpPrePostDevType = Infinity
+
+ else:
+ _dev = dev
+
+ if local is None:
+ # Versions without a local segment should sort before those with one.
+ _local: CmpLocalType = NegativeInfinity
+ else:
+ # Versions with a local segment need that segment parsed to implement
+ # the sorting rules in PEP440.
+ # - Alpha numeric segments sort before numeric segments
+ # - Alpha numeric segments sort lexicographically
+ # - Numeric segments sort numerically
+ # - Shorter versions sort before longer versions when the prefixes
+ # match exactly
+ _local = tuple(
+ (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+ )
+
+ return epoch, _release, _pre, _post, _dev, _local
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/AUTHORS.txt b/env/Lib/site-packages/pip-24.3.1.dist-info/AUTHORS.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8ccefbc6e59f0bc761589c7994c15c7922ce5127
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/AUTHORS.txt
@@ -0,0 +1,799 @@
+@Switch01
+A_Rog
+Aakanksha Agrawal
+Abhinav Sagar
+ABHYUDAY PRATAP SINGH
+abs51295
+AceGentile
+Adam Chainz
+Adam Tse
+Adam Wentz
+admin
+Adolfo Ochagavía
+Adrien Morison
+Agus
+ahayrapetyan
+Ahilya
+AinsworthK
+Akash Srivastava
+Alan Yee
+Albert Tugushev
+Albert-Guan
+albertg
+Alberto Sottile
+Aleks Bunin
+Ales Erjavec
+Alethea Flowers
+Alex Gaynor
+Alex Grönholm
+Alex Hedges
+Alex Loosley
+Alex Morega
+Alex Stachowiak
+Alexander Shtyrov
+Alexandre Conrad
+Alexey Popravka
+Aleš Erjavec
+Alli
+Ami Fischman
+Ananya Maiti
+Anatoly Techtonik
+Anders Kaseorg
+Andre Aguiar
+Andreas Lutro
+Andrei Geacar
+Andrew Gaul
+Andrew Shymanel
+Andrey Bienkowski
+Andrey Bulgakov
+Andrés Delfino
+Andy Freeland
+Andy Kluger
+Ani Hayrapetyan
+Aniruddha Basak
+Anish Tambe
+Anrs Hu
+Anthony Sottile
+Antoine Musso
+Anton Ovchinnikov
+Anton Patrushev
+Anton Zelenov
+Antonio Alvarado Hernandez
+Antony Lee
+Antti Kaihola
+Anubhav Patel
+Anudit Nagar
+Anuj Godase
+AQNOUCH Mohammed
+AraHaan
+arena
+arenasys
+Arindam Choudhury
+Armin Ronacher
+Arnon Yaari
+Artem
+Arun Babu Neelicattu
+Ashley Manton
+Ashwin Ramaswami
+atse
+Atsushi Odagiri
+Avinash Karhana
+Avner Cohen
+Awit (Ah-Wit) Ghirmai
+Baptiste Mispelon
+Barney Gale
+barneygale
+Bartek Ogryczak
+Bastian Venthur
+Ben Bodenmiller
+Ben Darnell
+Ben Hoyt
+Ben Mares
+Ben Rosser
+Bence Nagy
+Benjamin Peterson
+Benjamin VanEvery
+Benoit Pierre
+Berker Peksag
+Bernard
+Bernard Tyers
+Bernardo B. Marques
+Bernhard M. Wiedemann
+Bertil Hatt
+Bhavam Vidyarthi
+Blazej Michalik
+Bogdan Opanchuk
+BorisZZZ
+Brad Erickson
+Bradley Ayers
+Branch Vincent
+Brandon L. Reiss
+Brandt Bucher
+Brannon Dorsey
+Brett Randall
+Brett Rosen
+Brian Cristante
+Brian Rosner
+briantracy
+BrownTruck
+Bruno Oliveira
+Bruno Renié
+Bruno S
+Bstrdsmkr
+Buck Golemon
+burrows
+Bussonnier Matthias
+bwoodsend
+c22
+Caleb Martinez
+Calvin Smith
+Carl Meyer
+Carlos Liam
+Carol Willing
+Carter Thayer
+Cass
+Chandrasekhar Atina
+Charlie Marsh
+Chih-Hsuan Yen
+Chris Brinker
+Chris Hunt
+Chris Jerdonek
+Chris Kuehl
+Chris Markiewicz
+Chris McDonough
+Chris Pawley
+Chris Pryer
+Chris Wolfe
+Christian Clauss
+Christian Heimes
+Christian Oudard
+Christoph Reiter
+Christopher Hunt
+Christopher Snyder
+chrysle
+cjc7373
+Clark Boylan
+Claudio Jolowicz
+Clay McClure
+Cody
+Cody Soyland
+Colin Watson
+Collin Anderson
+Connor Osborn
+Cooper Lees
+Cooper Ry Lees
+Cory Benfield
+Cory Wright
+Craig Kerstiens
+Cristian Sorinel
+Cristina
+Cristina Muñoz
+ctg123
+Curtis Doty
+cytolentino
+Daan De Meyer
+Dale
+Damian
+Damian Quiroga
+Damian Shaw
+Dan Black
+Dan Savilonis
+Dan Sully
+Dane Hillard
+daniel
+Daniel Collins
+Daniel Hahler
+Daniel Holth
+Daniel Jost
+Daniel Katz
+Daniel Shaulov
+Daniele Esposti
+Daniele Nicolodi
+Daniele Procida
+Daniil Konovalenko
+Danny Hermes
+Danny McClanahan
+Darren Kavanagh
+Dav Clark
+Dave Abrahams
+Dave Jones
+David Aguilar
+David Black
+David Bordeynik
+David Caro
+David D Lowe
+David Evans
+David Hewitt
+David Linke
+David Poggi
+David Poznik
+David Pursehouse
+David Runge
+David Tucker
+David Wales
+Davidovich
+ddelange
+Deepak Sharma
+Deepyaman Datta
+Denise Yu
+dependabot[bot]
+derwolfe
+Desetude
+Devesh Kumar Singh
+devsagul
+Diego Caraballo
+Diego Ramirez
+DiegoCaraballo
+Dimitri Merejkowsky
+Dimitri Papadopoulos
+Dimitri Papadopoulos Orfanos
+Dirk Stolle
+Dmitry Gladkov
+Dmitry Volodin
+Domen Kožar
+Dominic Davis-Foster
+Donald Stufft
+Dongweiming
+doron zarhi
+Dos Moonen
+Douglas Thor
+DrFeathers
+Dustin Ingram
+Dustin Rodrigues
+Dwayne Bailey
+Ed Morley
+Edgar Ramírez
+Edgar Ramírez Mondragón
+Ee Durbin
+Efflam Lemaillet
+efflamlemaillet
+Eitan Adler
+ekristina
+elainechan
+Eli Schwartz
+Elisha Hollander
+Ellen Marie Dash
+Emil Burzo
+Emil Styrke
+Emmanuel Arias
+Endoh Takanao
+enoch
+Erdinc Mutlu
+Eric Cousineau
+Eric Gillingham
+Eric Hanchrow
+Eric Hopper
+Erik M. Bray
+Erik Rose
+Erwin Janssen
+Eugene Vereshchagin
+everdimension
+Federico
+Felipe Peter
+Felix Yan
+fiber-space
+Filip Kokosiński
+Filipe Laíns
+Finn Womack
+finnagin
+Flavio Amurrio
+Florian Briand
+Florian Rathgeber
+Francesco
+Francesco Montesano
+Fredrik Orderud
+Frost Ming
+Gabriel Curio
+Gabriel de Perthuis
+Garry Polley
+gavin
+gdanielson
+Geoffrey Sneddon
+George Song
+Georgi Valkov
+Georgy Pchelkin
+ghost
+Giftlin Rajaiah
+gizmoguy1
+gkdoc
+Godefroid Chapelle
+Gopinath M
+GOTO Hayato
+gousaiyang
+gpiks
+Greg Roodt
+Greg Ward
+Guilherme Espada
+Guillaume Seguin
+gutsytechster
+Guy Rozendorn
+Guy Tuval
+gzpan123
+Hanjun Kim
+Hari Charan
+Harsh Vardhan
+harupy
+Harutaka Kawamura
+hauntsaninja
+Henrich Hartzer
+Henry Schreiner
+Herbert Pfennig
+Holly Stotelmyer
+Honnix
+Hsiaoming Yang
+Hugo Lopes Tavares
+Hugo van Kemenade
+Hugues Bruant
+Hynek Schlawack
+Ian Bicking
+Ian Cordasco
+Ian Lee
+Ian Stapleton Cordasco
+Ian Wienand
+Igor Kuzmitshov
+Igor Sobreira
+Ikko Ashimine
+Ilan Schnell
+Illia Volochii
+Ilya Baryshev
+Inada Naoki
+Ionel Cristian Mărieș
+Ionel Maries Cristian
+Itamar Turner-Trauring
+Ivan Pozdeev
+J. Nick Koston
+Jacob Kim
+Jacob Walls
+Jaime Sanz
+jakirkham
+Jakub Kuczys
+Jakub Stasiak
+Jakub Vysoky
+Jakub Wilk
+James Cleveland
+James Curtin
+James Firth
+James Gerity
+James Polley
+Jan Pokorný
+Jannis Leidel
+Jarek Potiuk
+jarondl
+Jason Curtis
+Jason R. Coombs
+JasonMo
+JasonMo1
+Jay Graves
+Jean Abou Samra
+Jean-Christophe Fillion-Robin
+Jeff Barber
+Jeff Dairiki
+Jeff Widman
+Jelmer Vernooij
+jenix21
+Jeremy Fleischman
+Jeremy Stanley
+Jeremy Zafran
+Jesse Rittner
+Jiashuo Li
+Jim Fisher
+Jim Garrison
+Jinzhe Zeng
+Jiun Bae
+Jivan Amara
+Joe Bylund
+Joe Michelini
+John Paton
+John Sirois
+John T. Wodder II
+John-Scott Atlakson
+johnthagen
+Jon Banafato
+Jon Dufresne
+Jon Parise
+Jonas Nockert
+Jonathan Herbert
+Joonatan Partanen
+Joost Molenaar
+Jorge Niedbalski
+Joseph Bylund
+Joseph Long
+Josh Bronson
+Josh Cannon
+Josh Hansen
+Josh Schneier
+Joshua
+Juan Luis Cano Rodríguez
+Juanjo Bazán
+Judah Rand
+Julian Berman
+Julian Gethmann
+Julien Demoor
+Jussi Kukkonen
+jwg4
+Jyrki Pulliainen
+Kai Chen
+Kai Mueller
+Kamal Bin Mustafa
+kasium
+kaustav haldar
+keanemind
+Keith Maxwell
+Kelsey Hightower
+Kenneth Belitzky
+Kenneth Reitz
+Kevin Burke
+Kevin Carter
+Kevin Frommelt
+Kevin R Patterson
+Kexuan Sun
+Kit Randel
+Klaas van Schelven
+KOLANICH
+konstin
+kpinc
+Krishna Oza
+Kumar McMillan
+Kuntal Majumder
+Kurt McKee
+Kyle Persohn
+lakshmanaram
+Laszlo Kiss-Kollar
+Laurent Bristiel
+Laurent LAPORTE
+Laurie O
+Laurie Opperman
+layday
+Leon Sasson
+Lev Givon
+Lincoln de Sousa
+Lipis
+lorddavidiii
+Loren Carvalho
+Lucas Cimon
+Ludovic Gasc
+Luis Medel
+Lukas Geiger
+Lukas Juhrich
+Luke Macken
+Luo Jiebin
+luojiebin
+luz.paz
+László Kiss Kollár
+M00nL1ght
+Marc Abramowitz
+Marc Tamlyn
+Marcus Smith
+Mariatta
+Mark Kohler
+Mark McLoughlin
+Mark Williams
+Markus Hametner
+Martey Dodoo
+Martin Fischer
+Martin Häcker
+Martin Pavlasek
+Masaki
+Masklinn
+Matej Stuchlik
+Mathew Jennings
+Mathieu Bridon
+Mathieu Kniewallner
+Matt Bacchi
+Matt Good
+Matt Maker
+Matt Robenolt
+Matt Wozniski
+matthew
+Matthew Einhorn
+Matthew Feickert
+Matthew Gilliard
+Matthew Hughes
+Matthew Iversen
+Matthew Treinish
+Matthew Trumbell
+Matthew Willson
+Matthias Bussonnier
+mattip
+Maurits van Rees
+Max W Chase
+Maxim Kurnikov
+Maxime Rouyrre
+mayeut
+mbaluna
+mdebi
+memoselyk
+meowmeowcat
+Michael
+Michael Aquilina
+Michael E. Karpeles
+Michael Klich
+Michael Mintz
+Michael Williamson
+michaelpacer
+Michał Górny
+Mickaël Schoentgen
+Miguel Araujo Perez
+Mihir Singh
+Mike
+Mike Hendricks
+Min RK
+MinRK
+Miro Hrončok
+Monica Baluna
+montefra
+Monty Taylor
+morotti
+mrKazzila
+Muha Ajjan
+Nadav Wexler
+Nahuel Ambrosini
+Nate Coraor
+Nate Prewitt
+Nathan Houghton
+Nathaniel J. Smith
+Nehal J Wani
+Neil Botelho
+Nguyễn Gia Phong
+Nicholas Serra
+Nick Coghlan
+Nick Stenning
+Nick Timkovich
+Nicolas Bock
+Nicole Harris
+Nikhil Benesch
+Nikhil Ladha
+Nikita Chepanov
+Nikolay Korolev
+Nipunn Koorapati
+Nitesh Sharma
+Niyas Sait
+Noah
+Noah Gorny
+Nowell Strite
+NtaleGrey
+nvdv
+OBITORASU
+Ofek Lev
+ofrinevo
+Oliver Freund
+Oliver Jeeves
+Oliver Mannion
+Oliver Tonnhofer
+Olivier Girardot
+Olivier Grisel
+Ollie Rutherfurd
+OMOTO Kenji
+Omry Yadan
+onlinejudge95
+Oren Held
+Oscar Benjamin
+Oz N Tiram
+Pachwenko
+Patrick Dubroy
+Patrick Jenkins
+Patrick Lawson
+patricktokeeffe
+Patrik Kopkan
+Paul Ganssle
+Paul Kehrer
+Paul Moore
+Paul Nasrat
+Paul Oswald
+Paul van der Linden
+Paulus Schoutsen
+Pavel Safronov
+Pavithra Eswaramoorthy
+Pawel Jasinski
+Paweł Szramowski
+Pekka Klärck
+Peter Gessler
+Peter Lisák
+Peter Shen
+Peter Waller
+Petr Viktorin
+petr-tik
+Phaneendra Chiruvella
+Phil Elson
+Phil Freo
+Phil Pennock
+Phil Whelan
+Philip Jägenstedt
+Philip Molloy
+Philippe Ombredanne
+Pi Delport
+Pierre-Yves Rofes
+Pieter Degroote
+pip
+Prabakaran Kumaresshan
+Prabhjyotsing Surjit Singh Sodhi
+Prabhu Marappan
+Pradyun Gedam
+Prashant Sharma
+Pratik Mallya
+pre-commit-ci[bot]
+Preet Thakkar
+Preston Holmes
+Przemek Wrzos
+Pulkit Goyal
+q0w
+Qiangning Hong
+Qiming Xu
+Quentin Lee
+Quentin Pradet
+R. David Murray
+Rafael Caricio
+Ralf Schmitt
+Ran Benita
+Razzi Abuissa
+rdb
+Reece Dunham
+Remi Rampin
+Rene Dudfield
+Riccardo Magliocchetti
+Riccardo Schirone
+Richard Jones
+Richard Si
+Ricky Ng-Adam
+Rishi
+rmorotti
+RobberPhex
+Robert Collins
+Robert McGibbon
+Robert Pollak
+Robert T. McGibbon
+robin elisha robinson
+Roey Berman
+Rohan Jain
+Roman Bogorodskiy
+Roman Donchenko
+Romuald Brunet
+ronaudinho
+Ronny Pfannschmidt
+Rory McCann
+Ross Brattain
+Roy Wellington Ⅳ
+Ruairidh MacLeod
+Russell Keith-Magee
+Ryan Shepherd
+Ryan Wooden
+ryneeverett
+S. Guliaev
+Sachi King
+Salvatore Rinchiera
+sandeepkiran-js
+Sander Van Balen
+Savio Jomton
+schlamar
+Scott Kitterman
+Sean
+seanj
+Sebastian Jordan
+Sebastian Schaetz
+Segev Finer
+SeongSoo Cho
+Sergey Vasilyev
+Seth Michael Larson
+Seth Woodworth
+Shahar Epstein
+Shantanu
+shenxianpeng
+shireenrao
+Shivansh-007
+Shixian Sheng
+Shlomi Fish
+Shovan Maity
+Simeon Visser
+Simon Cross
+Simon Pichugin
+sinoroc
+sinscary
+snook92
+socketubs
+Sorin Sbarnea
+Srinivas Nyayapati
+Srishti Hegde
+Stavros Korokithakis
+Stefan Scherfke
+Stefano Rivera
+Stephan Erb
+Stephen Rosen
+stepshal
+Steve (Gadget) Barnes
+Steve Barnes
+Steve Dower
+Steve Kowalik
+Steven Myint
+Steven Silvester
+stonebig
+studioj
+Stéphane Bidoul
+Stéphane Bidoul (ACSONE)
+Stéphane Klein
+Sumana Harihareswara
+Surbhi Sharma
+Sviatoslav Sydorenko
+Sviatoslav Sydorenko (Святослав Сидоренко)
+Swat009
+Sylvain
+Takayuki SHIMIZUKAWA
+Taneli Hukkinen
+tbeswick
+Thiago
+Thijs Triemstra
+Thomas Fenzl
+Thomas Grainger
+Thomas Guettler
+Thomas Johansson
+Thomas Kluyver
+Thomas Smith
+Thomas VINCENT
+Tim D. Smith
+Tim Gates
+Tim Harder
+Tim Heap
+tim smith
+tinruufu
+Tobias Hermann
+Tom Forbes
+Tom Freudenheim
+Tom V
+Tomas Hrnciar
+Tomas Orsava
+Tomer Chachamu
+Tommi Enenkel | AnB
+Tomáš Hrnčiar
+Tony Beswick
+Tony Narlock
+Tony Zhaocheng Tan
+TonyBeswick
+toonarmycaptain
+Toshio Kuratomi
+toxinu
+Travis Swicegood
+Tushar Sadhwani
+Tzu-ping Chung
+Valentin Haenel
+Victor Stinner
+victorvpaulo
+Vikram - Google
+Viktor Szépe
+Ville Skyttä
+Vinay Sajip
+Vincent Philippon
+Vinicyus Macedo
+Vipul Kumar
+Vitaly Babiy
+Vladimir Fokow
+Vladimir Rutsky
+W. Trevor King
+Wil Tan
+Wilfred Hughes
+William Edwards
+William ML Leslie
+William T Olson
+William Woodruff
+Wilson Mo
+wim glenn
+Winson Luk
+Wolfgang Maier
+Wu Zhenyu
+XAMES3
+Xavier Fernandez
+Xianpeng Shen
+xoviat
+xtreak
+YAMAMOTO Takashi
+Yen Chi Hsuan
+Yeray Diaz Diaz
+Yoval P
+Yu Jian
+Yuan Jing Vincent Yan
+Yusuke Hayashi
+Zearin
+Zhiping Deng
+ziebam
+Zvezdan Petkovic
+Łukasz Langa
+Роман Донченко
+Семён Марьясин
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/INSTALLER b/env/Lib/site-packages/pip-24.3.1.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/LICENSE.txt b/env/Lib/site-packages/pip-24.3.1.dist-info/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8e7b65eaf628360e6f32f4140fcdd7ec7c2b7077
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/METADATA b/env/Lib/site-packages/pip-24.3.1.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..9e5aa3a486f7e6a60761c3b0760a62730478d741
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/METADATA
@@ -0,0 +1,90 @@
+Metadata-Version: 2.1
+Name: pip
+Version: 24.3.1
+Summary: The PyPA recommended tool for installing Python packages.
+Author-email: The pip developers
+License: MIT
+Project-URL: Homepage, https://pip.pypa.io/
+Project-URL: Documentation, https://pip.pypa.io
+Project-URL: Source, https://github.com/pypa/pip
+Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Software Development :: Build Tools
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+License-File: LICENSE.txt
+License-File: AUTHORS.txt
+
+pip - The Python Package Installer
+==================================
+
+.. |pypi-version| image:: https://img.shields.io/pypi/v/pip.svg
+ :target: https://pypi.org/project/pip/
+ :alt: PyPI
+
+.. |python-versions| image:: https://img.shields.io/pypi/pyversions/pip
+ :target: https://pypi.org/project/pip
+ :alt: PyPI - Python Version
+
+.. |docs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest
+ :target: https://pip.pypa.io/en/latest
+ :alt: Documentation
+
+|pypi-version| |python-versions| |docs-badge|
+
+pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
+
+Please take a look at our documentation for how to install and use pip:
+
+* `Installation`_
+* `Usage`_
+
+We release updates regularly, with a new version every 3 months. Find more details in our documentation:
+
+* `Release notes`_
+* `Release process`_
+
+If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
+
+* `Issue tracking`_
+* `Discourse channel`_
+* `User IRC`_
+
+If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
+
+* `GitHub page`_
+* `Development documentation`_
+* `Development IRC`_
+
+Code of Conduct
+---------------
+
+Everyone interacting in the pip project's codebases, issue trackers, chat
+rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
+
+.. _package installer: https://packaging.python.org/guides/tool-recommendations/
+.. _Python Package Index: https://pypi.org
+.. _Installation: https://pip.pypa.io/en/stable/installation/
+.. _Usage: https://pip.pypa.io/en/stable/
+.. _Release notes: https://pip.pypa.io/en/stable/news.html
+.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
+.. _GitHub page: https://github.com/pypa/pip
+.. _Development documentation: https://pip.pypa.io/en/latest/development
+.. _Issue tracking: https://github.com/pypa/pip/issues
+.. _Discourse channel: https://discuss.python.org/c/packaging
+.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
+.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
+.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/RECORD b/env/Lib/site-packages/pip-24.3.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..f35614d157fecc0a01884ef826c72987439648db
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/RECORD
@@ -0,0 +1,853 @@
+../../Scripts/pip.exe,sha256=jFHTWAAi0A_4mCr5vYynrTLI1XerlvxDC-llYhXabUc,108406
+../../Scripts/pip3.12.exe,sha256=jFHTWAAi0A_4mCr5vYynrTLI1XerlvxDC-llYhXabUc,108406
+../../Scripts/pip3.exe,sha256=jFHTWAAi0A_4mCr5vYynrTLI1XerlvxDC-llYhXabUc,108406
+pip-24.3.1.dist-info/AUTHORS.txt,sha256=Cbb630k8EL9FkBzX9Vpi6hpYWrLSlh08eXodL5u0eLI,10925
+pip-24.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pip-24.3.1.dist-info/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093
+pip-24.3.1.dist-info/METADATA,sha256=V8iCNK1GYbC82PWsLMsASDh9AO4veocRlM4Pn9q2KFI,3677
+pip-24.3.1.dist-info/RECORD,,
+pip-24.3.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip-24.3.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
+pip-24.3.1.dist-info/entry_points.txt,sha256=eeIjuzfnfR2PrhbjnbzFU6MnSS70kZLxwaHHq6M-bD0,87
+pip-24.3.1.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pip/__init__.py,sha256=faXY_neeYrA_88plEhkyhwAaYeds7wu5U1iGwP24J0s,357
+pip/__main__.py,sha256=WzbhHXTbSE6gBY19mNN9m4s5o_365LOvTYSgqgbdBhE,854
+pip/__pip-runner__.py,sha256=cPPWuJ6NK_k-GzfvlejLFgwzmYUROmpAR6QC3Q-vkXQ,1450
+pip/__pycache__/__init__.cpython-312.pyc,,
+pip/__pycache__/__main__.cpython-312.pyc,,
+pip/__pycache__/__pip-runner__.cpython-312.pyc,,
+pip/_internal/__init__.py,sha256=MfcoOluDZ8QMCFYal04IqOJ9q6m2V7a0aOsnI-WOxUo,513
+pip/_internal/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/__pycache__/build_env.cpython-312.pyc,,
+pip/_internal/__pycache__/cache.cpython-312.pyc,,
+pip/_internal/__pycache__/configuration.cpython-312.pyc,,
+pip/_internal/__pycache__/exceptions.cpython-312.pyc,,
+pip/_internal/__pycache__/main.cpython-312.pyc,,
+pip/_internal/__pycache__/pyproject.cpython-312.pyc,,
+pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc,,
+pip/_internal/__pycache__/wheel_builder.cpython-312.pyc,,
+pip/_internal/build_env.py,sha256=wsTPOWyPTKvUREUcO585OU01kbQufpdigY8fVHv3WIw,10584
+pip/_internal/cache.py,sha256=Jb698p5PNigRtpW5o26wQNkkUv4MnQ94mc471wL63A0,10369
+pip/_internal/cli/__init__.py,sha256=FkHBgpxxb-_gd6r1FjnNhfMOzAUYyXoXKJ6abijfcFU,132
+pip/_internal/cli/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/base_command.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/command_context.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/index_command.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/main.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/parser.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/req_command.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/spinners.cpython-312.pyc,,
+pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc,,
+pip/_internal/cli/autocompletion.py,sha256=Lli3Mr6aDNu7ZkJJFFvwD2-hFxNI6Avz8OwMyS5TVrs,6865
+pip/_internal/cli/base_command.py,sha256=F8nUcSM-Y-MQljJUe724-yxmc5viFXHyM_zH70NmIh4,8289
+pip/_internal/cli/cmdoptions.py,sha256=mDqBr0d0hoztbRJs-PWtcKpqNAc7khU6ZpoesZKocT8,30110
+pip/_internal/cli/command_context.py,sha256=RHgIPwtObh5KhMrd3YZTkl8zbVG-6Okml7YbFX4Ehg0,774
+pip/_internal/cli/index_command.py,sha256=-0oPTruZGkLSMrWDleZ6UtcKP3G-SImRRuhH0RfVE3o,5631
+pip/_internal/cli/main.py,sha256=BDZef-bWe9g9Jpr4OVs4dDf-845HJsKw835T7AqEnAc,2817
+pip/_internal/cli/main_parser.py,sha256=laDpsuBDl6kyfywp9eMMA9s84jfH2TJJn-vmL0GG90w,4338
+pip/_internal/cli/parser.py,sha256=VCMtduzECUV87KaHNu-xJ-wLNL82yT3x16V4XBxOAqI,10825
+pip/_internal/cli/progress_bars.py,sha256=VgydyqjZvfhqpuNcFDn00QNuA9GxRe9CKrRG8jhPuKU,2723
+pip/_internal/cli/req_command.py,sha256=DqeFhmUMs6o6Ev8qawAcOoYNdAZsfyKS0MZI5jsJYwQ,12250
+pip/_internal/cli/spinners.py,sha256=hIJ83GerdFgFCdobIA23Jggetegl_uC4Sp586nzFbPE,5118
+pip/_internal/cli/status_codes.py,sha256=sEFHUaUJbqv8iArL3HAtcztWZmGOFX01hTesSytDEh0,116
+pip/_internal/commands/__init__.py,sha256=5oRO9O3dM2vGuh0bFw4HOVletryrz5HHMmmPWwJrH9U,3882
+pip/_internal/commands/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/cache.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/check.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/completion.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/configuration.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/debug.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/download.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/freeze.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/hash.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/help.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/index.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/inspect.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/install.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/list.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/search.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/show.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc,,
+pip/_internal/commands/__pycache__/wheel.cpython-312.pyc,,
+pip/_internal/commands/cache.py,sha256=xg76_ZFEBC6zoQ3gXLRfMZJft4z2a0RwH4GEFZC6nnU,7944
+pip/_internal/commands/check.py,sha256=Hr_4eiMd9cgVDgEvjtIdw915NmL7ROIWW8enkr8slPQ,2268
+pip/_internal/commands/completion.py,sha256=HT4lD0bgsflHq2IDgYfiEdp7IGGtE7s6MgI3xn0VQEw,4287
+pip/_internal/commands/configuration.py,sha256=n98enwp6y0b5G6fiRQjaZo43FlJKYve_daMhN-4BRNc,9766
+pip/_internal/commands/debug.py,sha256=DNDRgE9YsKrbYzU0s3VKi8rHtKF4X13CJ_br_8PUXO0,6797
+pip/_internal/commands/download.py,sha256=0qB0nys6ZEPsog451lDsjL5Bx7Z97t-B80oFZKhpzKM,5273
+pip/_internal/commands/freeze.py,sha256=2Vt72BYTSm9rzue6d8dNzt8idxWK4Db6Hd-anq7GQ80,3203
+pip/_internal/commands/hash.py,sha256=EVVOuvGtoPEdFi8SNnmdqlCQrhCxV-kJsdwtdcCnXGQ,1703
+pip/_internal/commands/help.py,sha256=gcc6QDkcgHMOuAn5UxaZwAStsRBrnGSn_yxjS57JIoM,1132
+pip/_internal/commands/index.py,sha256=RAXxmJwFhVb5S1BYzb5ifX3sn9Na8v2CCVYwSMP8pao,4731
+pip/_internal/commands/inspect.py,sha256=PGrY9TRTRCM3y5Ml8Bdk8DEOXquWRfscr4DRo1LOTPc,3189
+pip/_internal/commands/install.py,sha256=iqesiLIZc6Op9uihMQFYRhAA2DQRZUxbM4z1BwXoFls,29428
+pip/_internal/commands/list.py,sha256=oiIzSjLP6__d7dIS3q0Xb5ywsaOThBWRqMyjjKzkPdM,12769
+pip/_internal/commands/search.py,sha256=fWkUQVx_gm8ebbFAlCgqtxKXT9rNahpJ-BI__3HNZpg,5626
+pip/_internal/commands/show.py,sha256=IG9L5uo8w6UA4tI_IlmaxLCoNKPa5JNJCljj3NWs0OE,7507
+pip/_internal/commands/uninstall.py,sha256=7pOR7enK76gimyxQbzxcG1OsyLXL3DvX939xmM8Fvtg,3892
+pip/_internal/commands/wheel.py,sha256=eJRhr_qoNNxWAkkdJCNiQM7CXd4E1_YyQhsqJnBPGGg,6414
+pip/_internal/configuration.py,sha256=XkAiBS0hpzsM-LF0Qu5hvPWO_Bs67-oQKRYFBuMbESs,14006
+pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858
+pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/distributions/__pycache__/base.cpython-312.pyc,,
+pip/_internal/distributions/__pycache__/installed.cpython-312.pyc,,
+pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc,,
+pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc,,
+pip/_internal/distributions/base.py,sha256=QeB9qvKXDIjLdPBDE5fMgpfGqMMCr-govnuoQnGuiF8,1783
+pip/_internal/distributions/installed.py,sha256=QinHFbWAQ8oE0pbD8MFZWkwlnfU1QYTccA1vnhrlYOU,842
+pip/_internal/distributions/sdist.py,sha256=PlcP4a6-R6c98XnOM-b6Lkb3rsvh9iG4ok8shaanrzs,6751
+pip/_internal/distributions/wheel.py,sha256=THBYfnv7VVt8mYhMYUtH13S1E7FDwtDyDfmUcl8ai0E,1317
+pip/_internal/exceptions.py,sha256=2_byISIv3kSnI_9T-Esfxrt0LnTRgcUHyxu0twsHjQY,26481
+pip/_internal/index/__init__.py,sha256=vpt-JeTZefh8a-FC22ZeBSXFVbuBcXSGiILhQZJaNpQ,30
+pip/_internal/index/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/index/__pycache__/collector.cpython-312.pyc,,
+pip/_internal/index/__pycache__/package_finder.cpython-312.pyc,,
+pip/_internal/index/__pycache__/sources.cpython-312.pyc,,
+pip/_internal/index/collector.py,sha256=RdPO0JLAlmyBWPAWYHPyRoGjz3GNAeTngCNkbGey_mE,16265
+pip/_internal/index/package_finder.py,sha256=yRC4xsyudwKnNoU6IXvNoyqYo5ScT7lB6Wa-z2eh7cs,37666
+pip/_internal/index/sources.py,sha256=lPBLK5Xiy8Q6IQMio26Wl7ocfZOKkgGklIBNyUJ23fI,8632
+pip/_internal/locations/__init__.py,sha256=UaAxeZ_f93FyouuFf4p7SXYF-4WstXuEvd3LbmPCAno,14925
+pip/_internal/locations/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc,,
+pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc,,
+pip/_internal/locations/__pycache__/base.cpython-312.pyc,,
+pip/_internal/locations/_distutils.py,sha256=x6nyVLj7X11Y4khIdf-mFlxMl2FWadtVEgeb8upc_WI,6013
+pip/_internal/locations/_sysconfig.py,sha256=IGzds60qsFneRogC-oeBaY7bEh3lPt_v47kMJChQXsU,7724
+pip/_internal/locations/base.py,sha256=RQiPi1d4FVM2Bxk04dQhXZ2PqkeljEL2fZZ9SYqIQ78,2556
+pip/_internal/main.py,sha256=r-UnUe8HLo5XFJz8inTcOOTiu_sxNhgHb6VwlGUllOI,340
+pip/_internal/metadata/__init__.py,sha256=9pU3W3s-6HtjFuYhWcLTYVmSaziklPv7k2x8p7X1GmA,4339
+pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/metadata/__pycache__/_json.cpython-312.pyc,,
+pip/_internal/metadata/__pycache__/base.cpython-312.pyc,,
+pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc,,
+pip/_internal/metadata/_json.py,sha256=P0cAJrH_mtmMZvlZ16ZXm_-izA4lpr5wy08laICuiaA,2644
+pip/_internal/metadata/base.py,sha256=ft0K5XNgI4ETqZnRv2-CtvgYiMOMAeGMAzxT-f6VLJA,25298
+pip/_internal/metadata/importlib/__init__.py,sha256=jUUidoxnHcfITHHaAWG1G2i5fdBYklv_uJcjo2x7VYE,135
+pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc,,
+pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc,,
+pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc,,
+pip/_internal/metadata/importlib/_compat.py,sha256=c6av8sP8BBjAZuFSJow1iWfygUXNM3xRTCn5nqw6B9M,2796
+pip/_internal/metadata/importlib/_dists.py,sha256=anh0mLI-FYRPUhAdipd0Va3YJJc6HelCKQ0bFhY10a0,8017
+pip/_internal/metadata/importlib/_envs.py,sha256=UUB980XSrDWrMpQ1_G45i0r8Hqlg_tg3IPQ63mEqbNc,7431
+pip/_internal/metadata/pkg_resources.py,sha256=U07ETAINSGeSRBfWUG93E4tZZbaW_f7PGzEqZN0hulc,10542
+pip/_internal/models/__init__.py,sha256=3DHUd_qxpPozfzouoqa9g9ts1Czr5qaHfFxbnxriepM,63
+pip/_internal/models/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/models/__pycache__/candidate.cpython-312.pyc,,
+pip/_internal/models/__pycache__/direct_url.cpython-312.pyc,,
+pip/_internal/models/__pycache__/format_control.cpython-312.pyc,,
+pip/_internal/models/__pycache__/index.cpython-312.pyc,,
+pip/_internal/models/__pycache__/installation_report.cpython-312.pyc,,
+pip/_internal/models/__pycache__/link.cpython-312.pyc,,
+pip/_internal/models/__pycache__/scheme.cpython-312.pyc,,
+pip/_internal/models/__pycache__/search_scope.cpython-312.pyc,,
+pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc,,
+pip/_internal/models/__pycache__/target_python.cpython-312.pyc,,
+pip/_internal/models/__pycache__/wheel.cpython-312.pyc,,
+pip/_internal/models/candidate.py,sha256=zzgFRuw_kWPjKpGw7LC0ZUMD2CQ2EberUIYs8izjdCA,753
+pip/_internal/models/direct_url.py,sha256=uBtY2HHd3TO9cKQJWh0ThvE5FRr-MWRYChRU4IG9HZE,6578
+pip/_internal/models/format_control.py,sha256=wtsQqSK9HaUiNxQEuB-C62eVimw6G4_VQFxV9-_KDBE,2486
+pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030
+pip/_internal/models/installation_report.py,sha256=zRVZoaz-2vsrezj_H3hLOhMZCK9c7TbzWgC-jOalD00,2818
+pip/_internal/models/link.py,sha256=jHax9O-9zlSzEwjBCDkx0OXjKXwBDwOuPwn-PsR8dCs,21034
+pip/_internal/models/scheme.py,sha256=PakmHJM3e8OOWSZFtfz1Az7f1meONJnkGuQxFlt3wBE,575
+pip/_internal/models/search_scope.py,sha256=67NEnsYY84784S-MM7ekQuo9KXLH-7MzFntXjapvAo0,4531
+pip/_internal/models/selection_prefs.py,sha256=qaFfDs3ciqoXPg6xx45N1jPLqccLJw4N0s4P0PyHTQ8,2015
+pip/_internal/models/target_python.py,sha256=2XaH2rZ5ZF-K5wcJbEMGEl7SqrTToDDNkrtQ2v_v_-Q,4271
+pip/_internal/models/wheel.py,sha256=G7dND_s4ebPkEL7RJ1qCY0QhUUWIIK6AnjWgRATF5no,4539
+pip/_internal/network/__init__.py,sha256=jf6Tt5nV_7zkARBrKojIXItgejvoegVJVKUbhAa5Ioc,50
+pip/_internal/network/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/network/__pycache__/auth.cpython-312.pyc,,
+pip/_internal/network/__pycache__/cache.cpython-312.pyc,,
+pip/_internal/network/__pycache__/download.cpython-312.pyc,,
+pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc,,
+pip/_internal/network/__pycache__/session.cpython-312.pyc,,
+pip/_internal/network/__pycache__/utils.cpython-312.pyc,,
+pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc,,
+pip/_internal/network/auth.py,sha256=D4gASjUrqoDFlSt6gQ767KAAjv6PUyJU0puDlhXNVRE,20809
+pip/_internal/network/cache.py,sha256=48A971qCzKNFvkb57uGEk7-0xaqPS0HWj2711QNTxkU,3935
+pip/_internal/network/download.py,sha256=FLOP29dPYECBiAi7eEjvAbNkyzaKNqbyjOT2m8HPW8U,6048
+pip/_internal/network/lazy_wheel.py,sha256=PBdoMoNQQIA84Fhgne38jWF52W4x_KtsHjxgv4dkRKA,7622
+pip/_internal/network/session.py,sha256=XmanBKjVwPFmh1iJ58q6TDh9xabH37gREuQJ_feuZGA,18741
+pip/_internal/network/utils.py,sha256=Inaxel-NxBu4PQWkjyErdnfewsFCcgHph7dzR1-FboY,4088
+pip/_internal/network/xmlrpc.py,sha256=sAxzOacJ-N1NXGPvap9jC3zuYWSnnv3GXtgR2-E2APA,1838
+pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_internal/operations/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/operations/__pycache__/check.cpython-312.pyc,,
+pip/_internal/operations/__pycache__/freeze.cpython-312.pyc,,
+pip/_internal/operations/__pycache__/prepare.cpython-312.pyc,,
+pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc,,
+pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc,,
+pip/_internal/operations/build/build_tracker.py,sha256=-ARW_TcjHCOX7D2NUOGntB4Fgc6b4aolsXkAK6BWL7w,4774
+pip/_internal/operations/build/metadata.py,sha256=9S0CUD8U3QqZeXp-Zyt8HxwU90lE4QrnYDgrqZDzBnc,1422
+pip/_internal/operations/build/metadata_editable.py,sha256=VLL7LvntKE8qxdhUdEJhcotFzUsOSI8NNS043xULKew,1474
+pip/_internal/operations/build/metadata_legacy.py,sha256=8i6i1QZX9m_lKPStEFsHKM0MT4a-CD408JOw99daLmo,2190
+pip/_internal/operations/build/wheel.py,sha256=sT12FBLAxDC6wyrDorh8kvcZ1jG5qInCRWzzP-UkJiQ,1075
+pip/_internal/operations/build/wheel_editable.py,sha256=yOtoH6zpAkoKYEUtr8FhzrYnkNHQaQBjWQ2HYae1MQg,1417
+pip/_internal/operations/build/wheel_legacy.py,sha256=K-6kNhmj-1xDF45ny1yheMerF0ui4EoQCLzEoHh6-tc,3045
+pip/_internal/operations/check.py,sha256=L24vRL8VWbyywdoeAhM89WCd8zLTnjIbULlKelUgIec,5912
+pip/_internal/operations/freeze.py,sha256=V59yEyCSz_YhZuhH09-6aV_zvYBMrS_IxFFNqn2QzlA,9864
+pip/_internal/operations/install/__init__.py,sha256=mX7hyD2GNBO2mFGokDQ30r_GXv7Y_PLdtxcUv144e-s,51
+pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc,,
+pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc,,
+pip/_internal/operations/install/editable_legacy.py,sha256=PoEsNEPGbIZ2yQphPsmYTKLOCMs4gv5OcCdzW124NcA,1283
+pip/_internal/operations/install/wheel.py,sha256=X5Iz9yUg5LlK5VNQ9g2ikc6dcRu8EPi_SUi5iuEDRgo,27615
+pip/_internal/operations/prepare.py,sha256=joWJwPkuqGscQgVNImLK71e9hRapwKvRCM8HclysmvU,28118
+pip/_internal/pyproject.py,sha256=rw4fwlptDp1hZgYoplwbAGwWA32sWQkp7ysf8Ju6iXc,7287
+pip/_internal/req/__init__.py,sha256=HxBFtZy_BbCclLgr26waMtpzYdO5T3vxePvpGAXSt5s,2653
+pip/_internal/req/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/req/__pycache__/constructors.cpython-312.pyc,,
+pip/_internal/req/__pycache__/req_file.cpython-312.pyc,,
+pip/_internal/req/__pycache__/req_install.cpython-312.pyc,,
+pip/_internal/req/__pycache__/req_set.cpython-312.pyc,,
+pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc,,
+pip/_internal/req/constructors.py,sha256=v1qzCN1mIldwx-nCrPc8JO4lxkm3Fv8M5RWvt8LISjc,18430
+pip/_internal/req/req_file.py,sha256=gOOJTzL-mDRPcQhjwqjDrjn4V-3rK9TnEFnU3v8RA4Q,18752
+pip/_internal/req/req_install.py,sha256=yhT98NGDoAEk03jznTJnYCznzhiMEEA2ocgsUG_dcNU,35788
+pip/_internal/req/req_set.py,sha256=j3esG0s6SzoVReX9rWn4rpYNtyET_fwxbwJPRimvRxo,2858
+pip/_internal/req/req_uninstall.py,sha256=qzDIxJo-OETWqGais7tSMCDcWbATYABT-Tid3ityF0s,23853
+pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/resolution/__pycache__/base.cpython-312.pyc,,
+pip/_internal/resolution/base.py,sha256=qlmh325SBVfvG6Me9gc5Nsh5sdwHBwzHBq6aEXtKsLA,583
+pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc,,
+pip/_internal/resolution/legacy/resolver.py,sha256=3HZiJBRd1FTN6jQpI4qRO8-TbLYeIbUTS6PFvXnXs2w,24068
+pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc,,
+pip/_internal/resolution/resolvelib/base.py,sha256=DCf669FsqyQY5uqXeePDHQY1e4QO-pBzWH8O0s9-K94,5023
+pip/_internal/resolution/resolvelib/candidates.py,sha256=5UZ1upNnmqsP-nmEZaDYxaBgCoejw_e2WVGmmAvBxXc,20001
+pip/_internal/resolution/resolvelib/factory.py,sha256=511CaUR41LqjALuFafLVfx15WRvMhxYTdjQCoSvp4gw,32661
+pip/_internal/resolution/resolvelib/found_candidates.py,sha256=9hrTyQqFvl9I7Tji79F1AxHv39Qh1rkJ_7deSHSMfQc,6383
+pip/_internal/resolution/resolvelib/provider.py,sha256=bcsFnYvlmtB80cwVdW1fIwgol8ZNr1f1VHyRTkz47SM,9935
+pip/_internal/resolution/resolvelib/reporter.py,sha256=00JtoXEkTlw0-rl_sl54d71avwOsJHt9GGHcrj5Sza0,3168
+pip/_internal/resolution/resolvelib/requirements.py,sha256=7JG4Z72e5Yk4vU0S5ulGvbqTy4FMQGYhY5zQhX9zTtY,8065
+pip/_internal/resolution/resolvelib/resolver.py,sha256=nLJOsVMEVi2gQUVJoUFKMZAeu2f7GRMjGMvNSWyz0Bc,12592
+pip/_internal/self_outdated_check.py,sha256=pkjQixuWyQ1vrVxZAaYD6SSHgXuFUnHZybXEWTkh0S0,8145
+pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_internal/utils/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/_log.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/compat.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/datetime.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/encoding.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/glibc.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/hashes.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/logging.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/misc.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/packaging.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/retry.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/urls.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc,,
+pip/_internal/utils/__pycache__/wheel.cpython-312.pyc,,
+pip/_internal/utils/_jaraco_text.py,sha256=M15uUPIh5NpP1tdUGBxRau6q1ZAEtI8-XyLEETscFfE,3350
+pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015
+pip/_internal/utils/appdirs.py,sha256=swgcTKOm3daLeXTW6v5BUS2Ti2RvEnGRQYH_yDXklAo,1665
+pip/_internal/utils/compat.py,sha256=ckkFveBiYQjRWjkNsajt_oWPS57tJvE8XxoC4OIYgCY,2399
+pip/_internal/utils/compatibility_tags.py,sha256=OWq5axHpW-MEEPztGdvgADrgJPAcV9a88Rxm4Z8VBs8,6272
+pip/_internal/utils/datetime.py,sha256=m21Y3wAtQc-ji6Veb6k_M5g6A0ZyFI4egchTdnwh-pQ,242
+pip/_internal/utils/deprecation.py,sha256=k7Qg_UBAaaTdyq82YVARA6D7RmcGTXGv7fnfcgigj4Q,3707
+pip/_internal/utils/direct_url_helpers.py,sha256=r2MRtkVDACv9AGqYODBUC9CjwgtsUU1s68hmgfCJMtA,3196
+pip/_internal/utils/egg_link.py,sha256=0FePZoUYKv4RGQ2t6x7w5Z427wbA_Uo3WZnAkrgsuqo,2463
+pip/_internal/utils/encoding.py,sha256=qqsXDtiwMIjXMEiIVSaOjwH5YmirCaK-dIzb6-XJsL0,1169
+pip/_internal/utils/entrypoints.py,sha256=YlhLTRl2oHBAuqhc-zmL7USS67TPWVHImjeAQHreZTQ,3064
+pip/_internal/utils/filesystem.py,sha256=ajvA-q4ocliW9kPp8Yquh-4vssXbu-UKbo5FV9V4X64,4950
+pip/_internal/utils/filetypes.py,sha256=i8XAQ0eFCog26Fw9yV0Yb1ygAqKYB1w9Cz9n0fj8gZU,716
+pip/_internal/utils/glibc.py,sha256=vUkWq_1pJuzcYNcGKLlQmABoUiisK8noYY1yc8Wq4w4,3734
+pip/_internal/utils/hashes.py,sha256=XGGLL0AG8-RhWnyz87xF6MFZ--BKadHU35D47eApCKI,4972
+pip/_internal/utils/logging.py,sha256=7BFKB1uFjdxD5crM-GtwA5T2qjbQ2LPD-gJDuJeDNTg,11606
+pip/_internal/utils/misc.py,sha256=NRV0_2fFhzy1jhvInSBv4dqCmTwct8PV7Kp0m-BPRGM,23530
+pip/_internal/utils/packaging.py,sha256=iI3LH43lVNR4hWBOqF6lFsZq4aycb2j0UcHlmDmcqUg,2109
+pip/_internal/utils/retry.py,sha256=mhFbykXjhTnZfgzeuy-vl9c8nECnYn_CMtwNJX2tYzQ,1392
+pip/_internal/utils/setuptools_build.py,sha256=ouXpud-jeS8xPyTPsXJ-m34NPvK5os45otAzdSV_IJE,4435
+pip/_internal/utils/subprocess.py,sha256=EsvqSRiSMHF98T8Txmu6NLU3U--MpTTQjtNgKP0P--M,8988
+pip/_internal/utils/temp_dir.py,sha256=5qOXe8M4JeY6vaFQM867d5zkp1bSwMZ-KT5jymmP0Zg,9310
+pip/_internal/utils/unpacking.py,sha256=eyDkSsk4nW8ZfiSjNzJduCznpHyaGHVv3ak_LMGsiEM,11951
+pip/_internal/utils/urls.py,sha256=qceSOZb5lbNDrHNsv7_S4L4Ytszja5NwPKUMnZHbYnM,1599
+pip/_internal/utils/virtualenv.py,sha256=S6f7csYorRpiD6cvn3jISZYc3I8PJC43H5iMFpRAEDU,3456
+pip/_internal/utils/wheel.py,sha256=b442jkydFHjXzDy6cMR7MpzWBJ1Q82hR5F33cmcHV3g,4494
+pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596
+pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc,,
+pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc,,
+pip/_internal/vcs/__pycache__/git.cpython-312.pyc,,
+pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc,,
+pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc,,
+pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc,,
+pip/_internal/vcs/bazaar.py,sha256=EKStcQaKpNu0NK4p5Q10Oc4xb3DUxFw024XrJy40bFQ,3528
+pip/_internal/vcs/git.py,sha256=3tpc9LQA_J4IVW5r5NvWaaSeDzcmJOrSFZN0J8vIKfU,18177
+pip/_internal/vcs/mercurial.py,sha256=oULOhzJ2Uie-06d1omkL-_Gc6meGaUkyogvqG9ZCyPs,5249
+pip/_internal/vcs/subversion.py,sha256=ddTugHBqHzV3ebKlU5QXHPN4gUqlyXbOx8q8NgXKvs8,11735
+pip/_internal/vcs/versioncontrol.py,sha256=cvf_-hnTAjQLXJ3d17FMNhQfcO1AcKWUF10tfrYyP-c,22440
+pip/_internal/wheel_builder.py,sha256=DL3A8LKeRj_ACp11WS5wSgASgPFqeyAeXJKdXfmaWXU,11799
+pip/_vendor/__init__.py,sha256=JYuAXvClhInxIrA2FTp5p-uuWVL7WV6-vEpTs46-Qh4,4873
+pip/_vendor/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__init__.py,sha256=GiYoagwPEiJ_xR_lbwWGaoCiPtF_rz4isjfjdDAgHU4,676
+pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc,,
+pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc,,
+pip/_vendor/cachecontrol/_cmd.py,sha256=iist2EpzJvDVIhMAxXq8iFnTBsiZAd6iplxfmNboNyk,1737
+pip/_vendor/cachecontrol/adapter.py,sha256=fByO_Pd_EOemjWbuocvBWdN85xT0q_TBm2lxS6vD4fk,6355
+pip/_vendor/cachecontrol/cache.py,sha256=OTQj72tUf8C1uEgczdl3Gc8vkldSzsTITKtDGKMx4z8,1952
+pip/_vendor/cachecontrol/caches/__init__.py,sha256=dtrrroK5BnADR1GWjCZ19aZ0tFsMfvFBtLQQU1sp_ag,303
+pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc,,
+pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc,,
+pip/_vendor/cachecontrol/caches/file_cache.py,sha256=9AlmmTJc6cslb6k5z_6q0sGPHVrMj8zv-uWy-simmfE,5406
+pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=9rmqwtYu_ljVkW6_oLqbC7EaX_a8YT_yLuna-eS0dgo,1386
+pip/_vendor/cachecontrol/controller.py,sha256=o-ejGJlBmpKK8QQLyTPJj0t7siU8XVHXuV8MCybCxQ8,18575
+pip/_vendor/cachecontrol/filewrapper.py,sha256=STttGmIPBvZzt2b51dUOwoWX5crcMCpKZOisM3f5BNc,4292
+pip/_vendor/cachecontrol/heuristics.py,sha256=IYe4QmHERWsMvtxNrp920WeaIsaTTyqLB14DSheSbtY,4834
+pip/_vendor/cachecontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/cachecontrol/serialize.py,sha256=HQd2IllQ05HzPkVLMXTF2uX5mjEQjDBkxCqUJUODpZk,5163
+pip/_vendor/cachecontrol/wrapper.py,sha256=hsGc7g8QGQTT-4f8tgz3AM5qwScg6FO0BSdLSRdEvpU,1417
+pip/_vendor/certifi/__init__.py,sha256=p_GYZrjUwPBUhpLlCZoGb0miKBKSqDAyZC5DvIuqbHQ,94
+pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255
+pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc,,
+pip/_vendor/certifi/__pycache__/core.cpython-312.pyc,,
+pip/_vendor/certifi/cacert.pem,sha256=lO3rZukXdPyuk6BWUJFOKQliWaXH6HGh9l1GGrUgG0c,299427
+pip/_vendor/certifi/core.py,sha256=2SRT5rIcQChFDbe37BQa-kULxAgJ8qN6l1jfqTp4HIs,4486
+pip/_vendor/certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/distlib/__init__.py,sha256=dcwgYGYGQqAEawBXPDtIx80DO_3cOmFv8HTc8JMzknQ,625
+pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/database.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/index.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/util.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/version.cpython-312.pyc,,
+pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc,,
+pip/_vendor/distlib/compat.py,sha256=2jRSjRI4o-vlXeTK2BCGIUhkc6e9ZGhSsacRM5oseTw,41467
+pip/_vendor/distlib/database.py,sha256=mHy_LxiXIsIVRb-T0-idBrVLw3Ffij5teHCpbjmJ9YU,51160
+pip/_vendor/distlib/index.py,sha256=lTbw268rRhj8dw1sib3VZ_0EhSGgoJO3FKJzSFMOaeA,20797
+pip/_vendor/distlib/locators.py,sha256=oBeAZpFuPQSY09MgNnLfQGGAXXvVO96BFpZyKMuK4tM,51026
+pip/_vendor/distlib/manifest.py,sha256=3qfmAmVwxRqU1o23AlfXrQGZzh6g_GGzTAP_Hb9C5zQ,14168
+pip/_vendor/distlib/markers.py,sha256=X6sDvkFGcYS8gUW8hfsWuKEKAqhQZAJ7iXOMLxRYjYk,5164
+pip/_vendor/distlib/metadata.py,sha256=zil3sg2EUfLXVigljY2d_03IJt-JSs7nX-73fECMX2s,38724
+pip/_vendor/distlib/resources.py,sha256=LwbPksc0A1JMbi6XnuPdMBUn83X7BPuFNWqPGEKI698,10820
+pip/_vendor/distlib/scripts.py,sha256=BJliaDAZaVB7WAkwokgC3HXwLD2iWiHaVI50H7C6eG8,18608
+pip/_vendor/distlib/t32.exe,sha256=a0GV5kCoWsMutvliiCKmIgV98eRZ33wXoS-XrqvJQVs,97792
+pip/_vendor/distlib/t64-arm.exe,sha256=68TAa32V504xVBnufojh0PcenpR3U4wAqTqf-MZqbPw,182784
+pip/_vendor/distlib/t64.exe,sha256=gaYY8hy4fbkHYTTnA4i26ct8IQZzkBG2pRdy0iyuBrc,108032
+pip/_vendor/distlib/util.py,sha256=vMPGvsS4j9hF6Y9k3Tyom1aaHLb0rFmZAEyzeAdel9w,66682
+pip/_vendor/distlib/version.py,sha256=s5VIs8wBn0fxzGxWM_aA2ZZyx525HcZbMvcTlTyZ3Rg,23727
+pip/_vendor/distlib/w32.exe,sha256=R4csx3-OGM9kL4aPIzQKRo5TfmRSHZo6QWyLhDhNBks,91648
+pip/_vendor/distlib/w64-arm.exe,sha256=xdyYhKj0WDcVUOCb05blQYvzdYIKMbmJn2SZvzkcey4,168448
+pip/_vendor/distlib/w64.exe,sha256=ejGf-rojoBfXseGLpya6bFTFPWRG21X5KvU8J5iU-K0,101888
+pip/_vendor/distlib/wheel.py,sha256=DFIVguEQHCdxnSdAO0dfFsgMcvVZitg7bCOuLwZ7A_s,43979
+pip/_vendor/distro/__init__.py,sha256=2fHjF-SfgPvjyNZ1iHh_wjqWdR_Yo5ODHwZC0jLBPhc,981
+pip/_vendor/distro/__main__.py,sha256=bu9d3TifoKciZFcqRBuygV3GSuThnVD_m2IK4cz96Vs,64
+pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc,,
+pip/_vendor/distro/__pycache__/distro.cpython-312.pyc,,
+pip/_vendor/distro/distro.py,sha256=XqbefacAhDT4zr_trnbA15eY8vdK4GTghgmvUGrEM_4,49430
+pip/_vendor/distro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/idna/__init__.py,sha256=KJQN1eQBr8iIK5SKrJ47lXvxG0BJ7Lm38W4zT0v_8lk,849
+pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/codec.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/compat.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/core.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc,,
+pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc,,
+pip/_vendor/idna/codec.py,sha256=PS6m-XmdST7Wj7J7ulRMakPDt5EBJyYrT3CPtjh-7t4,3426
+pip/_vendor/idna/compat.py,sha256=0_sOEUMT4CVw9doD3vyRhX80X19PwqFoUBs7gWsFME4,321
+pip/_vendor/idna/core.py,sha256=lyhpoe2vulEaB_65xhXmoKgO-xUqFDvcwxu5hpNNO4E,12663
+pip/_vendor/idna/idnadata.py,sha256=dqRwytzkjIHMBa2R1lYvHDwACenZPt8eGVu1Y8UBE-E,78320
+pip/_vendor/idna/intranges.py,sha256=YBr4fRYuWH7kTKS2tXlFjM24ZF1Pdvcir-aywniInqg,1881
+pip/_vendor/idna/package_data.py,sha256=Tkt0KnIeyIlnHddOaz9WSkkislNgokJAuE-p5GorMqo,21
+pip/_vendor/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/idna/uts46data.py,sha256=1KuksWqLuccPXm2uyRVkhfiFLNIhM_H2m4azCcnOqEU,206503
+pip/_vendor/msgpack/__init__.py,sha256=gsMP7JTECZNUSjvOyIbdhNOkpB9Z8BcGwabVGY2UcdQ,1077
+pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc,,
+pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc,,
+pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc,,
+pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081
+pip/_vendor/msgpack/ext.py,sha256=fKp00BqDLjUtZnPd70Llr138zk8JsCuSpJkkZ5S4dt8,5629
+pip/_vendor/msgpack/fallback.py,sha256=wdUWJkWX2gzfRW9BBCTOuIE1Wvrf5PtBtR8ZtY7G_EE,33175
+pip/_vendor/packaging/__init__.py,sha256=dtw2bNmWCQ9WnMoK3bk_elL1svSlikXtLpZhCFIB9SE,496
+pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc,,
+pip/_vendor/packaging/__pycache__/version.cpython-312.pyc,,
+pip/_vendor/packaging/_elffile.py,sha256=_LcJW4YNKywYsl4169B2ukKRqwxjxst_8H0FRVQKlz8,3282
+pip/_vendor/packaging/_manylinux.py,sha256=Xo4V0PZz8sbuVCbTni0t1CR0AHeir_7ib4lTmV8scD4,9586
+pip/_vendor/packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
+pip/_vendor/packaging/_parser.py,sha256=s_TvTvDNK0NrM2QB3VKThdWFM4Nc0P6JnkObkl3MjpM,10236
+pip/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
+pip/_vendor/packaging/_tokenizer.py,sha256=J6v5H7Jzvb-g81xp_2QACKwO7LxHQA6ikryMU7zXwN8,5273
+pip/_vendor/packaging/markers.py,sha256=dWKSqn5Sp-jDmOG-W3GfLHKjwhf1IsznbT71VlBoB5M,10671
+pip/_vendor/packaging/metadata.py,sha256=KINuSkJ12u-SyoKNTy_pHNGAfMUtxNvZ53qA1zAKcKI,32349
+pip/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
+pip/_vendor/packaging/specifiers.py,sha256=HfGgfNJRvrzC759gnnoojHyiWs_DYmcw5PEh5jHH-YE,39738
+pip/_vendor/packaging/tags.py,sha256=Fo6_cit95-7QfcMb16XtI7AUiSMgdwA_hCO_9lV2pz4,21388
+pip/_vendor/packaging/utils.py,sha256=NAdYUwnlAOpkat_RthavX8a07YuVxgGL_vwrx73GSDM,5287
+pip/_vendor/packaging/version.py,sha256=wE4sSVlF-d1H6HFC1vszEe35CwTig_fh4HHIFg95hFE,16210
+pip/_vendor/pkg_resources/__init__.py,sha256=jrhDRbOubP74QuPXxd7U7Po42PH2l-LZ2XfcO7llpZ4,124463
+pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/platformdirs/__init__.py,sha256=FTA6LGNm40GwNZt3gG3uLAacWvf2E_2HTmH0rAALGR8,22285
+pip/_vendor/platformdirs/__main__.py,sha256=jBJ8zb7Mpx5ebcqF83xrpO94MaeCpNGHVf9cvDN2JLg,1505
+pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc,,
+pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc,,
+pip/_vendor/platformdirs/android.py,sha256=xZXY9Jd46WOsxT2U6-5HsNtDZ-IQqxcEUrBLl3hYk4o,9016
+pip/_vendor/platformdirs/api.py,sha256=QBYdUac2eC521ek_y53uD1Dcq-lJX8IgSRVd4InC6uc,8996
+pip/_vendor/platformdirs/macos.py,sha256=wftsbsvq6nZ0WORXSiCrZNkRHz_WKuktl0a6mC7MFkI,5580
+pip/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/platformdirs/unix.py,sha256=Cci9Wqt35dAMsg6HT9nRGHSBW5obb0pR3AE1JJnsCXg,10643
+pip/_vendor/platformdirs/version.py,sha256=r7F76tZRjgQKzrpx_I0_ZMQOMU-PS7eGnHD7zEK3KB0,411
+pip/_vendor/platformdirs/windows.py,sha256=IFpiohUBwxPtCzlyKwNtxyW4Jk8haa6W8o59mfrDXVo,10125
+pip/_vendor/pygments/__init__.py,sha256=7N1oiaWulw_nCsTY4EEixYLz15pWY5u4uPAFFi-ielU,2983
+pip/_vendor/pygments/__main__.py,sha256=isIhBxLg65nLlXukG4VkMuPfNdd7gFzTZ_R_z3Q8diY,353
+pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/console.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/style.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/token.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc,,
+pip/_vendor/pygments/__pycache__/util.cpython-312.pyc,,
+pip/_vendor/pygments/cmdline.py,sha256=LIVzmAunlk9sRJJp54O4KRy9GDIN4Wu13v9p9QzfGPM,23656
+pip/_vendor/pygments/console.py,sha256=yhP9UsLAVmWKVQf2446JJewkA7AiXeeTf4Ieg3Oi2fU,1718
+pip/_vendor/pygments/filter.py,sha256=_ADNPCskD8_GmodHi6_LoVgPU3Zh336aBCT5cOeTMs0,1910
+pip/_vendor/pygments/filters/__init__.py,sha256=RdedK2KWKXlKwR7cvkfr3NUj9YiZQgMgilRMFUg2jPA,40392
+pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pygments/formatter.py,sha256=jDWBTndlBH2Z5IYZFVDnP0qn1CaTQjTWt7iAGtCnJEg,4390
+pip/_vendor/pygments/formatters/__init__.py,sha256=8No-NUs8rBTSSBJIv4hSEQt2M0cFB4hwAT0snVc2QGE,5385
+pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc,,
+pip/_vendor/pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176
+pip/_vendor/pygments/formatters/bbcode.py,sha256=3JQLI45tcrQ_kRUMjuab6C7Hb0XUsbVWqqbSn9cMjkI,3320
+pip/_vendor/pygments/formatters/groff.py,sha256=M39k0PaSSZRnxWjqBSVPkF0mu1-Vr7bm6RsFvs-CNN4,5106
+pip/_vendor/pygments/formatters/html.py,sha256=SE2jc3YCqbMS3rZW9EAmDlAUhdVxJ52gA4dileEvCGU,35669
+pip/_vendor/pygments/formatters/img.py,sha256=MwA4xWPLOwh6j7Yc6oHzjuqSPt0M1fh5r-5BTIIUfsU,23287
+pip/_vendor/pygments/formatters/irc.py,sha256=dp1Z0l_ObJ5NFh9MhqLGg5ptG5hgJqedT2Vkutt9v0M,4981
+pip/_vendor/pygments/formatters/latex.py,sha256=XMmhOCqUKDBQtG5mGJNAFYxApqaC5puo5cMmPfK3944,19306
+pip/_vendor/pygments/formatters/other.py,sha256=56PMJOliin-rAUdnRM0i1wsV1GdUPd_dvQq0_UPfF9c,5034
+pip/_vendor/pygments/formatters/pangomarkup.py,sha256=y16U00aVYYEFpeCfGXlYBSMacG425CbfoG8oKbKegIg,2218
+pip/_vendor/pygments/formatters/rtf.py,sha256=ZT90dmcKyJboIB0mArhL7IhE467GXRN0G7QAUgG03To,11957
+pip/_vendor/pygments/formatters/svg.py,sha256=KKsiophPupHuxm0So-MsbQEWOT54IAiSF7hZPmxtKXE,7174
+pip/_vendor/pygments/formatters/terminal.py,sha256=AojNG4MlKq2L6IsC_VnXHu4AbHCBn9Otog6u45XvxeI,4674
+pip/_vendor/pygments/formatters/terminal256.py,sha256=kGkNUVo3FpwjytIDS0if79EuUoroAprcWt3igrcIqT0,11753
+pip/_vendor/pygments/lexer.py,sha256=TYHDt___gNW4axTl2zvPZff-VQi8fPaIh5OKRcVSjUM,35349
+pip/_vendor/pygments/lexers/__init__.py,sha256=pIlxyQJuu_syh9lE080cq8ceVbEVcKp0osAFU5fawJU,12115
+pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc,,
+pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc,,
+pip/_vendor/pygments/lexers/_mapping.py,sha256=61-h3zr103m01OS5BUq_AfUiL9YI06Ves9ipQ7k4vr4,76097
+pip/_vendor/pygments/lexers/python.py,sha256=2J_YJrPTr_A6fJY_qKiKv0GpgPwHMrlMSeo59qN3fe4,53687
+pip/_vendor/pygments/modeline.py,sha256=gtRYZBS-CKOCDXHhGZqApboHBaZwGH8gznN3O6nuxj4,1005
+pip/_vendor/pygments/plugin.py,sha256=ioeJ3QeoJ-UQhZpY9JL7vbxsTVuwwM7BCu-Jb8nN0AU,1891
+pip/_vendor/pygments/regexopt.py,sha256=Hky4EB13rIXEHQUNkwmCrYqtIlnXDehNR3MztafZ43w,3072
+pip/_vendor/pygments/scanner.py,sha256=NDy3ofK_fHRFK4hIDvxpamG871aewqcsIb6sgTi7Fhk,3092
+pip/_vendor/pygments/sphinxext.py,sha256=iOptJBcqOGPwMEJ2p70PvwpZPIGdvdZ8dxvq6kzxDgA,7981
+pip/_vendor/pygments/style.py,sha256=rSCZWFpg1_DwFMXDU0nEVmAcBHpuQGf9RxvOPPQvKLQ,6420
+pip/_vendor/pygments/styles/__init__.py,sha256=qUk6_1z5KmT8EdJFZYgESmG6P_HJF_2vVrDD7HSCGYY,2042
+pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc,,
+pip/_vendor/pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312
+pip/_vendor/pygments/token.py,sha256=qZwT7LSPy5YBY3JgDjut642CCy7JdQzAfmqD9NmT5j0,6226
+pip/_vendor/pygments/unistring.py,sha256=p5c1i-HhoIhWemy9CUsaN9o39oomYHNxXll0Xfw6tEA,63208
+pip/_vendor/pygments/util.py,sha256=2tj2nS1X9_OpcuSjf8dOET2bDVZhs8cEKd_uT6-Fgg8,10031
+pip/_vendor/pyproject_hooks/__init__.py,sha256=kCehmy0UaBa9oVMD7ZIZrnswfnP3LXZ5lvnNJAL5JBM,491
+pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc,,
+pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc,,
+pip/_vendor/pyproject_hooks/_compat.py,sha256=by6evrYnqkisiM-MQcvOKs5bgDMzlOSgZqRHNqf04zE,138
+pip/_vendor/pyproject_hooks/_impl.py,sha256=61GJxzQip0IInhuO69ZI5GbNQ82XEDUB_1Gg5_KtUoc,11920
+pip/_vendor/pyproject_hooks/_in_process/__init__.py,sha256=9gQATptbFkelkIy0OfWFEACzqxXJMQDWCH9rBOAZVwQ,546
+pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc,,
+pip/_vendor/pyproject_hooks/_in_process/_in_process.py,sha256=m2b34c917IW5o-Q_6TYIHlsK9lSUlNiyrITTUH_zwew,10927
+pip/_vendor/requests/__init__.py,sha256=HlB_HzhrzGtfD_aaYUwUh1zWXLZ75_YCLyit75d0Vz8,5057
+pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/api.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/auth.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/certs.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/compat.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/help.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/models.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/packages.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/structures.cpython-312.pyc,,
+pip/_vendor/requests/__pycache__/utils.cpython-312.pyc,,
+pip/_vendor/requests/__version__.py,sha256=FVfglgZmNQnmYPXpOohDU58F5EUb_-VnSTaAesS187g,435
+pip/_vendor/requests/_internal_utils.py,sha256=nMQymr4hs32TqVo5AbCrmcJEhvPUh7xXlluyqwslLiQ,1495
+pip/_vendor/requests/adapters.py,sha256=J7VeVxKBvawbtlX2DERVo05J9BXTcWYLMHNd1Baa-bk,27607
+pip/_vendor/requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449
+pip/_vendor/requests/auth.py,sha256=kF75tqnLctZ9Mf_hm9TZIj4cQWnN5uxRz8oWsx5wmR0,10186
+pip/_vendor/requests/certs.py,sha256=PVPooB0jP5hkZEULSCwC074532UFbR2Ptgu0I5zwmCs,575
+pip/_vendor/requests/compat.py,sha256=Mo9f9xZpefod8Zm-n9_StJcVTmwSukXR2p3IQyyVXvU,1485
+pip/_vendor/requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590
+pip/_vendor/requests/exceptions.py,sha256=D1wqzYWne1mS2rU43tP9CeN1G7QAy7eqL9o1god6Ejw,4272
+pip/_vendor/requests/help.py,sha256=hRKaf9u0G7fdwrqMHtF3oG16RKktRf6KiwtSq2Fo1_0,3813
+pip/_vendor/requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733
+pip/_vendor/requests/models.py,sha256=x4K4CmH-lC0l2Kb-iPfMN4dRXxHEcbOaEWBL_i09AwI,35483
+pip/_vendor/requests/packages.py,sha256=_ZQDCJTJ8SP3kVWunSqBsRZNPzj2c1WFVqbdr08pz3U,1057
+pip/_vendor/requests/sessions.py,sha256=ykTI8UWGSltOfH07HKollH7kTBGw4WhiBVaQGmckTw4,30495
+pip/_vendor/requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322
+pip/_vendor/requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912
+pip/_vendor/requests/utils.py,sha256=L79vnFbzJ3SFLKtJwpoWe41Tozi3RlZv94pY1TFIyow,33631
+pip/_vendor/resolvelib/__init__.py,sha256=h509TdEcpb5-44JonaU3ex2TM15GVBLjM9CNCPwnTTs,537
+pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc,,
+pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc,,
+pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc,,
+pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc,,
+pip/_vendor/resolvelib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc,,
+pip/_vendor/resolvelib/compat/collections_abc.py,sha256=uy8xUZ-NDEw916tugUXm8HgwCGiMO0f-RcdnpkfXfOs,156
+pip/_vendor/resolvelib/providers.py,sha256=fuuvVrCetu5gsxPB43ERyjfO8aReS3rFQHpDgiItbs4,5871
+pip/_vendor/resolvelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/resolvelib/reporters.py,sha256=TSbRmWzTc26w0ggsV1bxVpeWDB8QNIre6twYl7GIZBE,1601
+pip/_vendor/resolvelib/resolvers.py,sha256=G8rsLZSq64g5VmIq-lB7UcIJ1gjAxIQJmTF4REZleQ0,20511
+pip/_vendor/resolvelib/structs.py,sha256=0_1_XO8z_CLhegP3Vpf9VJ3zJcfLm0NOHRM-i0Ykz3o,4963
+pip/_vendor/rich/__init__.py,sha256=dRxjIL-SbFVY0q3IjSMrfgBTHrm1LZDgLOygVBwiYZc,6090
+pip/_vendor/rich/__main__.py,sha256=eO7Cq8JnrgG8zVoeImiAs92q3hXNMIfp0w5lMsO7Q2Y,8477
+pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/abc.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/align.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/bar.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/box.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/cells.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/color.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/columns.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/console.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/containers.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/control.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/errors.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/json.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/layout.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/live.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/logging.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/markup.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/measure.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/padding.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/pager.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/palette.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/panel.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/progress.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/region.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/repr.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/rule.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/scope.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/screen.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/segment.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/status.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/style.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/styled.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/table.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/text.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/theme.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/themes.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc,,
+pip/_vendor/rich/__pycache__/tree.cpython-312.pyc,,
+pip/_vendor/rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209
+pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235
+pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064
+pip/_vendor/rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128
+pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265
+pip/_vendor/rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799
+pip/_vendor/rich/_inspect.py,sha256=oZJGw31e64dwXSCmrDnvZbwVb1ZKhWfU8wI3VWohjJk,9695
+pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225
+pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236
+pip/_vendor/rich/_null_file.py,sha256=tGSXk_v-IZmbj1GAzHit8A3kYIQMiCpVsCFfsC-_KJ4,1387
+pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063
+pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423
+pip/_vendor/rich/_ratio.py,sha256=Zt58apszI6hAAcXPpgdWKpu3c31UBWebOeR4mbyptvU,5471
+pip/_vendor/rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919
+pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351
+pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417
+pip/_vendor/rich/_win32_console.py,sha256=P0vxI2fcndym1UU1S37XAzQzQnkyY7YqAKmxm24_gug,22820
+pip/_vendor/rich/_windows.py,sha256=aBwaD_S56SbgopIvayVmpk0Y28uwY2C5Bab1wl3Bp-I,1925
+pip/_vendor/rich/_windows_renderer.py,sha256=t74ZL3xuDCP3nmTp9pH1L5LiI2cakJuQRQleHCJerlk,2783
+pip/_vendor/rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404
+pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890
+pip/_vendor/rich/align.py,sha256=sCUkisXkQfoq-IQPyBELfJ8l7LihZJX3HbH8K7Cie-M,10368
+pip/_vendor/rich/ansi.py,sha256=iD6532QYqnBm6hADulKjrV8l8kFJ-9fEVooHJHH3hMg,6906
+pip/_vendor/rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263
+pip/_vendor/rich/box.py,sha256=nr5fYIUghB_iUCEq6y0Z3LlCT8gFPDrzN9u2kn7tJl4,10831
+pip/_vendor/rich/cells.py,sha256=aMmGK4BjXhgE6_JF1ZEGmW3O7mKkE8g84vUnj4Et4To,4780
+pip/_vendor/rich/color.py,sha256=bCRATVdRe5IClJ6Hl62de2PKQ_U4i2MZ4ugjUEg7Tao,18223
+pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054
+pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131
+pip/_vendor/rich/console.py,sha256=deFZIubq2M9A2MCsKFAsFQlWDvcOMsGuUA07QkOaHIw,99173
+pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288
+pip/_vendor/rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502
+pip/_vendor/rich/control.py,sha256=DSkHTUQLorfSERAKE_oTAEUFefZnZp4bQb4q8rHbKws,6630
+pip/_vendor/rich/default_styles.py,sha256=-Fe318kMVI_IwciK5POpThcO0-9DYJ67TZAN6DlmlmM,8082
+pip/_vendor/rich/diagnose.py,sha256=an6uouwhKPAlvQhYpNNpGq9EJysfMIOvvCbO3oSoR24,972
+pip/_vendor/rich/emoji.py,sha256=omTF9asaAnsM4yLY94eR_9dgRRSm1lHUszX20D1yYCQ,2501
+pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642
+pip/_vendor/rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683
+pip/_vendor/rich/filesize.py,sha256=9fTLAPCAwHmBXdRv7KZU194jSgNrRb6Wx7RIoBgqeKY,2508
+pip/_vendor/rich/highlighter.py,sha256=6ZAjUcNhBRajBCo9umFUclyi2xL0-55JL7S0vYGUJu4,9585
+pip/_vendor/rich/json.py,sha256=vVEoKdawoJRjAFayPwXkMBPLy7RSTs-f44wSQDR2nJ0,5031
+pip/_vendor/rich/jupyter.py,sha256=QyoKoE_8IdCbrtiSHp9TsTSNyTHY0FO5whE7jOTd9UE,3252
+pip/_vendor/rich/layout.py,sha256=ajkSFAtEVv9EFTcFs-w4uZfft7nEXhNzL7ZVdgrT5rI,14004
+pip/_vendor/rich/live.py,sha256=vUcnJV2LMSK3sQNaILbm0-_B8BpAeiHfcQMAMLfpRe0,14271
+pip/_vendor/rich/live_render.py,sha256=zJtB471jGziBtEwxc54x12wEQtH4BuQr1SA8v9kU82w,3666
+pip/_vendor/rich/logging.py,sha256=uB-cB-3Q4bmXDLLpbOWkmFviw-Fde39zyMV6tKJ2WHQ,11903
+pip/_vendor/rich/markup.py,sha256=3euGKP5s41NCQwaSjTnJxus5iZMHjxpIM0W6fCxra38,8451
+pip/_vendor/rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305
+pip/_vendor/rich/padding.py,sha256=kTFGsdGe0os7tXLnHKpwTI90CXEvrceeZGCshmJy5zw,4970
+pip/_vendor/rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828
+pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396
+pip/_vendor/rich/panel.py,sha256=2Fd1V7e1kHxlPFIusoHY5T7-Cs0RpkrihgVG9ZVqJ4g,10705
+pip/_vendor/rich/pretty.py,sha256=5oIHP_CGWnHEnD0zMdW5qfGC5kHqIKn7zH_eC4crULE,35848
+pip/_vendor/rich/progress.py,sha256=P02xi7T2Ua3qq17o83bkshe4c0v_45cg8VyTj6US6Vg,59715
+pip/_vendor/rich/progress_bar.py,sha256=L4jw8E6Qb_x-jhOrLVhkuMaPmiAhFIl8jHQbWFrKuR8,8164
+pip/_vendor/rich/prompt.py,sha256=wdOn2X8XTJKnLnlw6PoMY7xG4iUPp3ezt4O5gqvpV-E,11304
+pip/_vendor/rich/protocol.py,sha256=5hHHDDNHckdk8iWH5zEbi-zuIVSF5hbU2jIo47R7lTE,1391
+pip/_vendor/rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166
+pip/_vendor/rich/repr.py,sha256=5MZJZmONgC6kud-QW-_m1okXwL2aR6u6y-pUcUCJz28,4431
+pip/_vendor/rich/rule.py,sha256=0fNaS_aERa3UMRc3T5WMpN_sumtDxfaor2y3of1ftBk,4602
+pip/_vendor/rich/scope.py,sha256=TMUU8qo17thyqQCPqjDLYpg_UU1k5qVd-WwiJvnJVas,2843
+pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591
+pip/_vendor/rich/segment.py,sha256=hU1ueeXqI6YeFa08K9DAjlF2QLxcJY9pwZx7RsXavlk,24246
+pip/_vendor/rich/spinner.py,sha256=15koCmF0DQeD8-k28Lpt6X_zJQUlzEhgo_6A6uy47lc,4339
+pip/_vendor/rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424
+pip/_vendor/rich/style.py,sha256=3hiocH_4N8vwRm3-8yFWzM7tSwjjEven69XqWasSQwM,27073
+pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258
+pip/_vendor/rich/syntax.py,sha256=TnZDuOD4DeHFbkaVEAji1gf8qgAlMU9Boe_GksMGCkk,35475
+pip/_vendor/rich/table.py,sha256=nGEvAZHF4dy1vT9h9Gj9O5qhSQO3ODAxJv0RY1vnIB8,39680
+pip/_vendor/rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370
+pip/_vendor/rich/text.py,sha256=5rQ3zvNrg5UZKNLecbh7fiw9v3HeFulNVtRY_CBDjjE,47312
+pip/_vendor/rich/theme.py,sha256=belFJogzA0W0HysQabKaHOc3RWH2ko3fQAJhoN-AFdo,3777
+pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102
+pip/_vendor/rich/traceback.py,sha256=CUpxYLjQWIb6vQQ6O72X0hvDV6caryGqU6UweHgOyCY,29601
+pip/_vendor/rich/tree.py,sha256=meAOUU6sYnoBEOX2ILrPLY9k5bWrWNQKkaiEFvHinXM,9167
+pip/_vendor/tomli/__init__.py,sha256=JhUwV66DB1g4Hvt1UQCVMdfCu-IgAV8FXmvDU9onxd4,396
+pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc,,
+pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc,,
+pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc,,
+pip/_vendor/tomli/_parser.py,sha256=g9-ENaALS-B8dokYpCuzUFalWlog7T-SIYMjLZSWrtM,22633
+pip/_vendor/tomli/_re.py,sha256=dbjg5ChZT23Ka9z9DHOXfdtSpPwUfdgMXnj8NOoly-w,2943
+pip/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254
+pip/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
+pip/_vendor/truststore/__init__.py,sha256=WIDeyzWm7EVX44g354M25vpRXbeY1lsPH6EmUJUcq4o,1264
+pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc,,
+pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc,,
+pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc,,
+pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc,,
+pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc,,
+pip/_vendor/truststore/_api.py,sha256=GeXRNTlxPZ3kif4kNoh6JY0oE4QRzTGcgXr6l_X_Gk0,10555
+pip/_vendor/truststore/_macos.py,sha256=nZlLkOmszUE0g6ryRwBVGY5COzPyudcsiJtDWarM5LQ,20503
+pip/_vendor/truststore/_openssl.py,sha256=LLUZ7ZGaio-i5dpKKjKCSeSufmn6T8pi9lDcFnvSyq0,2324
+pip/_vendor/truststore/_ssl_constants.py,sha256=NUD4fVKdSD02ri7-db0tnO0VqLP9aHuzmStcW7tAl08,1130
+pip/_vendor/truststore/_windows.py,sha256=rAHyKYD8M7t-bXfG8VgOVa3TpfhVhbt4rZQlO45YuP8,17993
+pip/_vendor/truststore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/typing_extensions.py,sha256=78hFl0HpDY-ylHUVCnWdU5nTHxUP2-S-3wEZk6CQmLk,134499
+pip/_vendor/urllib3/__init__.py,sha256=iXLcYiJySn0GNbWOOZDDApgBL1JgP44EZ8i1760S8Mc,3333
+pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc,,
+pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc,,
+pip/_vendor/urllib3/_collections.py,sha256=pyASJJhW7wdOpqJj9QJA8FyGRfr8E8uUUhqUvhF0728,11372
+pip/_vendor/urllib3/_version.py,sha256=t9wGB6ooOTXXgiY66K1m6BZS1CJyXHAU8EoWDTe6Shk,64
+pip/_vendor/urllib3/connection.py,sha256=ttIA909BrbTUzwkqEe_TzZVh4JOOj7g61Ysei2mrwGg,20314
+pip/_vendor/urllib3/connectionpool.py,sha256=e2eiAwNbFNCKxj4bwDKNK-w7HIdSz3OmMxU_TIt-evQ,40408
+pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957
+pip/_vendor/urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc,,
+pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632
+pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922
+pip/_vendor/urllib3/contrib/appengine.py,sha256=VR68eAVE137lxTgjBDwCna5UiBZTOKa01Aj_-5BaCz4,11036
+pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=NlfkW7WMdW8ziqudopjHoW299og1BTWi0IeIibquFwk,4528
+pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=hDJh4MhyY_p-oKlFcYcQaVQRDv6GMmBGuW9yjxyeejM,17081
+pip/_vendor/urllib3/contrib/securetransport.py,sha256=Fef1IIUUFHqpevzXiDPbIGkDKchY2FVKeVeLGR1Qq3g,34446
+pip/_vendor/urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097
+pip/_vendor/urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217
+pip/_vendor/urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579
+pip/_vendor/urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440
+pip/_vendor/urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc,,
+pip/_vendor/urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc,,
+pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc,,
+pip/_vendor/urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417
+pip/_vendor/urllib3/packages/backports/weakref_finalize.py,sha256=tRCal5OAhNSRyb0DhHp-38AtIlCsRP8BxF3NX-6rqIA,5343
+pip/_vendor/urllib3/packages/six.py,sha256=b9LM0wBXv7E7SrbCjAm4wwN-hrH-iNxv18LgWNMMKPo,34665
+pip/_vendor/urllib3/poolmanager.py,sha256=aWyhXRtNO4JUnCSVVqKTKQd8EXTvUm1VN9pgs2bcONo,19990
+pip/_vendor/urllib3/request.py,sha256=YTWFNr7QIwh7E1W9dde9LM77v2VWTJ5V78XuTTw7D1A,6691
+pip/_vendor/urllib3/response.py,sha256=fmDJAFkG71uFTn-sVSTh2Iw0WmcXQYqkbRjihvwBjU8,30641
+pip/_vendor/urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155
+pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc,,
+pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc,,
+pip/_vendor/urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901
+pip/_vendor/urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605
+pip/_vendor/urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498
+pip/_vendor/urllib3/util/request.py,sha256=C0OUt2tcU6LRiQJ7YYNP9GvPrSvl7ziIBekQ-5nlBZk,3997
+pip/_vendor/urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510
+pip/_vendor/urllib3/util/retry.py,sha256=6ENvOZ8PBDzh8kgixpql9lIrb2dxH-k7ZmBanJF2Ng4,22050
+pip/_vendor/urllib3/util/ssl_.py,sha256=QDuuTxPSCj1rYtZ4xpD7Ux-r20TD50aHyqKyhQ7Bq4A,17460
+pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=Ir4cZVEjmAk8gUAIHWSi7wtOO83UCYABY2xFD1Ql_WA,5758
+pip/_vendor/urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895
+pip/_vendor/urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168
+pip/_vendor/urllib3/util/url.py,sha256=lCAE7M5myA8EDdW0sJuyyZhVB9K_j38ljWhHAnFaWoE,14296
+pip/_vendor/urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403
+pip/_vendor/vendor.txt,sha256=43152uDtpsunEE29vmLqqKZUosdrbvzIFkzscLB55Cg,332
+pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/REQUESTED b/env/Lib/site-packages/pip-24.3.1.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/WHEEL b/env/Lib/site-packages/pip-24.3.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..da25d7b42315fc321e466d6e69705ea21bcff507
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (75.2.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/entry_points.txt b/env/Lib/site-packages/pip-24.3.1.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..25fcf7e2cdf0943ad5c074d4777c721cd3340986
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+pip = pip._internal.cli.main:main
+pip3 = pip._internal.cli.main:main
diff --git a/env/Lib/site-packages/pip-24.3.1.dist-info/top_level.txt b/env/Lib/site-packages/pip-24.3.1.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/env/Lib/site-packages/pip-24.3.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+pip
diff --git a/env/Lib/site-packages/pip/__init__.py b/env/Lib/site-packages/pip/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..efefccffc7a1d61d478ead8475f52dffb4b832c5
--- /dev/null
+++ b/env/Lib/site-packages/pip/__init__.py
@@ -0,0 +1,13 @@
+from typing import List, Optional
+
+__version__ = "24.3.1"
+
+
+def main(args: Optional[List[str]] = None) -> int:
+ """This is an internal API only meant for use by pip's own console scripts.
+
+ For additional details, see https://github.com/pypa/pip/issues/7498.
+ """
+ from pip._internal.utils.entrypoints import _wrapper
+
+ return _wrapper(args)
diff --git a/env/Lib/site-packages/pip/__main__.py b/env/Lib/site-packages/pip/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5991326115fe5026470165b387ba2bc78bceb006
--- /dev/null
+++ b/env/Lib/site-packages/pip/__main__.py
@@ -0,0 +1,24 @@
+import os
+import sys
+
+# Remove '' and current working directory from the first entry
+# of sys.path, if present to avoid using current directory
+# in pip commands check, freeze, install, list and show,
+# when invoked as python -m pip
+if sys.path[0] in ("", os.getcwd()):
+ sys.path.pop(0)
+
+# If we are running from a wheel, add the wheel to sys.path
+# This allows the usage python pip-*.whl/pip install pip-*.whl
+if __package__ == "":
+ # __file__ is pip-*.whl/pip/__main__.py
+ # first dirname call strips of '/__main__.py', second strips off '/pip'
+ # Resulting path is the name of the wheel itself
+ # Add that to sys.path so we can import pip
+ path = os.path.dirname(os.path.dirname(__file__))
+ sys.path.insert(0, path)
+
+if __name__ == "__main__":
+ from pip._internal.cli.main import main as _main
+
+ sys.exit(_main())
diff --git a/env/Lib/site-packages/pip/__pip-runner__.py b/env/Lib/site-packages/pip/__pip-runner__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c633787fcedff4f145f1edfcc4e4ffc6988fe5ac
--- /dev/null
+++ b/env/Lib/site-packages/pip/__pip-runner__.py
@@ -0,0 +1,50 @@
+"""Execute exactly this copy of pip, within a different environment.
+
+This file is named as it is, to ensure that this module can't be imported via
+an import statement.
+"""
+
+# /!\ This version compatibility check section must be Python 2 compatible. /!\
+
+import sys
+
+# Copied from pyproject.toml
+PYTHON_REQUIRES = (3, 8)
+
+
+def version_str(version): # type: ignore
+ return ".".join(str(v) for v in version)
+
+
+if sys.version_info[:2] < PYTHON_REQUIRES:
+ raise SystemExit(
+ "This version of pip does not support python {} (requires >={}).".format(
+ version_str(sys.version_info[:2]), version_str(PYTHON_REQUIRES)
+ )
+ )
+
+# From here on, we can use Python 3 features, but the syntax must remain
+# Python 2 compatible.
+
+import runpy # noqa: E402
+from importlib.machinery import PathFinder # noqa: E402
+from os.path import dirname # noqa: E402
+
+PIP_SOURCES_ROOT = dirname(dirname(__file__))
+
+
+class PipImportRedirectingFinder:
+ @classmethod
+ def find_spec(self, fullname, path=None, target=None): # type: ignore
+ if fullname != "pip":
+ return None
+
+ spec = PathFinder.find_spec(fullname, [PIP_SOURCES_ROOT], target)
+ assert spec, (PIP_SOURCES_ROOT, fullname)
+ return spec
+
+
+sys.meta_path.insert(0, PipImportRedirectingFinder())
+
+assert __name__ == "__main__", "Cannot run __pip-runner__.py as a non-main module"
+runpy.run_module("pip", run_name="__main__", alter_sys=True)
diff --git a/env/Lib/site-packages/pip/_internal/__init__.py b/env/Lib/site-packages/pip/_internal/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a5b7f87f973b36af0ee6fbfb76ce38420f5f9d7
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/__init__.py
@@ -0,0 +1,18 @@
+from typing import List, Optional
+
+from pip._internal.utils import _log
+
+# init_logging() must be called before any call to logging.getLogger()
+# which happens at import of most modules.
+_log.init_logging()
+
+
+def main(args: Optional[List[str]] = None) -> int:
+ """This is preserved for old console scripts that may still be referencing
+ it.
+
+ For additional details, see https://github.com/pypa/pip/issues/7498.
+ """
+ from pip._internal.utils.entrypoints import _wrapper
+
+ return _wrapper(args)
diff --git a/env/Lib/site-packages/pip/_internal/build_env.py b/env/Lib/site-packages/pip/_internal/build_env.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f1e2667caf75eb9a7e995ab1060b059b90c60e8
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/build_env.py
@@ -0,0 +1,319 @@
+"""Build Environment used for isolation during sdist building
+"""
+
+import logging
+import os
+import pathlib
+import site
+import sys
+import textwrap
+from collections import OrderedDict
+from types import TracebackType
+from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
+
+from pip._vendor.certifi import where
+from pip._vendor.packaging.version import Version
+
+from pip import __file__ as pip_location
+from pip._internal.cli.spinners import open_spinner
+from pip._internal.locations import get_platlib, get_purelib, get_scheme
+from pip._internal.metadata import get_default_environment, get_environment
+from pip._internal.utils.logging import VERBOSE
+from pip._internal.utils.packaging import get_requirement
+from pip._internal.utils.subprocess import call_subprocess
+from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
+
+if TYPE_CHECKING:
+ from pip._internal.index.package_finder import PackageFinder
+
+logger = logging.getLogger(__name__)
+
+
+def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
+ return (a, b) if a != b else (a,)
+
+
+class _Prefix:
+ def __init__(self, path: str) -> None:
+ self.path = path
+ self.setup = False
+ scheme = get_scheme("", prefix=path)
+ self.bin_dir = scheme.scripts
+ self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
+
+
+def get_runnable_pip() -> str:
+ """Get a file to pass to a Python executable, to run the currently-running pip.
+
+ This is used to run a pip subprocess, for installing requirements into the build
+ environment.
+ """
+ source = pathlib.Path(pip_location).resolve().parent
+
+ if not source.is_dir():
+ # This would happen if someone is using pip from inside a zip file. In that
+ # case, we can use that directly.
+ return str(source)
+
+ return os.fsdecode(source / "__pip-runner__.py")
+
+
+def _get_system_sitepackages() -> Set[str]:
+ """Get system site packages
+
+ Usually from site.getsitepackages,
+ but fallback on `get_purelib()/get_platlib()` if unavailable
+ (e.g. in a virtualenv created by virtualenv<20)
+
+ Returns normalized set of strings.
+ """
+ if hasattr(site, "getsitepackages"):
+ system_sites = site.getsitepackages()
+ else:
+ # virtualenv < 20 overwrites site.py without getsitepackages
+ # fallback on get_purelib/get_platlib.
+ # this is known to miss things, but shouldn't in the cases
+ # where getsitepackages() has been removed (inside a virtualenv)
+ system_sites = [get_purelib(), get_platlib()]
+ return {os.path.normcase(path) for path in system_sites}
+
+
+class BuildEnvironment:
+ """Creates and manages an isolated environment to install build deps"""
+
+ def __init__(self) -> None:
+ temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
+
+ self._prefixes = OrderedDict(
+ (name, _Prefix(os.path.join(temp_dir.path, name)))
+ for name in ("normal", "overlay")
+ )
+
+ self._bin_dirs: List[str] = []
+ self._lib_dirs: List[str] = []
+ for prefix in reversed(list(self._prefixes.values())):
+ self._bin_dirs.append(prefix.bin_dir)
+ self._lib_dirs.extend(prefix.lib_dirs)
+
+ # Customize site to:
+ # - ensure .pth files are honored
+ # - prevent access to system site packages
+ system_sites = _get_system_sitepackages()
+
+ self._site_dir = os.path.join(temp_dir.path, "site")
+ if not os.path.exists(self._site_dir):
+ os.mkdir(self._site_dir)
+ with open(
+ os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
+ ) as fp:
+ fp.write(
+ textwrap.dedent(
+ """
+ import os, site, sys
+
+ # First, drop system-sites related paths.
+ original_sys_path = sys.path[:]
+ known_paths = set()
+ for path in {system_sites!r}:
+ site.addsitedir(path, known_paths=known_paths)
+ system_paths = set(
+ os.path.normcase(path)
+ for path in sys.path[len(original_sys_path):]
+ )
+ original_sys_path = [
+ path for path in original_sys_path
+ if os.path.normcase(path) not in system_paths
+ ]
+ sys.path = original_sys_path
+
+ # Second, add lib directories.
+ # ensuring .pth file are processed.
+ for path in {lib_dirs!r}:
+ assert not path in sys.path
+ site.addsitedir(path)
+ """
+ ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
+ )
+
+ def __enter__(self) -> None:
+ self._save_env = {
+ name: os.environ.get(name, None)
+ for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
+ }
+
+ path = self._bin_dirs[:]
+ old_path = self._save_env["PATH"]
+ if old_path:
+ path.extend(old_path.split(os.pathsep))
+
+ pythonpath = [self._site_dir]
+
+ os.environ.update(
+ {
+ "PATH": os.pathsep.join(path),
+ "PYTHONNOUSERSITE": "1",
+ "PYTHONPATH": os.pathsep.join(pythonpath),
+ }
+ )
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ for varname, old_value in self._save_env.items():
+ if old_value is None:
+ os.environ.pop(varname, None)
+ else:
+ os.environ[varname] = old_value
+
+ def check_requirements(
+ self, reqs: Iterable[str]
+ ) -> Tuple[Set[Tuple[str, str]], Set[str]]:
+ """Return 2 sets:
+ - conflicting requirements: set of (installed, wanted) reqs tuples
+ - missing requirements: set of reqs
+ """
+ missing = set()
+ conflicting = set()
+ if reqs:
+ env = (
+ get_environment(self._lib_dirs)
+ if hasattr(self, "_lib_dirs")
+ else get_default_environment()
+ )
+ for req_str in reqs:
+ req = get_requirement(req_str)
+ # We're explicitly evaluating with an empty extra value, since build
+ # environments are not provided any mechanism to select specific extras.
+ if req.marker is not None and not req.marker.evaluate({"extra": ""}):
+ continue
+ dist = env.get_distribution(req.name)
+ if not dist:
+ missing.add(req_str)
+ continue
+ if isinstance(dist.version, Version):
+ installed_req_str = f"{req.name}=={dist.version}"
+ else:
+ installed_req_str = f"{req.name}==={dist.version}"
+ if not req.specifier.contains(dist.version, prereleases=True):
+ conflicting.add((installed_req_str, req_str))
+ # FIXME: Consider direct URL?
+ return conflicting, missing
+
+ def install_requirements(
+ self,
+ finder: "PackageFinder",
+ requirements: Iterable[str],
+ prefix_as_string: str,
+ *,
+ kind: str,
+ ) -> None:
+ prefix = self._prefixes[prefix_as_string]
+ assert not prefix.setup
+ prefix.setup = True
+ if not requirements:
+ return
+ self._install_requirements(
+ get_runnable_pip(),
+ finder,
+ requirements,
+ prefix,
+ kind=kind,
+ )
+
+ @staticmethod
+ def _install_requirements(
+ pip_runnable: str,
+ finder: "PackageFinder",
+ requirements: Iterable[str],
+ prefix: _Prefix,
+ *,
+ kind: str,
+ ) -> None:
+ args: List[str] = [
+ sys.executable,
+ pip_runnable,
+ "install",
+ "--ignore-installed",
+ "--no-user",
+ "--prefix",
+ prefix.path,
+ "--no-warn-script-location",
+ "--disable-pip-version-check",
+ # The prefix specified two lines above, thus
+ # target from config file or env var should be ignored
+ "--target",
+ "",
+ ]
+ if logger.getEffectiveLevel() <= logging.DEBUG:
+ args.append("-vv")
+ elif logger.getEffectiveLevel() <= VERBOSE:
+ args.append("-v")
+ for format_control in ("no_binary", "only_binary"):
+ formats = getattr(finder.format_control, format_control)
+ args.extend(
+ (
+ "--" + format_control.replace("_", "-"),
+ ",".join(sorted(formats or {":none:"})),
+ )
+ )
+
+ index_urls = finder.index_urls
+ if index_urls:
+ args.extend(["-i", index_urls[0]])
+ for extra_index in index_urls[1:]:
+ args.extend(["--extra-index-url", extra_index])
+ else:
+ args.append("--no-index")
+ for link in finder.find_links:
+ args.extend(["--find-links", link])
+
+ for host in finder.trusted_hosts:
+ args.extend(["--trusted-host", host])
+ if finder.allow_all_prereleases:
+ args.append("--pre")
+ if finder.prefer_binary:
+ args.append("--prefer-binary")
+ args.append("--")
+ args.extend(requirements)
+ extra_environ = {"_PIP_STANDALONE_CERT": where()}
+ with open_spinner(f"Installing {kind}") as spinner:
+ call_subprocess(
+ args,
+ command_desc=f"pip subprocess to install {kind}",
+ spinner=spinner,
+ extra_environ=extra_environ,
+ )
+
+
+class NoOpBuildEnvironment(BuildEnvironment):
+ """A no-op drop-in replacement for BuildEnvironment"""
+
+ def __init__(self) -> None:
+ pass
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ pass
+
+ def cleanup(self) -> None:
+ pass
+
+ def install_requirements(
+ self,
+ finder: "PackageFinder",
+ requirements: Iterable[str],
+ prefix_as_string: str,
+ *,
+ kind: str,
+ ) -> None:
+ raise NotImplementedError()
diff --git a/env/Lib/site-packages/pip/_internal/cache.py b/env/Lib/site-packages/pip/_internal/cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b4512672dbad2766464405adb0193f12500e767
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cache.py
@@ -0,0 +1,290 @@
+"""Cache Management
+"""
+
+import hashlib
+import json
+import logging
+import os
+from pathlib import Path
+from typing import Any, Dict, List, Optional
+
+from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.exceptions import InvalidWheelFilename
+from pip._internal.models.direct_url import DirectUrl
+from pip._internal.models.link import Link
+from pip._internal.models.wheel import Wheel
+from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
+from pip._internal.utils.urls import path_to_url
+
+logger = logging.getLogger(__name__)
+
+ORIGIN_JSON_NAME = "origin.json"
+
+
+def _hash_dict(d: Dict[str, str]) -> str:
+ """Return a stable sha224 of a dictionary."""
+ s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
+ return hashlib.sha224(s.encode("ascii")).hexdigest()
+
+
+class Cache:
+ """An abstract class - provides cache directories for data from links
+
+ :param cache_dir: The root of the cache.
+ """
+
+ def __init__(self, cache_dir: str) -> None:
+ super().__init__()
+ assert not cache_dir or os.path.isabs(cache_dir)
+ self.cache_dir = cache_dir or None
+
+ def _get_cache_path_parts(self, link: Link) -> List[str]:
+ """Get parts of part that must be os.path.joined with cache_dir"""
+
+ # We want to generate an url to use as our cache key, we don't want to
+ # just reuse the URL because it might have other items in the fragment
+ # and we don't care about those.
+ key_parts = {"url": link.url_without_fragment}
+ if link.hash_name is not None and link.hash is not None:
+ key_parts[link.hash_name] = link.hash
+ if link.subdirectory_fragment:
+ key_parts["subdirectory"] = link.subdirectory_fragment
+
+ # Include interpreter name, major and minor version in cache key
+ # to cope with ill-behaved sdists that build a different wheel
+ # depending on the python version their setup.py is being run on,
+ # and don't encode the difference in compatibility tags.
+ # https://github.com/pypa/pip/issues/7296
+ key_parts["interpreter_name"] = interpreter_name()
+ key_parts["interpreter_version"] = interpreter_version()
+
+ # Encode our key url with sha224, we'll use this because it has similar
+ # security properties to sha256, but with a shorter total output (and
+ # thus less secure). However the differences don't make a lot of
+ # difference for our use case here.
+ hashed = _hash_dict(key_parts)
+
+ # We want to nest the directories some to prevent having a ton of top
+ # level directories where we might run out of sub directories on some
+ # FS.
+ parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
+
+ return parts
+
+ def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
+ can_not_cache = not self.cache_dir or not canonical_package_name or not link
+ if can_not_cache:
+ return []
+
+ path = self.get_path_for_link(link)
+ if os.path.isdir(path):
+ return [(candidate, path) for candidate in os.listdir(path)]
+ return []
+
+ def get_path_for_link(self, link: Link) -> str:
+ """Return a directory to store cached items in for link."""
+ raise NotImplementedError()
+
+ def get(
+ self,
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Link:
+ """Returns a link to a cached item if it exists, otherwise returns the
+ passed link.
+ """
+ raise NotImplementedError()
+
+
+class SimpleWheelCache(Cache):
+ """A cache of wheels for future installs."""
+
+ def __init__(self, cache_dir: str) -> None:
+ super().__init__(cache_dir)
+
+ def get_path_for_link(self, link: Link) -> str:
+ """Return a directory to store cached wheels for link
+
+ Because there are M wheels for any one sdist, we provide a directory
+ to cache them in, and then consult that directory when looking up
+ cache hits.
+
+ We only insert things into the cache if they have plausible version
+ numbers, so that we don't contaminate the cache with things that were
+ not unique. E.g. ./package might have dozens of installs done for it
+ and build a version of 0.0...and if we built and cached a wheel, we'd
+ end up using the same wheel even if the source has been edited.
+
+ :param link: The link of the sdist for which this will cache wheels.
+ """
+ parts = self._get_cache_path_parts(link)
+ assert self.cache_dir
+ # Store wheels within the root cache_dir
+ return os.path.join(self.cache_dir, "wheels", *parts)
+
+ def get(
+ self,
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Link:
+ candidates = []
+
+ if not package_name:
+ return link
+
+ canonical_package_name = canonicalize_name(package_name)
+ for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name):
+ try:
+ wheel = Wheel(wheel_name)
+ except InvalidWheelFilename:
+ continue
+ if canonicalize_name(wheel.name) != canonical_package_name:
+ logger.debug(
+ "Ignoring cached wheel %s for %s as it "
+ "does not match the expected distribution name %s.",
+ wheel_name,
+ link,
+ package_name,
+ )
+ continue
+ if not wheel.supported(supported_tags):
+ # Built for a different python/arch/etc
+ continue
+ candidates.append(
+ (
+ wheel.support_index_min(supported_tags),
+ wheel_name,
+ wheel_dir,
+ )
+ )
+
+ if not candidates:
+ return link
+
+ _, wheel_name, wheel_dir = min(candidates)
+ return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
+
+
+class EphemWheelCache(SimpleWheelCache):
+ """A SimpleWheelCache that creates it's own temporary cache directory"""
+
+ def __init__(self) -> None:
+ self._temp_dir = TempDirectory(
+ kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
+ globally_managed=True,
+ )
+
+ super().__init__(self._temp_dir.path)
+
+
+class CacheEntry:
+ def __init__(
+ self,
+ link: Link,
+ persistent: bool,
+ ):
+ self.link = link
+ self.persistent = persistent
+ self.origin: Optional[DirectUrl] = None
+ origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
+ if origin_direct_url_path.exists():
+ try:
+ self.origin = DirectUrl.from_json(
+ origin_direct_url_path.read_text(encoding="utf-8")
+ )
+ except Exception as e:
+ logger.warning(
+ "Ignoring invalid cache entry origin file %s for %s (%s)",
+ origin_direct_url_path,
+ link.filename,
+ e,
+ )
+
+
+class WheelCache(Cache):
+ """Wraps EphemWheelCache and SimpleWheelCache into a single Cache
+
+ This Cache allows for gracefully degradation, using the ephem wheel cache
+ when a certain link is not found in the simple wheel cache first.
+ """
+
+ def __init__(self, cache_dir: str) -> None:
+ super().__init__(cache_dir)
+ self._wheel_cache = SimpleWheelCache(cache_dir)
+ self._ephem_cache = EphemWheelCache()
+
+ def get_path_for_link(self, link: Link) -> str:
+ return self._wheel_cache.get_path_for_link(link)
+
+ def get_ephem_path_for_link(self, link: Link) -> str:
+ return self._ephem_cache.get_path_for_link(link)
+
+ def get(
+ self,
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Link:
+ cache_entry = self.get_cache_entry(link, package_name, supported_tags)
+ if cache_entry is None:
+ return link
+ return cache_entry.link
+
+ def get_cache_entry(
+ self,
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Optional[CacheEntry]:
+ """Returns a CacheEntry with a link to a cached item if it exists or
+ None. The cache entry indicates if the item was found in the persistent
+ or ephemeral cache.
+ """
+ retval = self._wheel_cache.get(
+ link=link,
+ package_name=package_name,
+ supported_tags=supported_tags,
+ )
+ if retval is not link:
+ return CacheEntry(retval, persistent=True)
+
+ retval = self._ephem_cache.get(
+ link=link,
+ package_name=package_name,
+ supported_tags=supported_tags,
+ )
+ if retval is not link:
+ return CacheEntry(retval, persistent=False)
+
+ return None
+
+ @staticmethod
+ def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None:
+ origin_path = Path(cache_dir) / ORIGIN_JSON_NAME
+ if origin_path.exists():
+ try:
+ origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8"))
+ except Exception as e:
+ logger.warning(
+ "Could not read origin file %s in cache entry (%s). "
+ "Will attempt to overwrite it.",
+ origin_path,
+ e,
+ )
+ else:
+ # TODO: use DirectUrl.equivalent when
+ # https://github.com/pypa/pip/pull/10564 is merged.
+ if origin.url != download_info.url:
+ logger.warning(
+ "Origin URL %s in cache entry %s does not match download URL "
+ "%s. This is likely a pip bug or a cache corruption issue. "
+ "Will overwrite it with the new value.",
+ origin.url,
+ cache_dir,
+ download_info.url,
+ )
+ origin_path.write_text(download_info.to_json(), encoding="utf-8")
diff --git a/env/Lib/site-packages/pip/_internal/cli/__init__.py b/env/Lib/site-packages/pip/_internal/cli/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e589bb917e23823e25f9fff7e0849c4d6d4a62bc
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/__init__.py
@@ -0,0 +1,4 @@
+"""Subpackage containing all of pip's command line interface related code
+"""
+
+# This file intentionally does not import submodules
diff --git a/env/Lib/site-packages/pip/_internal/cli/autocompletion.py b/env/Lib/site-packages/pip/_internal/cli/autocompletion.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3f70ac8553b069d2d7f87926e6ba12f8998b5e5
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/autocompletion.py
@@ -0,0 +1,176 @@
+"""Logic that powers autocompletion installed by ``pip completion``.
+"""
+
+import optparse
+import os
+import sys
+from itertools import chain
+from typing import Any, Iterable, List, Optional
+
+from pip._internal.cli.main_parser import create_main_parser
+from pip._internal.commands import commands_dict, create_command
+from pip._internal.metadata import get_default_environment
+
+
+def autocomplete() -> None:
+ """Entry Point for completion of main and subcommand options."""
+ # Don't complete if user hasn't sourced bash_completion file.
+ if "PIP_AUTO_COMPLETE" not in os.environ:
+ return
+ # Don't complete if autocompletion environment variables
+ # are not present
+ if not os.environ.get("COMP_WORDS") or not os.environ.get("COMP_CWORD"):
+ return
+ cwords = os.environ["COMP_WORDS"].split()[1:]
+ cword = int(os.environ["COMP_CWORD"])
+ try:
+ current = cwords[cword - 1]
+ except IndexError:
+ current = ""
+
+ parser = create_main_parser()
+ subcommands = list(commands_dict)
+ options = []
+
+ # subcommand
+ subcommand_name: Optional[str] = None
+ for word in cwords:
+ if word in subcommands:
+ subcommand_name = word
+ break
+ # subcommand options
+ if subcommand_name is not None:
+ # special case: 'help' subcommand has no options
+ if subcommand_name == "help":
+ sys.exit(1)
+ # special case: list locally installed dists for show and uninstall
+ should_list_installed = not current.startswith("-") and subcommand_name in [
+ "show",
+ "uninstall",
+ ]
+ if should_list_installed:
+ env = get_default_environment()
+ lc = current.lower()
+ installed = [
+ dist.canonical_name
+ for dist in env.iter_installed_distributions(local_only=True)
+ if dist.canonical_name.startswith(lc)
+ and dist.canonical_name not in cwords[1:]
+ ]
+ # if there are no dists installed, fall back to option completion
+ if installed:
+ for dist in installed:
+ print(dist)
+ sys.exit(1)
+
+ should_list_installables = (
+ not current.startswith("-") and subcommand_name == "install"
+ )
+ if should_list_installables:
+ for path in auto_complete_paths(current, "path"):
+ print(path)
+ sys.exit(1)
+
+ subcommand = create_command(subcommand_name)
+
+ for opt in subcommand.parser.option_list_all:
+ if opt.help != optparse.SUPPRESS_HELP:
+ options += [
+ (opt_str, opt.nargs) for opt_str in opt._long_opts + opt._short_opts
+ ]
+
+ # filter out previously specified options from available options
+ prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]]
+ options = [(x, v) for (x, v) in options if x not in prev_opts]
+ # filter options by current input
+ options = [(k, v) for k, v in options if k.startswith(current)]
+ # get completion type given cwords and available subcommand options
+ completion_type = get_path_completion_type(
+ cwords,
+ cword,
+ subcommand.parser.option_list_all,
+ )
+ # get completion files and directories if ``completion_type`` is
+ # ````, ```` or ````
+ if completion_type:
+ paths = auto_complete_paths(current, completion_type)
+ options = [(path, 0) for path in paths]
+ for option in options:
+ opt_label = option[0]
+ # append '=' to options which require args
+ if option[1] and option[0][:2] == "--":
+ opt_label += "="
+ print(opt_label)
+ else:
+ # show main parser options only when necessary
+
+ opts = [i.option_list for i in parser.option_groups]
+ opts.append(parser.option_list)
+ flattened_opts = chain.from_iterable(opts)
+ if current.startswith("-"):
+ for opt in flattened_opts:
+ if opt.help != optparse.SUPPRESS_HELP:
+ subcommands += opt._long_opts + opt._short_opts
+ else:
+ # get completion type given cwords and all available options
+ completion_type = get_path_completion_type(cwords, cword, flattened_opts)
+ if completion_type:
+ subcommands = list(auto_complete_paths(current, completion_type))
+
+ print(" ".join([x for x in subcommands if x.startswith(current)]))
+ sys.exit(1)
+
+
+def get_path_completion_type(
+ cwords: List[str], cword: int, opts: Iterable[Any]
+) -> Optional[str]:
+ """Get the type of path completion (``file``, ``dir``, ``path`` or None)
+
+ :param cwords: same as the environmental variable ``COMP_WORDS``
+ :param cword: same as the environmental variable ``COMP_CWORD``
+ :param opts: The available options to check
+ :return: path completion type (``file``, ``dir``, ``path`` or None)
+ """
+ if cword < 2 or not cwords[cword - 2].startswith("-"):
+ return None
+ for opt in opts:
+ if opt.help == optparse.SUPPRESS_HELP:
+ continue
+ for o in str(opt).split("/"):
+ if cwords[cword - 2].split("=")[0] == o:
+ if not opt.metavar or any(
+ x in ("path", "file", "dir") for x in opt.metavar.split("/")
+ ):
+ return opt.metavar
+ return None
+
+
+def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]:
+ """If ``completion_type`` is ``file`` or ``path``, list all regular files
+ and directories starting with ``current``; otherwise only list directories
+ starting with ``current``.
+
+ :param current: The word to be completed
+ :param completion_type: path completion type(``file``, ``path`` or ``dir``)
+ :return: A generator of regular files and/or directories
+ """
+ directory, filename = os.path.split(current)
+ current_path = os.path.abspath(directory)
+ # Don't complete paths if they can't be accessed
+ if not os.access(current_path, os.R_OK):
+ return
+ filename = os.path.normcase(filename)
+ # list all files that start with ``filename``
+ file_list = (
+ x for x in os.listdir(current_path) if os.path.normcase(x).startswith(filename)
+ )
+ for f in file_list:
+ opt = os.path.join(current_path, f)
+ comp_file = os.path.normcase(os.path.join(directory, f))
+ # complete regular files when there is not ```` after option
+ # complete directories when there is ````, ```` or
+ # ````after option
+ if completion_type != "dir" and os.path.isfile(opt):
+ yield comp_file
+ elif os.path.isdir(opt):
+ yield os.path.join(comp_file, "")
diff --git a/env/Lib/site-packages/pip/_internal/cli/base_command.py b/env/Lib/site-packages/pip/_internal/cli/base_command.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc1ab65949d8edace4e3f7938296bd32e963f06b
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/base_command.py
@@ -0,0 +1,231 @@
+"""Base Command class, and related routines"""
+
+import logging
+import logging.config
+import optparse
+import os
+import sys
+import traceback
+from optparse import Values
+from typing import List, Optional, Tuple
+
+from pip._vendor.rich import reconfigure
+from pip._vendor.rich import traceback as rich_traceback
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.command_context import CommandContextMixIn
+from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
+from pip._internal.cli.status_codes import (
+ ERROR,
+ PREVIOUS_BUILD_DIR_ERROR,
+ UNKNOWN_ERROR,
+ VIRTUALENV_NOT_FOUND,
+)
+from pip._internal.exceptions import (
+ BadCommand,
+ CommandError,
+ DiagnosticPipError,
+ InstallationError,
+ NetworkConnectionError,
+ PreviousBuildDirError,
+)
+from pip._internal.utils.filesystem import check_path_owner
+from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
+from pip._internal.utils.misc import get_prog, normalize_path
+from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry
+from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry
+from pip._internal.utils.virtualenv import running_under_virtualenv
+
+__all__ = ["Command"]
+
+logger = logging.getLogger(__name__)
+
+
+class Command(CommandContextMixIn):
+ usage: str = ""
+ ignore_require_venv: bool = False
+
+ def __init__(self, name: str, summary: str, isolated: bool = False) -> None:
+ super().__init__()
+
+ self.name = name
+ self.summary = summary
+ self.parser = ConfigOptionParser(
+ usage=self.usage,
+ prog=f"{get_prog()} {name}",
+ formatter=UpdatingDefaultsHelpFormatter(),
+ add_help_option=False,
+ name=name,
+ description=self.__doc__,
+ isolated=isolated,
+ )
+
+ self.tempdir_registry: Optional[TempDirRegistry] = None
+
+ # Commands should add options to this option group
+ optgroup_name = f"{self.name.capitalize()} Options"
+ self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
+
+ # Add the general options
+ gen_opts = cmdoptions.make_option_group(
+ cmdoptions.general_group,
+ self.parser,
+ )
+ self.parser.add_option_group(gen_opts)
+
+ self.add_options()
+
+ def add_options(self) -> None:
+ pass
+
+ def handle_pip_version_check(self, options: Values) -> None:
+ """
+ This is a no-op so that commands by default do not do the pip version
+ check.
+ """
+ # Make sure we do the pip version check if the index_group options
+ # are present.
+ assert not hasattr(options, "no_index")
+
+ def run(self, options: Values, args: List[str]) -> int:
+ raise NotImplementedError
+
+ def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int:
+ def _inner_run() -> int:
+ try:
+ return self.run(options, args)
+ finally:
+ self.handle_pip_version_check(options)
+
+ if options.debug_mode:
+ rich_traceback.install(show_locals=True)
+ return _inner_run()
+
+ try:
+ status = _inner_run()
+ assert isinstance(status, int)
+ return status
+ except DiagnosticPipError as exc:
+ logger.error("%s", exc, extra={"rich": True})
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except PreviousBuildDirError as exc:
+ logger.critical(str(exc))
+ logger.debug("Exception information:", exc_info=True)
+
+ return PREVIOUS_BUILD_DIR_ERROR
+ except (
+ InstallationError,
+ BadCommand,
+ NetworkConnectionError,
+ ) as exc:
+ logger.critical(str(exc))
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except CommandError as exc:
+ logger.critical("%s", exc)
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except BrokenStdoutLoggingError:
+ # Bypass our logger and write any remaining messages to
+ # stderr because stdout no longer works.
+ print("ERROR: Pipe to stdout was broken", file=sys.stderr)
+ if level_number <= logging.DEBUG:
+ traceback.print_exc(file=sys.stderr)
+
+ return ERROR
+ except KeyboardInterrupt:
+ logger.critical("Operation cancelled by user")
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except BaseException:
+ logger.critical("Exception:", exc_info=True)
+
+ return UNKNOWN_ERROR
+
+ def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]:
+ # factored out for testability
+ return self.parser.parse_args(args)
+
+ def main(self, args: List[str]) -> int:
+ try:
+ with self.main_context():
+ return self._main(args)
+ finally:
+ logging.shutdown()
+
+ def _main(self, args: List[str]) -> int:
+ # We must initialize this before the tempdir manager, otherwise the
+ # configuration would not be accessible by the time we clean up the
+ # tempdir manager.
+ self.tempdir_registry = self.enter_context(tempdir_registry())
+ # Intentionally set as early as possible so globally-managed temporary
+ # directories are available to the rest of the code.
+ self.enter_context(global_tempdir_manager())
+
+ options, args = self.parse_args(args)
+
+ # Set verbosity so that it can be used elsewhere.
+ self.verbosity = options.verbose - options.quiet
+
+ reconfigure(no_color=options.no_color)
+ level_number = setup_logging(
+ verbosity=self.verbosity,
+ no_color=options.no_color,
+ user_log_file=options.log,
+ )
+
+ always_enabled_features = set(options.features_enabled) & set(
+ cmdoptions.ALWAYS_ENABLED_FEATURES
+ )
+ if always_enabled_features:
+ logger.warning(
+ "The following features are always enabled: %s. ",
+ ", ".join(sorted(always_enabled_features)),
+ )
+
+ # Make sure that the --python argument isn't specified after the
+ # subcommand. We can tell, because if --python was specified,
+ # we should only reach this point if we're running in the created
+ # subprocess, which has the _PIP_RUNNING_IN_SUBPROCESS environment
+ # variable set.
+ if options.python and "_PIP_RUNNING_IN_SUBPROCESS" not in os.environ:
+ logger.critical(
+ "The --python option must be placed before the pip subcommand name"
+ )
+ sys.exit(ERROR)
+
+ # TODO: Try to get these passing down from the command?
+ # without resorting to os.environ to hold these.
+ # This also affects isolated builds and it should.
+
+ if options.no_input:
+ os.environ["PIP_NO_INPUT"] = "1"
+
+ if options.exists_action:
+ os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action)
+
+ if options.require_venv and not self.ignore_require_venv:
+ # If a venv is required check if it can really be found
+ if not running_under_virtualenv():
+ logger.critical("Could not find an activated virtualenv (required).")
+ sys.exit(VIRTUALENV_NOT_FOUND)
+
+ if options.cache_dir:
+ options.cache_dir = normalize_path(options.cache_dir)
+ if not check_path_owner(options.cache_dir):
+ logger.warning(
+ "The directory '%s' or its parent directory is not owned "
+ "or is not writable by the current user. The cache "
+ "has been disabled. Check the permissions and owner of "
+ "that directory. If executing pip with sudo, you should "
+ "use sudo's -H flag.",
+ options.cache_dir,
+ )
+ options.cache_dir = None
+
+ return self._run_wrapper(level_number, options, args)
diff --git a/env/Lib/site-packages/pip/_internal/cli/cmdoptions.py b/env/Lib/site-packages/pip/_internal/cli/cmdoptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b7cff77bdd5fe80be954f846b481873867e05b4
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/cmdoptions.py
@@ -0,0 +1,1075 @@
+"""
+shared options and groups
+
+The principle here is to define options once, but *not* instantiate them
+globally. One reason being that options with action='append' can carry state
+between parses. pip parses general options twice internally, and shouldn't
+pass on state. To be consistent, all options will follow this design.
+"""
+
+# The following comment should be removed at some point in the future.
+# mypy: strict-optional=False
+
+import importlib.util
+import logging
+import os
+import textwrap
+from functools import partial
+from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
+from textwrap import dedent
+from typing import Any, Callable, Dict, Optional, Tuple
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.cli.parser import ConfigOptionParser
+from pip._internal.exceptions import CommandError
+from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
+from pip._internal.models.format_control import FormatControl
+from pip._internal.models.index import PyPI
+from pip._internal.models.target_python import TargetPython
+from pip._internal.utils.hashes import STRONG_HASHES
+from pip._internal.utils.misc import strtobool
+
+logger = logging.getLogger(__name__)
+
+
+def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
+ """
+ Raise an option parsing error using parser.error().
+
+ Args:
+ parser: an OptionParser instance.
+ option: an Option instance.
+ msg: the error text.
+ """
+ msg = f"{option} error: {msg}"
+ msg = textwrap.fill(" ".join(msg.split()))
+ parser.error(msg)
+
+
+def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
+ """
+ Return an OptionGroup object
+ group -- assumed to be dict with 'name' and 'options' keys
+ parser -- an optparse Parser
+ """
+ option_group = OptionGroup(parser, group["name"])
+ for option in group["options"]:
+ option_group.add_option(option())
+ return option_group
+
+
+def check_dist_restriction(options: Values, check_target: bool = False) -> None:
+ """Function for determining if custom platform options are allowed.
+
+ :param options: The OptionParser options.
+ :param check_target: Whether or not to check if --target is being used.
+ """
+ dist_restriction_set = any(
+ [
+ options.python_version,
+ options.platforms,
+ options.abis,
+ options.implementation,
+ ]
+ )
+
+ binary_only = FormatControl(set(), {":all:"})
+ sdist_dependencies_allowed = (
+ options.format_control != binary_only and not options.ignore_dependencies
+ )
+
+ # Installations or downloads using dist restrictions must not combine
+ # source distributions and dist-specific wheels, as they are not
+ # guaranteed to be locally compatible.
+ if dist_restriction_set and sdist_dependencies_allowed:
+ raise CommandError(
+ "When restricting platform and interpreter constraints using "
+ "--python-version, --platform, --abi, or --implementation, "
+ "either --no-deps must be set, or --only-binary=:all: must be "
+ "set and --no-binary must not be set (or must be set to "
+ ":none:)."
+ )
+
+ if check_target:
+ if not options.dry_run and dist_restriction_set and not options.target_dir:
+ raise CommandError(
+ "Can not use any platform or abi specific options unless "
+ "installing via '--target' or using '--dry-run'"
+ )
+
+
+def _path_option_check(option: Option, opt: str, value: str) -> str:
+ return os.path.expanduser(value)
+
+
+def _package_name_option_check(option: Option, opt: str, value: str) -> str:
+ return canonicalize_name(value)
+
+
+class PipOption(Option):
+ TYPES = Option.TYPES + ("path", "package_name")
+ TYPE_CHECKER = Option.TYPE_CHECKER.copy()
+ TYPE_CHECKER["package_name"] = _package_name_option_check
+ TYPE_CHECKER["path"] = _path_option_check
+
+
+###########
+# options #
+###########
+
+help_: Callable[..., Option] = partial(
+ Option,
+ "-h",
+ "--help",
+ dest="help",
+ action="help",
+ help="Show help.",
+)
+
+debug_mode: Callable[..., Option] = partial(
+ Option,
+ "--debug",
+ dest="debug_mode",
+ action="store_true",
+ default=False,
+ help=(
+ "Let unhandled exceptions propagate outside the main subroutine, "
+ "instead of logging them to stderr."
+ ),
+)
+
+isolated_mode: Callable[..., Option] = partial(
+ Option,
+ "--isolated",
+ dest="isolated_mode",
+ action="store_true",
+ default=False,
+ help=(
+ "Run pip in an isolated mode, ignoring environment variables and user "
+ "configuration."
+ ),
+)
+
+require_virtualenv: Callable[..., Option] = partial(
+ Option,
+ "--require-virtualenv",
+ "--require-venv",
+ dest="require_venv",
+ action="store_true",
+ default=False,
+ help=(
+ "Allow pip to only run in a virtual environment; "
+ "exit with an error otherwise."
+ ),
+)
+
+override_externally_managed: Callable[..., Option] = partial(
+ Option,
+ "--break-system-packages",
+ dest="override_externally_managed",
+ action="store_true",
+ help="Allow pip to modify an EXTERNALLY-MANAGED Python installation",
+)
+
+python: Callable[..., Option] = partial(
+ Option,
+ "--python",
+ dest="python",
+ help="Run pip with the specified Python interpreter.",
+)
+
+verbose: Callable[..., Option] = partial(
+ Option,
+ "-v",
+ "--verbose",
+ dest="verbose",
+ action="count",
+ default=0,
+ help="Give more output. Option is additive, and can be used up to 3 times.",
+)
+
+no_color: Callable[..., Option] = partial(
+ Option,
+ "--no-color",
+ dest="no_color",
+ action="store_true",
+ default=False,
+ help="Suppress colored output.",
+)
+
+version: Callable[..., Option] = partial(
+ Option,
+ "-V",
+ "--version",
+ dest="version",
+ action="store_true",
+ help="Show version and exit.",
+)
+
+quiet: Callable[..., Option] = partial(
+ Option,
+ "-q",
+ "--quiet",
+ dest="quiet",
+ action="count",
+ default=0,
+ help=(
+ "Give less output. Option is additive, and can be used up to 3"
+ " times (corresponding to WARNING, ERROR, and CRITICAL logging"
+ " levels)."
+ ),
+)
+
+progress_bar: Callable[..., Option] = partial(
+ Option,
+ "--progress-bar",
+ dest="progress_bar",
+ type="choice",
+ choices=["on", "off", "raw"],
+ default="on",
+ help="Specify whether the progress bar should be used [on, off, raw] (default: on)",
+)
+
+log: Callable[..., Option] = partial(
+ PipOption,
+ "--log",
+ "--log-file",
+ "--local-log",
+ dest="log",
+ metavar="path",
+ type="path",
+ help="Path to a verbose appending log.",
+)
+
+no_input: Callable[..., Option] = partial(
+ Option,
+ # Don't ask for input
+ "--no-input",
+ dest="no_input",
+ action="store_true",
+ default=False,
+ help="Disable prompting for input.",
+)
+
+keyring_provider: Callable[..., Option] = partial(
+ Option,
+ "--keyring-provider",
+ dest="keyring_provider",
+ choices=["auto", "disabled", "import", "subprocess"],
+ default="auto",
+ help=(
+ "Enable the credential lookup via the keyring library if user input is allowed."
+ " Specify which mechanism to use [disabled, import, subprocess]."
+ " (default: disabled)"
+ ),
+)
+
+proxy: Callable[..., Option] = partial(
+ Option,
+ "--proxy",
+ dest="proxy",
+ type="str",
+ default="",
+ help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.",
+)
+
+retries: Callable[..., Option] = partial(
+ Option,
+ "--retries",
+ dest="retries",
+ type="int",
+ default=5,
+ help="Maximum number of retries each connection should attempt "
+ "(default %default times).",
+)
+
+timeout: Callable[..., Option] = partial(
+ Option,
+ "--timeout",
+ "--default-timeout",
+ metavar="sec",
+ dest="timeout",
+ type="float",
+ default=15,
+ help="Set the socket timeout (default %default seconds).",
+)
+
+
+def exists_action() -> Option:
+ return Option(
+ # Option when path already exist
+ "--exists-action",
+ dest="exists_action",
+ type="choice",
+ choices=["s", "i", "w", "b", "a"],
+ default=[],
+ action="append",
+ metavar="action",
+ help="Default action when a path already exists: "
+ "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.",
+ )
+
+
+cert: Callable[..., Option] = partial(
+ PipOption,
+ "--cert",
+ dest="cert",
+ type="path",
+ metavar="path",
+ help=(
+ "Path to PEM-encoded CA certificate bundle. "
+ "If provided, overrides the default. "
+ "See 'SSL Certificate Verification' in pip documentation "
+ "for more information."
+ ),
+)
+
+client_cert: Callable[..., Option] = partial(
+ PipOption,
+ "--client-cert",
+ dest="client_cert",
+ type="path",
+ default=None,
+ metavar="path",
+ help="Path to SSL client certificate, a single file containing the "
+ "private key and the certificate in PEM format.",
+)
+
+index_url: Callable[..., Option] = partial(
+ Option,
+ "-i",
+ "--index-url",
+ "--pypi-url",
+ dest="index_url",
+ metavar="URL",
+ default=PyPI.simple_url,
+ help="Base URL of the Python Package Index (default %default). "
+ "This should point to a repository compliant with PEP 503 "
+ "(the simple repository API) or a local directory laid out "
+ "in the same format.",
+)
+
+
+def extra_index_url() -> Option:
+ return Option(
+ "--extra-index-url",
+ dest="extra_index_urls",
+ metavar="URL",
+ action="append",
+ default=[],
+ help="Extra URLs of package indexes to use in addition to "
+ "--index-url. Should follow the same rules as "
+ "--index-url.",
+ )
+
+
+no_index: Callable[..., Option] = partial(
+ Option,
+ "--no-index",
+ dest="no_index",
+ action="store_true",
+ default=False,
+ help="Ignore package index (only looking at --find-links URLs instead).",
+)
+
+
+def find_links() -> Option:
+ return Option(
+ "-f",
+ "--find-links",
+ dest="find_links",
+ action="append",
+ default=[],
+ metavar="url",
+ help="If a URL or path to an html file, then parse for links to "
+ "archives such as sdist (.tar.gz) or wheel (.whl) files. "
+ "If a local path or file:// URL that's a directory, "
+ "then look for archives in the directory listing. "
+ "Links to VCS project URLs are not supported.",
+ )
+
+
+def trusted_host() -> Option:
+ return Option(
+ "--trusted-host",
+ dest="trusted_hosts",
+ action="append",
+ metavar="HOSTNAME",
+ default=[],
+ help="Mark this host or host:port pair as trusted, even though it "
+ "does not have valid or any HTTPS.",
+ )
+
+
+def constraints() -> Option:
+ return Option(
+ "-c",
+ "--constraint",
+ dest="constraints",
+ action="append",
+ default=[],
+ metavar="file",
+ help="Constrain versions using the given constraints file. "
+ "This option can be used multiple times.",
+ )
+
+
+def requirements() -> Option:
+ return Option(
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
+ default=[],
+ metavar="file",
+ help="Install from the given requirements file. "
+ "This option can be used multiple times.",
+ )
+
+
+def editable() -> Option:
+ return Option(
+ "-e",
+ "--editable",
+ dest="editables",
+ action="append",
+ default=[],
+ metavar="path/url",
+ help=(
+ "Install a project in editable mode (i.e. setuptools "
+ '"develop mode") from a local project path or a VCS url.'
+ ),
+ )
+
+
+def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None:
+ value = os.path.abspath(value)
+ setattr(parser.values, option.dest, value)
+
+
+src: Callable[..., Option] = partial(
+ PipOption,
+ "--src",
+ "--source",
+ "--source-dir",
+ "--source-directory",
+ dest="src_dir",
+ type="path",
+ metavar="dir",
+ default=get_src_prefix(),
+ action="callback",
+ callback=_handle_src,
+ help="Directory to check out editable projects into. "
+ 'The default in a virtualenv is "/src". '
+ 'The default for global installs is "/src".',
+)
+
+
+def _get_format_control(values: Values, option: Option) -> Any:
+ """Get a format_control object."""
+ return getattr(values, option.dest)
+
+
+def _handle_no_binary(
+ option: Option, opt_str: str, value: str, parser: OptionParser
+) -> None:
+ existing = _get_format_control(parser.values, option)
+ FormatControl.handle_mutual_excludes(
+ value,
+ existing.no_binary,
+ existing.only_binary,
+ )
+
+
+def _handle_only_binary(
+ option: Option, opt_str: str, value: str, parser: OptionParser
+) -> None:
+ existing = _get_format_control(parser.values, option)
+ FormatControl.handle_mutual_excludes(
+ value,
+ existing.only_binary,
+ existing.no_binary,
+ )
+
+
+def no_binary() -> Option:
+ format_control = FormatControl(set(), set())
+ return Option(
+ "--no-binary",
+ dest="format_control",
+ action="callback",
+ callback=_handle_no_binary,
+ type="str",
+ default=format_control,
+ help="Do not use binary packages. Can be supplied multiple times, and "
+ 'each time adds to the existing value. Accepts either ":all:" to '
+ 'disable all binary packages, ":none:" to empty the set (notice '
+ "the colons), or one or more package names with commas between "
+ "them (no colons). Note that some packages are tricky to compile "
+ "and may fail to install when this option is used on them.",
+ )
+
+
+def only_binary() -> Option:
+ format_control = FormatControl(set(), set())
+ return Option(
+ "--only-binary",
+ dest="format_control",
+ action="callback",
+ callback=_handle_only_binary,
+ type="str",
+ default=format_control,
+ help="Do not use source packages. Can be supplied multiple times, and "
+ 'each time adds to the existing value. Accepts either ":all:" to '
+ 'disable all source packages, ":none:" to empty the set, or one '
+ "or more package names with commas between them. Packages "
+ "without binary distributions will fail to install when this "
+ "option is used on them.",
+ )
+
+
+platforms: Callable[..., Option] = partial(
+ Option,
+ "--platform",
+ dest="platforms",
+ metavar="platform",
+ action="append",
+ default=None,
+ help=(
+ "Only use wheels compatible with . Defaults to the "
+ "platform of the running system. Use this option multiple times to "
+ "specify multiple platforms supported by the target interpreter."
+ ),
+)
+
+
+# This was made a separate function for unit-testing purposes.
+def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]:
+ """
+ Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
+
+ :return: A 2-tuple (version_info, error_msg), where `error_msg` is
+ non-None if and only if there was a parsing error.
+ """
+ if not value:
+ # The empty string is the same as not providing a value.
+ return (None, None)
+
+ parts = value.split(".")
+ if len(parts) > 3:
+ return ((), "at most three version parts are allowed")
+
+ if len(parts) == 1:
+ # Then we are in the case of "3" or "37".
+ value = parts[0]
+ if len(value) > 1:
+ parts = [value[0], value[1:]]
+
+ try:
+ version_info = tuple(int(part) for part in parts)
+ except ValueError:
+ return ((), "each version part must be an integer")
+
+ return (version_info, None)
+
+
+def _handle_python_version(
+ option: Option, opt_str: str, value: str, parser: OptionParser
+) -> None:
+ """
+ Handle a provided --python-version value.
+ """
+ version_info, error_msg = _convert_python_version(value)
+ if error_msg is not None:
+ msg = f"invalid --python-version value: {value!r}: {error_msg}"
+ raise_option_error(parser, option=option, msg=msg)
+
+ parser.values.python_version = version_info
+
+
+python_version: Callable[..., Option] = partial(
+ Option,
+ "--python-version",
+ dest="python_version",
+ metavar="python_version",
+ action="callback",
+ callback=_handle_python_version,
+ type="str",
+ default=None,
+ help=dedent(
+ """\
+ The Python interpreter version to use for wheel and "Requires-Python"
+ compatibility checks. Defaults to a version derived from the running
+ interpreter. The version can be specified using up to three dot-separated
+ integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor
+ version can also be given as a string without dots (e.g. "37" for 3.7.0).
+ """
+ ),
+)
+
+
+implementation: Callable[..., Option] = partial(
+ Option,
+ "--implementation",
+ dest="implementation",
+ metavar="implementation",
+ default=None,
+ help=(
+ "Only use wheels compatible with Python "
+ "implementation , e.g. 'pp', 'jy', 'cp', "
+ " or 'ip'. If not specified, then the current "
+ "interpreter implementation is used. Use 'py' to force "
+ "implementation-agnostic wheels."
+ ),
+)
+
+
+abis: Callable[..., Option] = partial(
+ Option,
+ "--abi",
+ dest="abis",
+ metavar="abi",
+ action="append",
+ default=None,
+ help=(
+ "Only use wheels compatible with Python abi , e.g. 'pypy_41'. "
+ "If not specified, then the current interpreter abi tag is used. "
+ "Use this option multiple times to specify multiple abis supported "
+ "by the target interpreter. Generally you will need to specify "
+ "--implementation, --platform, and --python-version when using this "
+ "option."
+ ),
+)
+
+
+def add_target_python_options(cmd_opts: OptionGroup) -> None:
+ cmd_opts.add_option(platforms())
+ cmd_opts.add_option(python_version())
+ cmd_opts.add_option(implementation())
+ cmd_opts.add_option(abis())
+
+
+def make_target_python(options: Values) -> TargetPython:
+ target_python = TargetPython(
+ platforms=options.platforms,
+ py_version_info=options.python_version,
+ abis=options.abis,
+ implementation=options.implementation,
+ )
+
+ return target_python
+
+
+def prefer_binary() -> Option:
+ return Option(
+ "--prefer-binary",
+ dest="prefer_binary",
+ action="store_true",
+ default=False,
+ help=(
+ "Prefer binary packages over source packages, even if the "
+ "source packages are newer."
+ ),
+ )
+
+
+cache_dir: Callable[..., Option] = partial(
+ PipOption,
+ "--cache-dir",
+ dest="cache_dir",
+ default=USER_CACHE_DIR,
+ metavar="dir",
+ type="path",
+ help="Store the cache data in .",
+)
+
+
+def _handle_no_cache_dir(
+ option: Option, opt: str, value: str, parser: OptionParser
+) -> None:
+ """
+ Process a value provided for the --no-cache-dir option.
+
+ This is an optparse.Option callback for the --no-cache-dir option.
+ """
+ # The value argument will be None if --no-cache-dir is passed via the
+ # command-line, since the option doesn't accept arguments. However,
+ # the value can be non-None if the option is triggered e.g. by an
+ # environment variable, like PIP_NO_CACHE_DIR=true.
+ if value is not None:
+ # Then parse the string value to get argument error-checking.
+ try:
+ strtobool(value)
+ except ValueError as exc:
+ raise_option_error(parser, option=option, msg=str(exc))
+
+ # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
+ # converted to 0 (like "false" or "no") caused cache_dir to be disabled
+ # rather than enabled (logic would say the latter). Thus, we disable
+ # the cache directory not just on values that parse to True, but (for
+ # backwards compatibility reasons) also on values that parse to False.
+ # In other words, always set it to False if the option is provided in
+ # some (valid) form.
+ parser.values.cache_dir = False
+
+
+no_cache: Callable[..., Option] = partial(
+ Option,
+ "--no-cache-dir",
+ dest="cache_dir",
+ action="callback",
+ callback=_handle_no_cache_dir,
+ help="Disable the cache.",
+)
+
+no_deps: Callable[..., Option] = partial(
+ Option,
+ "--no-deps",
+ "--no-dependencies",
+ dest="ignore_dependencies",
+ action="store_true",
+ default=False,
+ help="Don't install package dependencies.",
+)
+
+ignore_requires_python: Callable[..., Option] = partial(
+ Option,
+ "--ignore-requires-python",
+ dest="ignore_requires_python",
+ action="store_true",
+ help="Ignore the Requires-Python information.",
+)
+
+no_build_isolation: Callable[..., Option] = partial(
+ Option,
+ "--no-build-isolation",
+ dest="build_isolation",
+ action="store_false",
+ default=True,
+ help="Disable isolation when building a modern source distribution. "
+ "Build dependencies specified by PEP 518 must be already installed "
+ "if this option is used.",
+)
+
+check_build_deps: Callable[..., Option] = partial(
+ Option,
+ "--check-build-dependencies",
+ dest="check_build_deps",
+ action="store_true",
+ default=False,
+ help="Check the build dependencies when PEP517 is used.",
+)
+
+
+def _handle_no_use_pep517(
+ option: Option, opt: str, value: str, parser: OptionParser
+) -> None:
+ """
+ Process a value provided for the --no-use-pep517 option.
+
+ This is an optparse.Option callback for the no_use_pep517 option.
+ """
+ # Since --no-use-pep517 doesn't accept arguments, the value argument
+ # will be None if --no-use-pep517 is passed via the command-line.
+ # However, the value can be non-None if the option is triggered e.g.
+ # by an environment variable, for example "PIP_NO_USE_PEP517=true".
+ if value is not None:
+ msg = """A value was passed for --no-use-pep517,
+ probably using either the PIP_NO_USE_PEP517 environment variable
+ or the "no-use-pep517" config file option. Use an appropriate value
+ of the PIP_USE_PEP517 environment variable or the "use-pep517"
+ config file option instead.
+ """
+ raise_option_error(parser, option=option, msg=msg)
+
+ # If user doesn't wish to use pep517, we check if setuptools and wheel are installed
+ # and raise error if it is not.
+ packages = ("setuptools", "wheel")
+ if not all(importlib.util.find_spec(package) for package in packages):
+ msg = (
+ f"It is not possible to use --no-use-pep517 "
+ f"without {' and '.join(packages)} installed."
+ )
+ raise_option_error(parser, option=option, msg=msg)
+
+ # Otherwise, --no-use-pep517 was passed via the command-line.
+ parser.values.use_pep517 = False
+
+
+use_pep517: Any = partial(
+ Option,
+ "--use-pep517",
+ dest="use_pep517",
+ action="store_true",
+ default=None,
+ help="Use PEP 517 for building source distributions "
+ "(use --no-use-pep517 to force legacy behaviour).",
+)
+
+no_use_pep517: Any = partial(
+ Option,
+ "--no-use-pep517",
+ dest="use_pep517",
+ action="callback",
+ callback=_handle_no_use_pep517,
+ default=None,
+ help=SUPPRESS_HELP,
+)
+
+
+def _handle_config_settings(
+ option: Option, opt_str: str, value: str, parser: OptionParser
+) -> None:
+ key, sep, val = value.partition("=")
+ if sep != "=":
+ parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL")
+ dest = getattr(parser.values, option.dest)
+ if dest is None:
+ dest = {}
+ setattr(parser.values, option.dest, dest)
+ if key in dest:
+ if isinstance(dest[key], list):
+ dest[key].append(val)
+ else:
+ dest[key] = [dest[key], val]
+ else:
+ dest[key] = val
+
+
+config_settings: Callable[..., Option] = partial(
+ Option,
+ "-C",
+ "--config-settings",
+ dest="config_settings",
+ type=str,
+ action="callback",
+ callback=_handle_config_settings,
+ metavar="settings",
+ help="Configuration settings to be passed to the PEP 517 build backend. "
+ "Settings take the form KEY=VALUE. Use multiple --config-settings options "
+ "to pass multiple keys to the backend.",
+)
+
+build_options: Callable[..., Option] = partial(
+ Option,
+ "--build-option",
+ dest="build_options",
+ metavar="options",
+ action="append",
+ help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
+)
+
+global_options: Callable[..., Option] = partial(
+ Option,
+ "--global-option",
+ dest="global_options",
+ action="append",
+ metavar="options",
+ help="Extra global options to be supplied to the setup.py "
+ "call before the install or bdist_wheel command.",
+)
+
+no_clean: Callable[..., Option] = partial(
+ Option,
+ "--no-clean",
+ action="store_true",
+ default=False,
+ help="Don't clean up build directories.",
+)
+
+pre: Callable[..., Option] = partial(
+ Option,
+ "--pre",
+ action="store_true",
+ default=False,
+ help="Include pre-release and development versions. By default, "
+ "pip only finds stable versions.",
+)
+
+disable_pip_version_check: Callable[..., Option] = partial(
+ Option,
+ "--disable-pip-version-check",
+ dest="disable_pip_version_check",
+ action="store_true",
+ default=False,
+ help="Don't periodically check PyPI to determine whether a new version "
+ "of pip is available for download. Implied with --no-index.",
+)
+
+root_user_action: Callable[..., Option] = partial(
+ Option,
+ "--root-user-action",
+ dest="root_user_action",
+ default="warn",
+ choices=["warn", "ignore"],
+ help="Action if pip is run as a root user [warn, ignore] (default: warn)",
+)
+
+
+def _handle_merge_hash(
+ option: Option, opt_str: str, value: str, parser: OptionParser
+) -> None:
+ """Given a value spelled "algo:digest", append the digest to a list
+ pointed to in a dict by the algo name."""
+ if not parser.values.hashes:
+ parser.values.hashes = {}
+ try:
+ algo, digest = value.split(":", 1)
+ except ValueError:
+ parser.error(
+ f"Arguments to {opt_str} must be a hash name "
+ "followed by a value, like --hash=sha256:"
+ "abcde..."
+ )
+ if algo not in STRONG_HASHES:
+ parser.error(
+ "Allowed hash algorithms for {} are {}.".format(
+ opt_str, ", ".join(STRONG_HASHES)
+ )
+ )
+ parser.values.hashes.setdefault(algo, []).append(digest)
+
+
+hash: Callable[..., Option] = partial(
+ Option,
+ "--hash",
+ # Hash values eventually end up in InstallRequirement.hashes due to
+ # __dict__ copying in process_line().
+ dest="hashes",
+ action="callback",
+ callback=_handle_merge_hash,
+ type="string",
+ help="Verify that the package's archive matches this "
+ "hash before installing. Example: --hash=sha256:abcdef...",
+)
+
+
+require_hashes: Callable[..., Option] = partial(
+ Option,
+ "--require-hashes",
+ dest="require_hashes",
+ action="store_true",
+ default=False,
+ help="Require a hash to check each requirement against, for "
+ "repeatable installs. This option is implied when any package in a "
+ "requirements file has a --hash option.",
+)
+
+
+list_path: Callable[..., Option] = partial(
+ PipOption,
+ "--path",
+ dest="path",
+ type="path",
+ action="append",
+ help="Restrict to the specified installation path for listing "
+ "packages (can be used multiple times).",
+)
+
+
+def check_list_path_option(options: Values) -> None:
+ if options.path and (options.user or options.local):
+ raise CommandError("Cannot combine '--path' with '--user' or '--local'")
+
+
+list_exclude: Callable[..., Option] = partial(
+ PipOption,
+ "--exclude",
+ dest="excludes",
+ action="append",
+ metavar="package",
+ type="package_name",
+ help="Exclude specified package from the output",
+)
+
+
+no_python_version_warning: Callable[..., Option] = partial(
+ Option,
+ "--no-python-version-warning",
+ dest="no_python_version_warning",
+ action="store_true",
+ default=False,
+ help="Silence deprecation warnings for upcoming unsupported Pythons.",
+)
+
+
+# Features that are now always on. A warning is printed if they are used.
+ALWAYS_ENABLED_FEATURES = [
+ "truststore", # always on since 24.2
+ "no-binary-enable-wheel-cache", # always on since 23.1
+]
+
+use_new_feature: Callable[..., Option] = partial(
+ Option,
+ "--use-feature",
+ dest="features_enabled",
+ metavar="feature",
+ action="append",
+ default=[],
+ choices=[
+ "fast-deps",
+ ]
+ + ALWAYS_ENABLED_FEATURES,
+ help="Enable new functionality, that may be backward incompatible.",
+)
+
+use_deprecated_feature: Callable[..., Option] = partial(
+ Option,
+ "--use-deprecated",
+ dest="deprecated_features_enabled",
+ metavar="feature",
+ action="append",
+ default=[],
+ choices=[
+ "legacy-resolver",
+ "legacy-certs",
+ ],
+ help=("Enable deprecated functionality, that will be removed in the future."),
+)
+
+
+##########
+# groups #
+##########
+
+general_group: Dict[str, Any] = {
+ "name": "General Options",
+ "options": [
+ help_,
+ debug_mode,
+ isolated_mode,
+ require_virtualenv,
+ python,
+ verbose,
+ version,
+ quiet,
+ log,
+ no_input,
+ keyring_provider,
+ proxy,
+ retries,
+ timeout,
+ exists_action,
+ trusted_host,
+ cert,
+ client_cert,
+ cache_dir,
+ no_cache,
+ disable_pip_version_check,
+ no_color,
+ no_python_version_warning,
+ use_new_feature,
+ use_deprecated_feature,
+ ],
+}
+
+index_group: Dict[str, Any] = {
+ "name": "Package Index Options",
+ "options": [
+ index_url,
+ extra_index_url,
+ no_index,
+ find_links,
+ ],
+}
diff --git a/env/Lib/site-packages/pip/_internal/cli/command_context.py b/env/Lib/site-packages/pip/_internal/cli/command_context.py
new file mode 100644
index 0000000000000000000000000000000000000000..139995ac3f109a82664e4913f7ebc32ecf7617e1
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/command_context.py
@@ -0,0 +1,27 @@
+from contextlib import ExitStack, contextmanager
+from typing import ContextManager, Generator, TypeVar
+
+_T = TypeVar("_T", covariant=True)
+
+
+class CommandContextMixIn:
+ def __init__(self) -> None:
+ super().__init__()
+ self._in_main_context = False
+ self._main_context = ExitStack()
+
+ @contextmanager
+ def main_context(self) -> Generator[None, None, None]:
+ assert not self._in_main_context
+
+ self._in_main_context = True
+ try:
+ with self._main_context:
+ yield
+ finally:
+ self._in_main_context = False
+
+ def enter_context(self, context_provider: ContextManager[_T]) -> _T:
+ assert self._in_main_context
+
+ return self._main_context.enter_context(context_provider)
diff --git a/env/Lib/site-packages/pip/_internal/cli/index_command.py b/env/Lib/site-packages/pip/_internal/cli/index_command.py
new file mode 100644
index 0000000000000000000000000000000000000000..db105d0fef957a3fbd4fc919cbe8f387793f7342
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/index_command.py
@@ -0,0 +1,170 @@
+"""
+Contains command classes which may interact with an index / the network.
+
+Unlike its sister module, req_command, this module still uses lazy imports
+so commands which don't always hit the network (e.g. list w/o --outdated or
+--uptodate) don't need waste time importing PipSession and friends.
+"""
+
+import logging
+import os
+import sys
+from optparse import Values
+from typing import TYPE_CHECKING, List, Optional
+
+from pip._vendor import certifi
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.command_context import CommandContextMixIn
+
+if TYPE_CHECKING:
+ from ssl import SSLContext
+
+ from pip._internal.network.session import PipSession
+
+logger = logging.getLogger(__name__)
+
+
+def _create_truststore_ssl_context() -> Optional["SSLContext"]:
+ if sys.version_info < (3, 10):
+ logger.debug("Disabling truststore because Python version isn't 3.10+")
+ return None
+
+ try:
+ import ssl
+ except ImportError:
+ logger.warning("Disabling truststore since ssl support is missing")
+ return None
+
+ try:
+ from pip._vendor import truststore
+ except ImportError:
+ logger.warning("Disabling truststore because platform isn't supported")
+ return None
+
+ ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ ctx.load_verify_locations(certifi.where())
+ return ctx
+
+
+class SessionCommandMixin(CommandContextMixIn):
+ """
+ A class mixin for command classes needing _build_session().
+ """
+
+ def __init__(self) -> None:
+ super().__init__()
+ self._session: Optional[PipSession] = None
+
+ @classmethod
+ def _get_index_urls(cls, options: Values) -> Optional[List[str]]:
+ """Return a list of index urls from user-provided options."""
+ index_urls = []
+ if not getattr(options, "no_index", False):
+ url = getattr(options, "index_url", None)
+ if url:
+ index_urls.append(url)
+ urls = getattr(options, "extra_index_urls", None)
+ if urls:
+ index_urls.extend(urls)
+ # Return None rather than an empty list
+ return index_urls or None
+
+ def get_default_session(self, options: Values) -> "PipSession":
+ """Get a default-managed session."""
+ if self._session is None:
+ self._session = self.enter_context(self._build_session(options))
+ # there's no type annotation on requests.Session, so it's
+ # automatically ContextManager[Any] and self._session becomes Any,
+ # then https://github.com/python/mypy/issues/7696 kicks in
+ assert self._session is not None
+ return self._session
+
+ def _build_session(
+ self,
+ options: Values,
+ retries: Optional[int] = None,
+ timeout: Optional[int] = None,
+ ) -> "PipSession":
+ from pip._internal.network.session import PipSession
+
+ cache_dir = options.cache_dir
+ assert not cache_dir or os.path.isabs(cache_dir)
+
+ if "legacy-certs" not in options.deprecated_features_enabled:
+ ssl_context = _create_truststore_ssl_context()
+ else:
+ ssl_context = None
+
+ session = PipSession(
+ cache=os.path.join(cache_dir, "http-v2") if cache_dir else None,
+ retries=retries if retries is not None else options.retries,
+ trusted_hosts=options.trusted_hosts,
+ index_urls=self._get_index_urls(options),
+ ssl_context=ssl_context,
+ )
+
+ # Handle custom ca-bundles from the user
+ if options.cert:
+ session.verify = options.cert
+
+ # Handle SSL client certificate
+ if options.client_cert:
+ session.cert = options.client_cert
+
+ # Handle timeouts
+ if options.timeout or timeout:
+ session.timeout = timeout if timeout is not None else options.timeout
+
+ # Handle configured proxies
+ if options.proxy:
+ session.proxies = {
+ "http": options.proxy,
+ "https": options.proxy,
+ }
+ session.trust_env = False
+
+ # Determine if we can prompt the user for authentication or not
+ session.auth.prompting = not options.no_input
+ session.auth.keyring_provider = options.keyring_provider
+
+ return session
+
+
+def _pip_self_version_check(session: "PipSession", options: Values) -> None:
+ from pip._internal.self_outdated_check import pip_self_version_check as check
+
+ check(session, options)
+
+
+class IndexGroupCommand(Command, SessionCommandMixin):
+ """
+ Abstract base class for commands with the index_group options.
+
+ This also corresponds to the commands that permit the pip version check.
+ """
+
+ def handle_pip_version_check(self, options: Values) -> None:
+ """
+ Do the pip version check if not disabled.
+
+ This overrides the default behavior of not doing the check.
+ """
+ # Make sure the index_group options are present.
+ assert hasattr(options, "no_index")
+
+ if options.disable_pip_version_check or options.no_index:
+ return
+
+ try:
+ # Otherwise, check if we're using the latest version of pip available.
+ session = self._build_session(
+ options,
+ retries=0,
+ timeout=min(5, options.timeout),
+ )
+ with session:
+ _pip_self_version_check(session, options)
+ except Exception:
+ logger.warning("There was an error checking the latest version of pip.")
+ logger.debug("See below for error", exc_info=True)
diff --git a/env/Lib/site-packages/pip/_internal/cli/main.py b/env/Lib/site-packages/pip/_internal/cli/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..563ac79c98460809f8f349d254a4f16dbda2f7e0
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/main.py
@@ -0,0 +1,80 @@
+"""Primary application entrypoint.
+"""
+
+import locale
+import logging
+import os
+import sys
+import warnings
+from typing import List, Optional
+
+from pip._internal.cli.autocompletion import autocomplete
+from pip._internal.cli.main_parser import parse_command
+from pip._internal.commands import create_command
+from pip._internal.exceptions import PipError
+from pip._internal.utils import deprecation
+
+logger = logging.getLogger(__name__)
+
+
+# Do not import and use main() directly! Using it directly is actively
+# discouraged by pip's maintainers. The name, location and behavior of
+# this function is subject to change, so calling it directly is not
+# portable across different pip versions.
+
+# In addition, running pip in-process is unsupported and unsafe. This is
+# elaborated in detail at
+# https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program.
+# That document also provides suggestions that should work for nearly
+# all users that are considering importing and using main() directly.
+
+# However, we know that certain users will still want to invoke pip
+# in-process. If you understand and accept the implications of using pip
+# in an unsupported manner, the best approach is to use runpy to avoid
+# depending on the exact location of this entry point.
+
+# The following example shows how to use runpy to invoke pip in that
+# case:
+#
+# sys.argv = ["pip", your, args, here]
+# runpy.run_module("pip", run_name="__main__")
+#
+# Note that this will exit the process after running, unlike a direct
+# call to main. As it is not safe to do any processing after calling
+# main, this should not be an issue in practice.
+
+
+def main(args: Optional[List[str]] = None) -> int:
+ if args is None:
+ args = sys.argv[1:]
+
+ # Suppress the pkg_resources deprecation warning
+ # Note - we use a module of .*pkg_resources to cover
+ # the normal case (pip._vendor.pkg_resources) and the
+ # devendored case (a bare pkg_resources)
+ warnings.filterwarnings(
+ action="ignore", category=DeprecationWarning, module=".*pkg_resources"
+ )
+
+ # Configure our deprecation warnings to be sent through loggers
+ deprecation.install_warning_logger()
+
+ autocomplete()
+
+ try:
+ cmd_name, cmd_args = parse_command(args)
+ except PipError as exc:
+ sys.stderr.write(f"ERROR: {exc}")
+ sys.stderr.write(os.linesep)
+ sys.exit(1)
+
+ # Needed for locale.getpreferredencoding(False) to work
+ # in pip._internal.utils.encoding.auto_decode
+ try:
+ locale.setlocale(locale.LC_ALL, "")
+ except locale.Error as e:
+ # setlocale can apparently crash if locale are uninitialized
+ logger.debug("Ignoring error %s when setting locale", e)
+ command = create_command(cmd_name, isolated=("--isolated" in cmd_args))
+
+ return command.main(cmd_args)
diff --git a/env/Lib/site-packages/pip/_internal/cli/main_parser.py b/env/Lib/site-packages/pip/_internal/cli/main_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ade356b9c2f3e375bf598635627870f248c0cc3
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/main_parser.py
@@ -0,0 +1,134 @@
+"""A single place for constructing and exposing the main parser
+"""
+
+import os
+import subprocess
+import sys
+from typing import List, Optional, Tuple
+
+from pip._internal.build_env import get_runnable_pip
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
+from pip._internal.commands import commands_dict, get_similar_commands
+from pip._internal.exceptions import CommandError
+from pip._internal.utils.misc import get_pip_version, get_prog
+
+__all__ = ["create_main_parser", "parse_command"]
+
+
+def create_main_parser() -> ConfigOptionParser:
+ """Creates and returns the main parser for pip's CLI"""
+
+ parser = ConfigOptionParser(
+ usage="\n%prog [options]",
+ add_help_option=False,
+ formatter=UpdatingDefaultsHelpFormatter(),
+ name="global",
+ prog=get_prog(),
+ )
+ parser.disable_interspersed_args()
+
+ parser.version = get_pip_version()
+
+ # add the general options
+ gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
+ parser.add_option_group(gen_opts)
+
+ # so the help formatter knows
+ parser.main = True # type: ignore
+
+ # create command listing for description
+ description = [""] + [
+ f"{name:27} {command_info.summary}"
+ for name, command_info in commands_dict.items()
+ ]
+ parser.description = "\n".join(description)
+
+ return parser
+
+
+def identify_python_interpreter(python: str) -> Optional[str]:
+ # If the named file exists, use it.
+ # If it's a directory, assume it's a virtual environment and
+ # look for the environment's Python executable.
+ if os.path.exists(python):
+ if os.path.isdir(python):
+ # bin/python for Unix, Scripts/python.exe for Windows
+ # Try both in case of odd cases like cygwin.
+ for exe in ("bin/python", "Scripts/python.exe"):
+ py = os.path.join(python, exe)
+ if os.path.exists(py):
+ return py
+ else:
+ return python
+
+ # Could not find the interpreter specified
+ return None
+
+
+def parse_command(args: List[str]) -> Tuple[str, List[str]]:
+ parser = create_main_parser()
+
+ # Note: parser calls disable_interspersed_args(), so the result of this
+ # call is to split the initial args into the general options before the
+ # subcommand and everything else.
+ # For example:
+ # args: ['--timeout=5', 'install', '--user', 'INITools']
+ # general_options: ['--timeout==5']
+ # args_else: ['install', '--user', 'INITools']
+ general_options, args_else = parser.parse_args(args)
+
+ # --python
+ if general_options.python and "_PIP_RUNNING_IN_SUBPROCESS" not in os.environ:
+ # Re-invoke pip using the specified Python interpreter
+ interpreter = identify_python_interpreter(general_options.python)
+ if interpreter is None:
+ raise CommandError(
+ f"Could not locate Python interpreter {general_options.python}"
+ )
+
+ pip_cmd = [
+ interpreter,
+ get_runnable_pip(),
+ ]
+ pip_cmd.extend(args)
+
+ # Set a flag so the child doesn't re-invoke itself, causing
+ # an infinite loop.
+ os.environ["_PIP_RUNNING_IN_SUBPROCESS"] = "1"
+ returncode = 0
+ try:
+ proc = subprocess.run(pip_cmd)
+ returncode = proc.returncode
+ except (subprocess.SubprocessError, OSError) as exc:
+ raise CommandError(f"Failed to run pip under {interpreter}: {exc}")
+ sys.exit(returncode)
+
+ # --version
+ if general_options.version:
+ sys.stdout.write(parser.version)
+ sys.stdout.write(os.linesep)
+ sys.exit()
+
+ # pip || pip help -> print_help()
+ if not args_else or (args_else[0] == "help" and len(args_else) == 1):
+ parser.print_help()
+ sys.exit()
+
+ # the subcommand name
+ cmd_name = args_else[0]
+
+ if cmd_name not in commands_dict:
+ guess = get_similar_commands(cmd_name)
+
+ msg = [f'unknown command "{cmd_name}"']
+ if guess:
+ msg.append(f'maybe you meant "{guess}"')
+
+ raise CommandError(" - ".join(msg))
+
+ # all the args without the subcommand
+ cmd_args = args[:]
+ cmd_args.remove(cmd_name)
+
+ return cmd_name, cmd_args
diff --git a/env/Lib/site-packages/pip/_internal/cli/parser.py b/env/Lib/site-packages/pip/_internal/cli/parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc4aca032d4adddbb7b9d8b0c981187aa11e40fc
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/parser.py
@@ -0,0 +1,294 @@
+"""Base option parser setup"""
+
+import logging
+import optparse
+import shutil
+import sys
+import textwrap
+from contextlib import suppress
+from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple
+
+from pip._internal.cli.status_codes import UNKNOWN_ERROR
+from pip._internal.configuration import Configuration, ConfigurationError
+from pip._internal.utils.misc import redact_auth_from_url, strtobool
+
+logger = logging.getLogger(__name__)
+
+
+class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
+ """A prettier/less verbose help formatter for optparse."""
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ # help position must be aligned with __init__.parseopts.description
+ kwargs["max_help_position"] = 30
+ kwargs["indent_increment"] = 1
+ kwargs["width"] = shutil.get_terminal_size()[0] - 2
+ super().__init__(*args, **kwargs)
+
+ def format_option_strings(self, option: optparse.Option) -> str:
+ return self._format_option_strings(option)
+
+ def _format_option_strings(
+ self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "
+ ) -> str:
+ """
+ Return a comma-separated list of option strings and metavars.
+
+ :param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
+ :param mvarfmt: metavar format string
+ :param optsep: separator
+ """
+ opts = []
+
+ if option._short_opts:
+ opts.append(option._short_opts[0])
+ if option._long_opts:
+ opts.append(option._long_opts[0])
+ if len(opts) > 1:
+ opts.insert(1, optsep)
+
+ if option.takes_value():
+ assert option.dest is not None
+ metavar = option.metavar or option.dest.lower()
+ opts.append(mvarfmt.format(metavar.lower()))
+
+ return "".join(opts)
+
+ def format_heading(self, heading: str) -> str:
+ if heading == "Options":
+ return ""
+ return heading + ":\n"
+
+ def format_usage(self, usage: str) -> str:
+ """
+ Ensure there is only one newline between usage and the first heading
+ if there is no description.
+ """
+ msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
+ return msg
+
+ def format_description(self, description: Optional[str]) -> str:
+ # leave full control over description to us
+ if description:
+ if hasattr(self.parser, "main"):
+ label = "Commands"
+ else:
+ label = "Description"
+ # some doc strings have initial newlines, some don't
+ description = description.lstrip("\n")
+ # some doc strings have final newlines and spaces, some don't
+ description = description.rstrip()
+ # dedent, then reindent
+ description = self.indent_lines(textwrap.dedent(description), " ")
+ description = f"{label}:\n{description}\n"
+ return description
+ else:
+ return ""
+
+ def format_epilog(self, epilog: Optional[str]) -> str:
+ # leave full control over epilog to us
+ if epilog:
+ return epilog
+ else:
+ return ""
+
+ def indent_lines(self, text: str, indent: str) -> str:
+ new_lines = [indent + line for line in text.split("\n")]
+ return "\n".join(new_lines)
+
+
+class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
+ """Custom help formatter for use in ConfigOptionParser.
+
+ This is updates the defaults before expanding them, allowing
+ them to show up correctly in the help listing.
+
+ Also redact auth from url type options
+ """
+
+ def expand_default(self, option: optparse.Option) -> str:
+ default_values = None
+ if self.parser is not None:
+ assert isinstance(self.parser, ConfigOptionParser)
+ self.parser._update_defaults(self.parser.defaults)
+ assert option.dest is not None
+ default_values = self.parser.defaults.get(option.dest)
+ help_text = super().expand_default(option)
+
+ if default_values and option.metavar == "URL":
+ if isinstance(default_values, str):
+ default_values = [default_values]
+
+ # If its not a list, we should abort and just return the help text
+ if not isinstance(default_values, list):
+ default_values = []
+
+ for val in default_values:
+ help_text = help_text.replace(val, redact_auth_from_url(val))
+
+ return help_text
+
+
+class CustomOptionParser(optparse.OptionParser):
+ def insert_option_group(
+ self, idx: int, *args: Any, **kwargs: Any
+ ) -> optparse.OptionGroup:
+ """Insert an OptionGroup at a given position."""
+ group = self.add_option_group(*args, **kwargs)
+
+ self.option_groups.pop()
+ self.option_groups.insert(idx, group)
+
+ return group
+
+ @property
+ def option_list_all(self) -> List[optparse.Option]:
+ """Get a list of all options, including those in option groups."""
+ res = self.option_list[:]
+ for i in self.option_groups:
+ res.extend(i.option_list)
+
+ return res
+
+
+class ConfigOptionParser(CustomOptionParser):
+ """Custom option parser which updates its defaults by checking the
+ configuration files and environmental variables"""
+
+ def __init__(
+ self,
+ *args: Any,
+ name: str,
+ isolated: bool = False,
+ **kwargs: Any,
+ ) -> None:
+ self.name = name
+ self.config = Configuration(isolated)
+
+ assert self.name
+ super().__init__(*args, **kwargs)
+
+ def check_default(self, option: optparse.Option, key: str, val: Any) -> Any:
+ try:
+ return option.check_value(key, val)
+ except optparse.OptionValueError as exc:
+ print(f"An error occurred during configuration: {exc}")
+ sys.exit(3)
+
+ def _get_ordered_configuration_items(
+ self,
+ ) -> Generator[Tuple[str, Any], None, None]:
+ # Configuration gives keys in an unordered manner. Order them.
+ override_order = ["global", self.name, ":env:"]
+
+ # Pool the options into different groups
+ section_items: Dict[str, List[Tuple[str, Any]]] = {
+ name: [] for name in override_order
+ }
+ for section_key, val in self.config.items():
+ # ignore empty values
+ if not val:
+ logger.debug(
+ "Ignoring configuration key '%s' as it's value is empty.",
+ section_key,
+ )
+ continue
+
+ section, key = section_key.split(".", 1)
+ if section in override_order:
+ section_items[section].append((key, val))
+
+ # Yield each group in their override order
+ for section in override_order:
+ for key, val in section_items[section]:
+ yield key, val
+
+ def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
+ """Updates the given defaults with values from the config files and
+ the environ. Does a little special handling for certain types of
+ options (lists)."""
+
+ # Accumulate complex default state.
+ self.values = optparse.Values(self.defaults)
+ late_eval = set()
+ # Then set the options with those values
+ for key, val in self._get_ordered_configuration_items():
+ # '--' because configuration supports only long names
+ option = self.get_option("--" + key)
+
+ # Ignore options not present in this parser. E.g. non-globals put
+ # in [global] by users that want them to apply to all applicable
+ # commands.
+ if option is None:
+ continue
+
+ assert option.dest is not None
+
+ if option.action in ("store_true", "store_false"):
+ try:
+ val = strtobool(val)
+ except ValueError:
+ self.error(
+ f"{val} is not a valid value for {key} option, "
+ "please specify a boolean value like yes/no, "
+ "true/false or 1/0 instead."
+ )
+ elif option.action == "count":
+ with suppress(ValueError):
+ val = strtobool(val)
+ with suppress(ValueError):
+ val = int(val)
+ if not isinstance(val, int) or val < 0:
+ self.error(
+ f"{val} is not a valid value for {key} option, "
+ "please instead specify either a non-negative integer "
+ "or a boolean value like yes/no or false/true "
+ "which is equivalent to 1/0."
+ )
+ elif option.action == "append":
+ val = val.split()
+ val = [self.check_default(option, key, v) for v in val]
+ elif option.action == "callback":
+ assert option.callback is not None
+ late_eval.add(option.dest)
+ opt_str = option.get_opt_string()
+ val = option.convert_value(opt_str, val)
+ # From take_action
+ args = option.callback_args or ()
+ kwargs = option.callback_kwargs or {}
+ option.callback(option, opt_str, val, self, *args, **kwargs)
+ else:
+ val = self.check_default(option, key, val)
+
+ defaults[option.dest] = val
+
+ for key in late_eval:
+ defaults[key] = getattr(self.values, key)
+ self.values = None
+ return defaults
+
+ def get_default_values(self) -> optparse.Values:
+ """Overriding to make updating the defaults after instantiation of
+ the option parser possible, _update_defaults() does the dirty work."""
+ if not self.process_default_values:
+ # Old, pre-Optik 1.5 behaviour.
+ return optparse.Values(self.defaults)
+
+ # Load the configuration, or error out in case of an error
+ try:
+ self.config.load()
+ except ConfigurationError as err:
+ self.exit(UNKNOWN_ERROR, str(err))
+
+ defaults = self._update_defaults(self.defaults.copy()) # ours
+ for option in self._get_all_options():
+ assert option.dest is not None
+ default = defaults.get(option.dest)
+ if isinstance(default, str):
+ opt_str = option.get_opt_string()
+ defaults[option.dest] = option.check_value(opt_str, default)
+ return optparse.Values(defaults)
+
+ def error(self, msg: str) -> NoReturn:
+ self.print_usage(sys.stderr)
+ self.exit(UNKNOWN_ERROR, f"{msg}\n")
diff --git a/env/Lib/site-packages/pip/_internal/cli/progress_bars.py b/env/Lib/site-packages/pip/_internal/cli/progress_bars.py
new file mode 100644
index 0000000000000000000000000000000000000000..1236180c08691f2bd6050f1cb0893359ce58397f
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/progress_bars.py
@@ -0,0 +1,94 @@
+import functools
+import sys
+from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple
+
+from pip._vendor.rich.progress import (
+ BarColumn,
+ DownloadColumn,
+ FileSizeColumn,
+ Progress,
+ ProgressColumn,
+ SpinnerColumn,
+ TextColumn,
+ TimeElapsedColumn,
+ TimeRemainingColumn,
+ TransferSpeedColumn,
+)
+
+from pip._internal.cli.spinners import RateLimiter
+from pip._internal.utils.logging import get_indentation
+
+DownloadProgressRenderer = Callable[[Iterable[bytes]], Iterator[bytes]]
+
+
+def _rich_progress_bar(
+ iterable: Iterable[bytes],
+ *,
+ bar_type: str,
+ size: Optional[int],
+) -> Generator[bytes, None, None]:
+ assert bar_type == "on", "This should only be used in the default mode."
+
+ if not size:
+ total = float("inf")
+ columns: Tuple[ProgressColumn, ...] = (
+ TextColumn("[progress.description]{task.description}"),
+ SpinnerColumn("line", speed=1.5),
+ FileSizeColumn(),
+ TransferSpeedColumn(),
+ TimeElapsedColumn(),
+ )
+ else:
+ total = size
+ columns = (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ DownloadColumn(),
+ TransferSpeedColumn(),
+ TextColumn("eta"),
+ TimeRemainingColumn(),
+ )
+
+ progress = Progress(*columns, refresh_per_second=5)
+ task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
+ with progress:
+ for chunk in iterable:
+ yield chunk
+ progress.update(task_id, advance=len(chunk))
+
+
+def _raw_progress_bar(
+ iterable: Iterable[bytes],
+ *,
+ size: Optional[int],
+) -> Generator[bytes, None, None]:
+ def write_progress(current: int, total: int) -> None:
+ sys.stdout.write("Progress %d of %d\n" % (current, total))
+ sys.stdout.flush()
+
+ current = 0
+ total = size or 0
+ rate_limiter = RateLimiter(0.25)
+
+ write_progress(current, total)
+ for chunk in iterable:
+ current += len(chunk)
+ if rate_limiter.ready() or current == total:
+ write_progress(current, total)
+ rate_limiter.reset()
+ yield chunk
+
+
+def get_download_progress_renderer(
+ *, bar_type: str, size: Optional[int] = None
+) -> DownloadProgressRenderer:
+ """Get an object that can be used to render the download progress.
+
+ Returns a callable, that takes an iterable to "wrap".
+ """
+ if bar_type == "on":
+ return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
+ elif bar_type == "raw":
+ return functools.partial(_raw_progress_bar, size=size)
+ else:
+ return iter # no-op, when passed an iterator
diff --git a/env/Lib/site-packages/pip/_internal/cli/req_command.py b/env/Lib/site-packages/pip/_internal/cli/req_command.py
new file mode 100644
index 0000000000000000000000000000000000000000..92900f94ff4cb4b43126ca92964655fcb9016f50
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/req_command.py
@@ -0,0 +1,329 @@
+"""Contains the RequirementCommand base class.
+
+This class is in a separate module so the commands that do not always
+need PackageFinder capability don't unnecessarily import the
+PackageFinder machinery and all its vendored dependencies, etc.
+"""
+
+import logging
+from functools import partial
+from optparse import Values
+from typing import Any, List, Optional, Tuple
+
+from pip._internal.cache import WheelCache
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.index_command import IndexGroupCommand
+from pip._internal.cli.index_command import SessionCommandMixin as SessionCommandMixin
+from pip._internal.exceptions import CommandError, PreviousBuildDirError
+from pip._internal.index.collector import LinkCollector
+from pip._internal.index.package_finder import PackageFinder
+from pip._internal.models.selection_prefs import SelectionPreferences
+from pip._internal.models.target_python import TargetPython
+from pip._internal.network.session import PipSession
+from pip._internal.operations.build.build_tracker import BuildTracker
+from pip._internal.operations.prepare import RequirementPreparer
+from pip._internal.req.constructors import (
+ install_req_from_editable,
+ install_req_from_line,
+ install_req_from_parsed_requirement,
+ install_req_from_req_string,
+)
+from pip._internal.req.req_file import parse_requirements
+from pip._internal.req.req_install import InstallRequirement
+from pip._internal.resolution.base import BaseResolver
+from pip._internal.utils.temp_dir import (
+ TempDirectory,
+ TempDirectoryTypeRegistry,
+ tempdir_kinds,
+)
+
+logger = logging.getLogger(__name__)
+
+
+KEEPABLE_TEMPDIR_TYPES = [
+ tempdir_kinds.BUILD_ENV,
+ tempdir_kinds.EPHEM_WHEEL_CACHE,
+ tempdir_kinds.REQ_BUILD,
+]
+
+
+def with_cleanup(func: Any) -> Any:
+ """Decorator for common logic related to managing temporary
+ directories.
+ """
+
+ def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None:
+ for t in KEEPABLE_TEMPDIR_TYPES:
+ registry.set_delete(t, False)
+
+ def wrapper(
+ self: RequirementCommand, options: Values, args: List[Any]
+ ) -> Optional[int]:
+ assert self.tempdir_registry is not None
+ if options.no_clean:
+ configure_tempdir_registry(self.tempdir_registry)
+
+ try:
+ return func(self, options, args)
+ except PreviousBuildDirError:
+ # This kind of conflict can occur when the user passes an explicit
+ # build directory with a pre-existing folder. In that case we do
+ # not want to accidentally remove it.
+ configure_tempdir_registry(self.tempdir_registry)
+ raise
+
+ return wrapper
+
+
+class RequirementCommand(IndexGroupCommand):
+ def __init__(self, *args: Any, **kw: Any) -> None:
+ super().__init__(*args, **kw)
+
+ self.cmd_opts.add_option(cmdoptions.no_clean())
+
+ @staticmethod
+ def determine_resolver_variant(options: Values) -> str:
+ """Determines which resolver should be used, based on the given options."""
+ if "legacy-resolver" in options.deprecated_features_enabled:
+ return "legacy"
+
+ return "resolvelib"
+
+ @classmethod
+ def make_requirement_preparer(
+ cls,
+ temp_build_dir: TempDirectory,
+ options: Values,
+ build_tracker: BuildTracker,
+ session: PipSession,
+ finder: PackageFinder,
+ use_user_site: bool,
+ download_dir: Optional[str] = None,
+ verbosity: int = 0,
+ ) -> RequirementPreparer:
+ """
+ Create a RequirementPreparer instance for the given parameters.
+ """
+ temp_build_dir_path = temp_build_dir.path
+ assert temp_build_dir_path is not None
+ legacy_resolver = False
+
+ resolver_variant = cls.determine_resolver_variant(options)
+ if resolver_variant == "resolvelib":
+ lazy_wheel = "fast-deps" in options.features_enabled
+ if lazy_wheel:
+ logger.warning(
+ "pip is using lazily downloaded wheels using HTTP "
+ "range requests to obtain dependency information. "
+ "This experimental feature is enabled through "
+ "--use-feature=fast-deps and it is not ready for "
+ "production."
+ )
+ else:
+ legacy_resolver = True
+ lazy_wheel = False
+ if "fast-deps" in options.features_enabled:
+ logger.warning(
+ "fast-deps has no effect when used with the legacy resolver."
+ )
+
+ return RequirementPreparer(
+ build_dir=temp_build_dir_path,
+ src_dir=options.src_dir,
+ download_dir=download_dir,
+ build_isolation=options.build_isolation,
+ check_build_deps=options.check_build_deps,
+ build_tracker=build_tracker,
+ session=session,
+ progress_bar=options.progress_bar,
+ finder=finder,
+ require_hashes=options.require_hashes,
+ use_user_site=use_user_site,
+ lazy_wheel=lazy_wheel,
+ verbosity=verbosity,
+ legacy_resolver=legacy_resolver,
+ )
+
+ @classmethod
+ def make_resolver(
+ cls,
+ preparer: RequirementPreparer,
+ finder: PackageFinder,
+ options: Values,
+ wheel_cache: Optional[WheelCache] = None,
+ use_user_site: bool = False,
+ ignore_installed: bool = True,
+ ignore_requires_python: bool = False,
+ force_reinstall: bool = False,
+ upgrade_strategy: str = "to-satisfy-only",
+ use_pep517: Optional[bool] = None,
+ py_version_info: Optional[Tuple[int, ...]] = None,
+ ) -> BaseResolver:
+ """
+ Create a Resolver instance for the given parameters.
+ """
+ make_install_req = partial(
+ install_req_from_req_string,
+ isolated=options.isolated_mode,
+ use_pep517=use_pep517,
+ )
+ resolver_variant = cls.determine_resolver_variant(options)
+ # The long import name and duplicated invocation is needed to convince
+ # Mypy into correctly typechecking. Otherwise it would complain the
+ # "Resolver" class being redefined.
+ if resolver_variant == "resolvelib":
+ import pip._internal.resolution.resolvelib.resolver
+
+ return pip._internal.resolution.resolvelib.resolver.Resolver(
+ preparer=preparer,
+ finder=finder,
+ wheel_cache=wheel_cache,
+ make_install_req=make_install_req,
+ use_user_site=use_user_site,
+ ignore_dependencies=options.ignore_dependencies,
+ ignore_installed=ignore_installed,
+ ignore_requires_python=ignore_requires_python,
+ force_reinstall=force_reinstall,
+ upgrade_strategy=upgrade_strategy,
+ py_version_info=py_version_info,
+ )
+ import pip._internal.resolution.legacy.resolver
+
+ return pip._internal.resolution.legacy.resolver.Resolver(
+ preparer=preparer,
+ finder=finder,
+ wheel_cache=wheel_cache,
+ make_install_req=make_install_req,
+ use_user_site=use_user_site,
+ ignore_dependencies=options.ignore_dependencies,
+ ignore_installed=ignore_installed,
+ ignore_requires_python=ignore_requires_python,
+ force_reinstall=force_reinstall,
+ upgrade_strategy=upgrade_strategy,
+ py_version_info=py_version_info,
+ )
+
+ def get_requirements(
+ self,
+ args: List[str],
+ options: Values,
+ finder: PackageFinder,
+ session: PipSession,
+ ) -> List[InstallRequirement]:
+ """
+ Parse command-line arguments into the corresponding requirements.
+ """
+ requirements: List[InstallRequirement] = []
+ for filename in options.constraints:
+ for parsed_req in parse_requirements(
+ filename,
+ constraint=True,
+ finder=finder,
+ options=options,
+ session=session,
+ ):
+ req_to_add = install_req_from_parsed_requirement(
+ parsed_req,
+ isolated=options.isolated_mode,
+ user_supplied=False,
+ )
+ requirements.append(req_to_add)
+
+ for req in args:
+ req_to_add = install_req_from_line(
+ req,
+ comes_from=None,
+ isolated=options.isolated_mode,
+ use_pep517=options.use_pep517,
+ user_supplied=True,
+ config_settings=getattr(options, "config_settings", None),
+ )
+ requirements.append(req_to_add)
+
+ for req in options.editables:
+ req_to_add = install_req_from_editable(
+ req,
+ user_supplied=True,
+ isolated=options.isolated_mode,
+ use_pep517=options.use_pep517,
+ config_settings=getattr(options, "config_settings", None),
+ )
+ requirements.append(req_to_add)
+
+ # NOTE: options.require_hashes may be set if --require-hashes is True
+ for filename in options.requirements:
+ for parsed_req in parse_requirements(
+ filename, finder=finder, options=options, session=session
+ ):
+ req_to_add = install_req_from_parsed_requirement(
+ parsed_req,
+ isolated=options.isolated_mode,
+ use_pep517=options.use_pep517,
+ user_supplied=True,
+ config_settings=(
+ parsed_req.options.get("config_settings")
+ if parsed_req.options
+ else None
+ ),
+ )
+ requirements.append(req_to_add)
+
+ # If any requirement has hash options, enable hash checking.
+ if any(req.has_hash_options for req in requirements):
+ options.require_hashes = True
+
+ if not (args or options.editables or options.requirements):
+ opts = {"name": self.name}
+ if options.find_links:
+ raise CommandError(
+ "You must give at least one requirement to {name} "
+ '(maybe you meant "pip {name} {links}"?)'.format(
+ **dict(opts, links=" ".join(options.find_links))
+ )
+ )
+ else:
+ raise CommandError(
+ "You must give at least one requirement to {name} "
+ '(see "pip help {name}")'.format(**opts)
+ )
+
+ return requirements
+
+ @staticmethod
+ def trace_basic_info(finder: PackageFinder) -> None:
+ """
+ Trace basic information about the provided objects.
+ """
+ # Display where finder is looking for packages
+ search_scope = finder.search_scope
+ locations = search_scope.get_formatted_locations()
+ if locations:
+ logger.info(locations)
+
+ def _build_package_finder(
+ self,
+ options: Values,
+ session: PipSession,
+ target_python: Optional[TargetPython] = None,
+ ignore_requires_python: Optional[bool] = None,
+ ) -> PackageFinder:
+ """
+ Create a package finder appropriate to this requirement command.
+
+ :param ignore_requires_python: Whether to ignore incompatible
+ "Requires-Python" values in links. Defaults to False.
+ """
+ link_collector = LinkCollector.create(session, options=options)
+ selection_prefs = SelectionPreferences(
+ allow_yanked=True,
+ format_control=options.format_control,
+ allow_all_prereleases=options.pre,
+ prefer_binary=options.prefer_binary,
+ ignore_requires_python=ignore_requires_python,
+ )
+
+ return PackageFinder.create(
+ link_collector=link_collector,
+ selection_prefs=selection_prefs,
+ target_python=target_python,
+ )
diff --git a/env/Lib/site-packages/pip/_internal/cli/spinners.py b/env/Lib/site-packages/pip/_internal/cli/spinners.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf2b976f377c2656afb3d84add8d30b0fc280c03
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/spinners.py
@@ -0,0 +1,159 @@
+import contextlib
+import itertools
+import logging
+import sys
+import time
+from typing import IO, Generator, Optional
+
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.logging import get_indentation
+
+logger = logging.getLogger(__name__)
+
+
+class SpinnerInterface:
+ def spin(self) -> None:
+ raise NotImplementedError()
+
+ def finish(self, final_status: str) -> None:
+ raise NotImplementedError()
+
+
+class InteractiveSpinner(SpinnerInterface):
+ def __init__(
+ self,
+ message: str,
+ file: Optional[IO[str]] = None,
+ spin_chars: str = "-\\|/",
+ # Empirically, 8 updates/second looks nice
+ min_update_interval_seconds: float = 0.125,
+ ):
+ self._message = message
+ if file is None:
+ file = sys.stdout
+ self._file = file
+ self._rate_limiter = RateLimiter(min_update_interval_seconds)
+ self._finished = False
+
+ self._spin_cycle = itertools.cycle(spin_chars)
+
+ self._file.write(" " * get_indentation() + self._message + " ... ")
+ self._width = 0
+
+ def _write(self, status: str) -> None:
+ assert not self._finished
+ # Erase what we wrote before by backspacing to the beginning, writing
+ # spaces to overwrite the old text, and then backspacing again
+ backup = "\b" * self._width
+ self._file.write(backup + " " * self._width + backup)
+ # Now we have a blank slate to add our status
+ self._file.write(status)
+ self._width = len(status)
+ self._file.flush()
+ self._rate_limiter.reset()
+
+ def spin(self) -> None:
+ if self._finished:
+ return
+ if not self._rate_limiter.ready():
+ return
+ self._write(next(self._spin_cycle))
+
+ def finish(self, final_status: str) -> None:
+ if self._finished:
+ return
+ self._write(final_status)
+ self._file.write("\n")
+ self._file.flush()
+ self._finished = True
+
+
+# Used for dumb terminals, non-interactive installs (no tty), etc.
+# We still print updates occasionally (once every 60 seconds by default) to
+# act as a keep-alive for systems like Travis-CI that take lack-of-output as
+# an indication that a task has frozen.
+class NonInteractiveSpinner(SpinnerInterface):
+ def __init__(self, message: str, min_update_interval_seconds: float = 60.0) -> None:
+ self._message = message
+ self._finished = False
+ self._rate_limiter = RateLimiter(min_update_interval_seconds)
+ self._update("started")
+
+ def _update(self, status: str) -> None:
+ assert not self._finished
+ self._rate_limiter.reset()
+ logger.info("%s: %s", self._message, status)
+
+ def spin(self) -> None:
+ if self._finished:
+ return
+ if not self._rate_limiter.ready():
+ return
+ self._update("still running...")
+
+ def finish(self, final_status: str) -> None:
+ if self._finished:
+ return
+ self._update(f"finished with status '{final_status}'")
+ self._finished = True
+
+
+class RateLimiter:
+ def __init__(self, min_update_interval_seconds: float) -> None:
+ self._min_update_interval_seconds = min_update_interval_seconds
+ self._last_update: float = 0
+
+ def ready(self) -> bool:
+ now = time.time()
+ delta = now - self._last_update
+ return delta >= self._min_update_interval_seconds
+
+ def reset(self) -> None:
+ self._last_update = time.time()
+
+
+@contextlib.contextmanager
+def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
+ # Interactive spinner goes directly to sys.stdout rather than being routed
+ # through the logging system, but it acts like it has level INFO,
+ # i.e. it's only displayed if we're at level INFO or better.
+ # Non-interactive spinner goes through the logging system, so it is always
+ # in sync with logging configuration.
+ if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO:
+ spinner: SpinnerInterface = InteractiveSpinner(message)
+ else:
+ spinner = NonInteractiveSpinner(message)
+ try:
+ with hidden_cursor(sys.stdout):
+ yield spinner
+ except KeyboardInterrupt:
+ spinner.finish("canceled")
+ raise
+ except Exception:
+ spinner.finish("error")
+ raise
+ else:
+ spinner.finish("done")
+
+
+HIDE_CURSOR = "\x1b[?25l"
+SHOW_CURSOR = "\x1b[?25h"
+
+
+@contextlib.contextmanager
+def hidden_cursor(file: IO[str]) -> Generator[None, None, None]:
+ # The Windows terminal does not support the hide/show cursor ANSI codes,
+ # even via colorama. So don't even try.
+ if WINDOWS:
+ yield
+ # We don't want to clutter the output with control characters if we're
+ # writing to a file, or if the user is running with --quiet.
+ # See https://github.com/pypa/pip/issues/3418
+ elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO:
+ yield
+ else:
+ file.write(HIDE_CURSOR)
+ try:
+ yield
+ finally:
+ file.write(SHOW_CURSOR)
diff --git a/env/Lib/site-packages/pip/_internal/cli/status_codes.py b/env/Lib/site-packages/pip/_internal/cli/status_codes.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e29502cddfa9a9887a93399ab4193fb75dfe605
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/cli/status_codes.py
@@ -0,0 +1,6 @@
+SUCCESS = 0
+ERROR = 1
+UNKNOWN_ERROR = 2
+VIRTUALENV_NOT_FOUND = 3
+PREVIOUS_BUILD_DIR_ERROR = 4
+NO_MATCHES_FOUND = 23
diff --git a/env/Lib/site-packages/pip/_internal/commands/__init__.py b/env/Lib/site-packages/pip/_internal/commands/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..858a41014169b8f0eb1b905fa3bb69c753a1bda5
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/__init__.py
@@ -0,0 +1,132 @@
+"""
+Package containing all pip commands
+"""
+
+import importlib
+from collections import namedtuple
+from typing import Any, Dict, Optional
+
+from pip._internal.cli.base_command import Command
+
+CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
+
+# This dictionary does a bunch of heavy lifting for help output:
+# - Enables avoiding additional (costly) imports for presenting `--help`.
+# - The ordering matters for help display.
+#
+# Even though the module path starts with the same "pip._internal.commands"
+# prefix, the full path makes testing easier (specifically when modifying
+# `commands_dict` in test setup / teardown).
+commands_dict: Dict[str, CommandInfo] = {
+ "install": CommandInfo(
+ "pip._internal.commands.install",
+ "InstallCommand",
+ "Install packages.",
+ ),
+ "download": CommandInfo(
+ "pip._internal.commands.download",
+ "DownloadCommand",
+ "Download packages.",
+ ),
+ "uninstall": CommandInfo(
+ "pip._internal.commands.uninstall",
+ "UninstallCommand",
+ "Uninstall packages.",
+ ),
+ "freeze": CommandInfo(
+ "pip._internal.commands.freeze",
+ "FreezeCommand",
+ "Output installed packages in requirements format.",
+ ),
+ "inspect": CommandInfo(
+ "pip._internal.commands.inspect",
+ "InspectCommand",
+ "Inspect the python environment.",
+ ),
+ "list": CommandInfo(
+ "pip._internal.commands.list",
+ "ListCommand",
+ "List installed packages.",
+ ),
+ "show": CommandInfo(
+ "pip._internal.commands.show",
+ "ShowCommand",
+ "Show information about installed packages.",
+ ),
+ "check": CommandInfo(
+ "pip._internal.commands.check",
+ "CheckCommand",
+ "Verify installed packages have compatible dependencies.",
+ ),
+ "config": CommandInfo(
+ "pip._internal.commands.configuration",
+ "ConfigurationCommand",
+ "Manage local and global configuration.",
+ ),
+ "search": CommandInfo(
+ "pip._internal.commands.search",
+ "SearchCommand",
+ "Search PyPI for packages.",
+ ),
+ "cache": CommandInfo(
+ "pip._internal.commands.cache",
+ "CacheCommand",
+ "Inspect and manage pip's wheel cache.",
+ ),
+ "index": CommandInfo(
+ "pip._internal.commands.index",
+ "IndexCommand",
+ "Inspect information available from package indexes.",
+ ),
+ "wheel": CommandInfo(
+ "pip._internal.commands.wheel",
+ "WheelCommand",
+ "Build wheels from your requirements.",
+ ),
+ "hash": CommandInfo(
+ "pip._internal.commands.hash",
+ "HashCommand",
+ "Compute hashes of package archives.",
+ ),
+ "completion": CommandInfo(
+ "pip._internal.commands.completion",
+ "CompletionCommand",
+ "A helper command used for command completion.",
+ ),
+ "debug": CommandInfo(
+ "pip._internal.commands.debug",
+ "DebugCommand",
+ "Show information useful for debugging.",
+ ),
+ "help": CommandInfo(
+ "pip._internal.commands.help",
+ "HelpCommand",
+ "Show help for commands.",
+ ),
+}
+
+
+def create_command(name: str, **kwargs: Any) -> Command:
+ """
+ Create an instance of the Command class with the given name.
+ """
+ module_path, class_name, summary = commands_dict[name]
+ module = importlib.import_module(module_path)
+ command_class = getattr(module, class_name)
+ command = command_class(name=name, summary=summary, **kwargs)
+
+ return command
+
+
+def get_similar_commands(name: str) -> Optional[str]:
+ """Command name auto-correct."""
+ from difflib import get_close_matches
+
+ name = name.lower()
+
+ close_commands = get_close_matches(name, commands_dict.keys())
+
+ if close_commands:
+ return close_commands[0]
+ else:
+ return None
diff --git a/env/Lib/site-packages/pip/_internal/commands/cache.py b/env/Lib/site-packages/pip/_internal/commands/cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..328336152cc665d02af99c588d2ad937c5800877
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/cache.py
@@ -0,0 +1,225 @@
+import os
+import textwrap
+from optparse import Values
+from typing import Any, List
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.exceptions import CommandError, PipError
+from pip._internal.utils import filesystem
+from pip._internal.utils.logging import getLogger
+
+logger = getLogger(__name__)
+
+
+class CacheCommand(Command):
+ """
+ Inspect and manage pip's wheel cache.
+
+ Subcommands:
+
+ - dir: Show the cache directory.
+ - info: Show information about the cache.
+ - list: List filenames of packages stored in the cache.
+ - remove: Remove one or more package from the cache.
+ - purge: Remove all items from the cache.
+
+ ```` can be a glob expression or a package name.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog dir
+ %prog info
+ %prog list [] [--format=[human, abspath]]
+ %prog remove
+ %prog purge
+ """
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "--format",
+ action="store",
+ dest="list_format",
+ default="human",
+ choices=("human", "abspath"),
+ help="Select the output format among: human (default) or abspath",
+ )
+
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ handlers = {
+ "dir": self.get_cache_dir,
+ "info": self.get_cache_info,
+ "list": self.list_cache_items,
+ "remove": self.remove_cache_items,
+ "purge": self.purge_cache,
+ }
+
+ if not options.cache_dir:
+ logger.error("pip cache commands can not function since cache is disabled.")
+ return ERROR
+
+ # Determine action
+ if not args or args[0] not in handlers:
+ logger.error(
+ "Need an action (%s) to perform.",
+ ", ".join(sorted(handlers)),
+ )
+ return ERROR
+
+ action = args[0]
+
+ # Error handling happens here, not in the action-handlers.
+ try:
+ handlers[action](options, args[1:])
+ except PipError as e:
+ logger.error(e.args[0])
+ return ERROR
+
+ return SUCCESS
+
+ def get_cache_dir(self, options: Values, args: List[Any]) -> None:
+ if args:
+ raise CommandError("Too many arguments")
+
+ logger.info(options.cache_dir)
+
+ def get_cache_info(self, options: Values, args: List[Any]) -> None:
+ if args:
+ raise CommandError("Too many arguments")
+
+ num_http_files = len(self._find_http_files(options))
+ num_packages = len(self._find_wheels(options, "*"))
+
+ http_cache_location = self._cache_dir(options, "http-v2")
+ old_http_cache_location = self._cache_dir(options, "http")
+ wheels_cache_location = self._cache_dir(options, "wheels")
+ http_cache_size = filesystem.format_size(
+ filesystem.directory_size(http_cache_location)
+ + filesystem.directory_size(old_http_cache_location)
+ )
+ wheels_cache_size = filesystem.format_directory_size(wheels_cache_location)
+
+ message = (
+ textwrap.dedent(
+ """
+ Package index page cache location (pip v23.3+): {http_cache_location}
+ Package index page cache location (older pips): {old_http_cache_location}
+ Package index page cache size: {http_cache_size}
+ Number of HTTP files: {num_http_files}
+ Locally built wheels location: {wheels_cache_location}
+ Locally built wheels size: {wheels_cache_size}
+ Number of locally built wheels: {package_count}
+ """ # noqa: E501
+ )
+ .format(
+ http_cache_location=http_cache_location,
+ old_http_cache_location=old_http_cache_location,
+ http_cache_size=http_cache_size,
+ num_http_files=num_http_files,
+ wheels_cache_location=wheels_cache_location,
+ package_count=num_packages,
+ wheels_cache_size=wheels_cache_size,
+ )
+ .strip()
+ )
+
+ logger.info(message)
+
+ def list_cache_items(self, options: Values, args: List[Any]) -> None:
+ if len(args) > 1:
+ raise CommandError("Too many arguments")
+
+ if args:
+ pattern = args[0]
+ else:
+ pattern = "*"
+
+ files = self._find_wheels(options, pattern)
+ if options.list_format == "human":
+ self.format_for_human(files)
+ else:
+ self.format_for_abspath(files)
+
+ def format_for_human(self, files: List[str]) -> None:
+ if not files:
+ logger.info("No locally built wheels cached.")
+ return
+
+ results = []
+ for filename in files:
+ wheel = os.path.basename(filename)
+ size = filesystem.format_file_size(filename)
+ results.append(f" - {wheel} ({size})")
+ logger.info("Cache contents:\n")
+ logger.info("\n".join(sorted(results)))
+
+ def format_for_abspath(self, files: List[str]) -> None:
+ if files:
+ logger.info("\n".join(sorted(files)))
+
+ def remove_cache_items(self, options: Values, args: List[Any]) -> None:
+ if len(args) > 1:
+ raise CommandError("Too many arguments")
+
+ if not args:
+ raise CommandError("Please provide a pattern")
+
+ files = self._find_wheels(options, args[0])
+
+ no_matching_msg = "No matching packages"
+ if args[0] == "*":
+ # Only fetch http files if no specific pattern given
+ files += self._find_http_files(options)
+ else:
+ # Add the pattern to the log message
+ no_matching_msg += f' for pattern "{args[0]}"'
+
+ if not files:
+ logger.warning(no_matching_msg)
+
+ for filename in files:
+ os.unlink(filename)
+ logger.verbose("Removed %s", filename)
+ logger.info("Files removed: %s", len(files))
+
+ def purge_cache(self, options: Values, args: List[Any]) -> None:
+ if args:
+ raise CommandError("Too many arguments")
+
+ return self.remove_cache_items(options, ["*"])
+
+ def _cache_dir(self, options: Values, subdir: str) -> str:
+ return os.path.join(options.cache_dir, subdir)
+
+ def _find_http_files(self, options: Values) -> List[str]:
+ old_http_dir = self._cache_dir(options, "http")
+ new_http_dir = self._cache_dir(options, "http-v2")
+ return filesystem.find_files(old_http_dir, "*") + filesystem.find_files(
+ new_http_dir, "*"
+ )
+
+ def _find_wheels(self, options: Values, pattern: str) -> List[str]:
+ wheel_dir = self._cache_dir(options, "wheels")
+
+ # The wheel filename format, as specified in PEP 427, is:
+ # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
+ #
+ # Additionally, non-alphanumeric values in the distribution are
+ # normalized to underscores (_), meaning hyphens can never occur
+ # before `-{version}`.
+ #
+ # Given that information:
+ # - If the pattern we're given contains a hyphen (-), the user is
+ # providing at least the version. Thus, we can just append `*.whl`
+ # to match the rest of it.
+ # - If the pattern we're given doesn't contain a hyphen (-), the
+ # user is only providing the name. Thus, we append `-*.whl` to
+ # match the hyphen before the version, followed by anything else.
+ #
+ # PEP 427: https://www.python.org/dev/peps/pep-0427/
+ pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl")
+
+ return filesystem.find_files(wheel_dir, pattern)
diff --git a/env/Lib/site-packages/pip/_internal/commands/check.py b/env/Lib/site-packages/pip/_internal/commands/check.py
new file mode 100644
index 0000000000000000000000000000000000000000..f54a16dc0a15892d935a5e79679bab3510c8b0d5
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/check.py
@@ -0,0 +1,67 @@
+import logging
+from optparse import Values
+from typing import List
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.metadata import get_default_environment
+from pip._internal.operations.check import (
+ check_package_set,
+ check_unsupported,
+ create_package_set_from_installed,
+)
+from pip._internal.utils.compatibility_tags import get_supported
+from pip._internal.utils.misc import write_output
+
+logger = logging.getLogger(__name__)
+
+
+class CheckCommand(Command):
+ """Verify installed packages have compatible dependencies."""
+
+ ignore_require_venv = True
+ usage = """
+ %prog [options]"""
+
+ def run(self, options: Values, args: List[str]) -> int:
+ package_set, parsing_probs = create_package_set_from_installed()
+ missing, conflicting = check_package_set(package_set)
+ unsupported = list(
+ check_unsupported(
+ get_default_environment().iter_installed_distributions(),
+ get_supported(),
+ )
+ )
+
+ for project_name in missing:
+ version = package_set[project_name].version
+ for dependency in missing[project_name]:
+ write_output(
+ "%s %s requires %s, which is not installed.",
+ project_name,
+ version,
+ dependency[0],
+ )
+
+ for project_name in conflicting:
+ version = package_set[project_name].version
+ for dep_name, dep_version, req in conflicting[project_name]:
+ write_output(
+ "%s %s has requirement %s, but you have %s %s.",
+ project_name,
+ version,
+ req,
+ dep_name,
+ dep_version,
+ )
+ for package in unsupported:
+ write_output(
+ "%s %s is not supported on this platform",
+ package.raw_name,
+ package.version,
+ )
+ if missing or conflicting or parsing_probs or unsupported:
+ return ERROR
+ else:
+ write_output("No broken requirements found.")
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/completion.py b/env/Lib/site-packages/pip/_internal/commands/completion.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e89e27988368821f6936cd1e94ac9395ca0312d
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/completion.py
@@ -0,0 +1,130 @@
+import sys
+import textwrap
+from optparse import Values
+from typing import List
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.utils.misc import get_prog
+
+BASE_COMPLETION = """
+# pip {shell} completion start{script}# pip {shell} completion end
+"""
+
+COMPLETION_SCRIPTS = {
+ "bash": """
+ _pip_completion()
+ {{
+ COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
+ COMP_CWORD=$COMP_CWORD \\
+ PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
+ }}
+ complete -o default -F _pip_completion {prog}
+ """,
+ "zsh": """
+ #compdef -P pip[0-9.]#
+ __pip() {{
+ compadd $( COMP_WORDS="$words[*]" \\
+ COMP_CWORD=$((CURRENT-1)) \\
+ PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )
+ }}
+ if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
+ # autoload from fpath, call function directly
+ __pip "$@"
+ else
+ # eval/source/. command, register function for later
+ compdef __pip -P 'pip[0-9.]#'
+ fi
+ """,
+ "fish": """
+ function __fish_complete_pip
+ set -lx COMP_WORDS (commandline -o) ""
+ set -lx COMP_CWORD ( \\
+ math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
+ )
+ set -lx PIP_AUTO_COMPLETE 1
+ string split \\ -- (eval $COMP_WORDS[1])
+ end
+ complete -fa "(__fish_complete_pip)" -c {prog}
+ """,
+ "powershell": """
+ if ((Test-Path Function:\\TabExpansion) -and -not `
+ (Test-Path Function:\\_pip_completeBackup)) {{
+ Rename-Item Function:\\TabExpansion _pip_completeBackup
+ }}
+ function TabExpansion($line, $lastWord) {{
+ $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
+ if ($lastBlock.StartsWith("{prog} ")) {{
+ $Env:COMP_WORDS=$lastBlock
+ $Env:COMP_CWORD=$lastBlock.Split().Length - 1
+ $Env:PIP_AUTO_COMPLETE=1
+ (& {prog}).Split()
+ Remove-Item Env:COMP_WORDS
+ Remove-Item Env:COMP_CWORD
+ Remove-Item Env:PIP_AUTO_COMPLETE
+ }}
+ elseif (Test-Path Function:\\_pip_completeBackup) {{
+ # Fall back on existing tab expansion
+ _pip_completeBackup $line $lastWord
+ }}
+ }}
+ """,
+}
+
+
+class CompletionCommand(Command):
+ """A helper command to be used for command completion."""
+
+ ignore_require_venv = True
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "--bash",
+ "-b",
+ action="store_const",
+ const="bash",
+ dest="shell",
+ help="Emit completion code for bash",
+ )
+ self.cmd_opts.add_option(
+ "--zsh",
+ "-z",
+ action="store_const",
+ const="zsh",
+ dest="shell",
+ help="Emit completion code for zsh",
+ )
+ self.cmd_opts.add_option(
+ "--fish",
+ "-f",
+ action="store_const",
+ const="fish",
+ dest="shell",
+ help="Emit completion code for fish",
+ )
+ self.cmd_opts.add_option(
+ "--powershell",
+ "-p",
+ action="store_const",
+ const="powershell",
+ dest="shell",
+ help="Emit completion code for powershell",
+ )
+
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ """Prints the completion code of the given shell"""
+ shells = COMPLETION_SCRIPTS.keys()
+ shell_options = ["--" + shell for shell in sorted(shells)]
+ if options.shell in shells:
+ script = textwrap.dedent(
+ COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog())
+ )
+ print(BASE_COMPLETION.format(script=script, shell=options.shell))
+ return SUCCESS
+ else:
+ sys.stderr.write(
+ "ERROR: You must pass {}\n".format(" or ".join(shell_options))
+ )
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/configuration.py b/env/Lib/site-packages/pip/_internal/commands/configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a1dc6b6cd841dc2a9f5666fb34d8120e612346e
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/configuration.py
@@ -0,0 +1,280 @@
+import logging
+import os
+import subprocess
+from optparse import Values
+from typing import Any, List, Optional
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.configuration import (
+ Configuration,
+ Kind,
+ get_configuration_files,
+ kinds,
+)
+from pip._internal.exceptions import PipError
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.misc import get_prog, write_output
+
+logger = logging.getLogger(__name__)
+
+
+class ConfigurationCommand(Command):
+ """
+ Manage local and global configuration.
+
+ Subcommands:
+
+ - list: List the active configuration (or from the file specified)
+ - edit: Edit the configuration file in an editor
+ - get: Get the value associated with command.option
+ - set: Set the command.option=value
+ - unset: Unset the value associated with command.option
+ - debug: List the configuration files and values defined under them
+
+ Configuration keys should be dot separated command and option name,
+ with the special prefix "global" affecting any command. For example,
+ "pip config set global.index-url https://example.org/" would configure
+ the index url for all commands, but "pip config set download.timeout 10"
+ would configure a 10 second timeout only for "pip download" commands.
+
+ If none of --user, --global and --site are passed, a virtual
+ environment configuration file is used if one is active and the file
+ exists. Otherwise, all modifications happen to the user file by
+ default.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog [] list
+ %prog [] [--editor ] edit
+
+ %prog [] get command.option
+ %prog [] set command.option value
+ %prog [] unset command.option
+ %prog [] debug
+ """
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "--editor",
+ dest="editor",
+ action="store",
+ default=None,
+ help=(
+ "Editor to use to edit the file. Uses VISUAL or EDITOR "
+ "environment variables if not provided."
+ ),
+ )
+
+ self.cmd_opts.add_option(
+ "--global",
+ dest="global_file",
+ action="store_true",
+ default=False,
+ help="Use the system-wide configuration file only",
+ )
+
+ self.cmd_opts.add_option(
+ "--user",
+ dest="user_file",
+ action="store_true",
+ default=False,
+ help="Use the user configuration file only",
+ )
+
+ self.cmd_opts.add_option(
+ "--site",
+ dest="site_file",
+ action="store_true",
+ default=False,
+ help="Use the current environment configuration file only",
+ )
+
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ handlers = {
+ "list": self.list_values,
+ "edit": self.open_in_editor,
+ "get": self.get_name,
+ "set": self.set_name_value,
+ "unset": self.unset_name,
+ "debug": self.list_config_values,
+ }
+
+ # Determine action
+ if not args or args[0] not in handlers:
+ logger.error(
+ "Need an action (%s) to perform.",
+ ", ".join(sorted(handlers)),
+ )
+ return ERROR
+
+ action = args[0]
+
+ # Determine which configuration files are to be loaded
+ # Depends on whether the command is modifying.
+ try:
+ load_only = self._determine_file(
+ options, need_value=(action in ["get", "set", "unset", "edit"])
+ )
+ except PipError as e:
+ logger.error(e.args[0])
+ return ERROR
+
+ # Load a new configuration
+ self.configuration = Configuration(
+ isolated=options.isolated_mode, load_only=load_only
+ )
+ self.configuration.load()
+
+ # Error handling happens here, not in the action-handlers.
+ try:
+ handlers[action](options, args[1:])
+ except PipError as e:
+ logger.error(e.args[0])
+ return ERROR
+
+ return SUCCESS
+
+ def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
+ file_options = [
+ key
+ for key, value in (
+ (kinds.USER, options.user_file),
+ (kinds.GLOBAL, options.global_file),
+ (kinds.SITE, options.site_file),
+ )
+ if value
+ ]
+
+ if not file_options:
+ if not need_value:
+ return None
+ # Default to user, unless there's a site file.
+ elif any(
+ os.path.exists(site_config_file)
+ for site_config_file in get_configuration_files()[kinds.SITE]
+ ):
+ return kinds.SITE
+ else:
+ return kinds.USER
+ elif len(file_options) == 1:
+ return file_options[0]
+
+ raise PipError(
+ "Need exactly one file to operate upon "
+ "(--user, --site, --global) to perform."
+ )
+
+ def list_values(self, options: Values, args: List[str]) -> None:
+ self._get_n_args(args, "list", n=0)
+
+ for key, value in sorted(self.configuration.items()):
+ write_output("%s=%r", key, value)
+
+ def get_name(self, options: Values, args: List[str]) -> None:
+ key = self._get_n_args(args, "get [name]", n=1)
+ value = self.configuration.get_value(key)
+
+ write_output("%s", value)
+
+ def set_name_value(self, options: Values, args: List[str]) -> None:
+ key, value = self._get_n_args(args, "set [name] [value]", n=2)
+ self.configuration.set_value(key, value)
+
+ self._save_configuration()
+
+ def unset_name(self, options: Values, args: List[str]) -> None:
+ key = self._get_n_args(args, "unset [name]", n=1)
+ self.configuration.unset_value(key)
+
+ self._save_configuration()
+
+ def list_config_values(self, options: Values, args: List[str]) -> None:
+ """List config key-value pairs across different config files"""
+ self._get_n_args(args, "debug", n=0)
+
+ self.print_env_var_values()
+ # Iterate over config files and print if they exist, and the
+ # key-value pairs present in them if they do
+ for variant, files in sorted(self.configuration.iter_config_files()):
+ write_output("%s:", variant)
+ for fname in files:
+ with indent_log():
+ file_exists = os.path.exists(fname)
+ write_output("%s, exists: %r", fname, file_exists)
+ if file_exists:
+ self.print_config_file_values(variant)
+
+ def print_config_file_values(self, variant: Kind) -> None:
+ """Get key-value pairs from the file of a variant"""
+ for name, value in self.configuration.get_values_in_config(variant).items():
+ with indent_log():
+ write_output("%s: %s", name, value)
+
+ def print_env_var_values(self) -> None:
+ """Get key-values pairs present as environment variables"""
+ write_output("%s:", "env_var")
+ with indent_log():
+ for key, value in sorted(self.configuration.get_environ_vars()):
+ env_var = f"PIP_{key.upper()}"
+ write_output("%s=%r", env_var, value)
+
+ def open_in_editor(self, options: Values, args: List[str]) -> None:
+ editor = self._determine_editor(options)
+
+ fname = self.configuration.get_file_to_edit()
+ if fname is None:
+ raise PipError("Could not determine appropriate file.")
+ elif '"' in fname:
+ # This shouldn't happen, unless we see a username like that.
+ # If that happens, we'd appreciate a pull request fixing this.
+ raise PipError(
+ f'Can not open an editor for a file name containing "\n{fname}'
+ )
+
+ try:
+ subprocess.check_call(f'{editor} "{fname}"', shell=True)
+ except FileNotFoundError as e:
+ if not e.filename:
+ e.filename = editor
+ raise
+ except subprocess.CalledProcessError as e:
+ raise PipError(f"Editor Subprocess exited with exit code {e.returncode}")
+
+ def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
+ """Helper to make sure the command got the right number of arguments"""
+ if len(args) != n:
+ msg = (
+ f"Got unexpected number of arguments, expected {n}. "
+ f'(example: "{get_prog()} config {example}")'
+ )
+ raise PipError(msg)
+
+ if n == 1:
+ return args[0]
+ else:
+ return args
+
+ def _save_configuration(self) -> None:
+ # We successfully ran a modifying command. Need to save the
+ # configuration.
+ try:
+ self.configuration.save()
+ except Exception:
+ logger.exception(
+ "Unable to save configuration. Please report this as a bug."
+ )
+ raise PipError("Internal Error.")
+
+ def _determine_editor(self, options: Values) -> str:
+ if options.editor is not None:
+ return options.editor
+ elif "VISUAL" in os.environ:
+ return os.environ["VISUAL"]
+ elif "EDITOR" in os.environ:
+ return os.environ["EDITOR"]
+ else:
+ raise PipError("Could not determine editor to use.")
diff --git a/env/Lib/site-packages/pip/_internal/commands/debug.py b/env/Lib/site-packages/pip/_internal/commands/debug.py
new file mode 100644
index 0000000000000000000000000000000000000000..567ca967e5b64478d17455288b79dd80301b4888
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/debug.py
@@ -0,0 +1,201 @@
+import locale
+import logging
+import os
+import sys
+from optparse import Values
+from types import ModuleType
+from typing import Any, Dict, List, Optional
+
+import pip._vendor
+from pip._vendor.certifi import where
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.cmdoptions import make_target_python
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.configuration import Configuration
+from pip._internal.metadata import get_environment
+from pip._internal.utils.compat import open_text_resource
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.misc import get_pip_version
+
+logger = logging.getLogger(__name__)
+
+
+def show_value(name: str, value: Any) -> None:
+ logger.info("%s: %s", name, value)
+
+
+def show_sys_implementation() -> None:
+ logger.info("sys.implementation:")
+ implementation_name = sys.implementation.name
+ with indent_log():
+ show_value("name", implementation_name)
+
+
+def create_vendor_txt_map() -> Dict[str, str]:
+ with open_text_resource("pip._vendor", "vendor.txt") as f:
+ # Purge non version specifying lines.
+ # Also, remove any space prefix or suffixes (including comments).
+ lines = [
+ line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line
+ ]
+
+ # Transform into "module" -> version dict.
+ return dict(line.split("==", 1) for line in lines)
+
+
+def get_module_from_module_name(module_name: str) -> Optional[ModuleType]:
+ # Module name can be uppercase in vendor.txt for some reason...
+ module_name = module_name.lower().replace("-", "_")
+ # PATCH: setuptools is actually only pkg_resources.
+ if module_name == "setuptools":
+ module_name = "pkg_resources"
+
+ try:
+ __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0)
+ return getattr(pip._vendor, module_name)
+ except ImportError:
+ # We allow 'truststore' to fail to import due
+ # to being unavailable on Python 3.9 and earlier.
+ if module_name == "truststore" and sys.version_info < (3, 10):
+ return None
+ raise
+
+
+def get_vendor_version_from_module(module_name: str) -> Optional[str]:
+ module = get_module_from_module_name(module_name)
+ version = getattr(module, "__version__", None)
+
+ if module and not version:
+ # Try to find version in debundled module info.
+ assert module.__file__ is not None
+ env = get_environment([os.path.dirname(module.__file__)])
+ dist = env.get_distribution(module_name)
+ if dist:
+ version = str(dist.version)
+
+ return version
+
+
+def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
+ """Log the actual version and print extra info if there is
+ a conflict or if the actual version could not be imported.
+ """
+ for module_name, expected_version in vendor_txt_versions.items():
+ extra_message = ""
+ actual_version = get_vendor_version_from_module(module_name)
+ if not actual_version:
+ extra_message = (
+ " (Unable to locate actual module version, using"
+ " vendor.txt specified version)"
+ )
+ actual_version = expected_version
+ elif parse_version(actual_version) != parse_version(expected_version):
+ extra_message = (
+ " (CONFLICT: vendor.txt suggests version should"
+ f" be {expected_version})"
+ )
+ logger.info("%s==%s%s", module_name, actual_version, extra_message)
+
+
+def show_vendor_versions() -> None:
+ logger.info("vendored library versions:")
+
+ vendor_txt_versions = create_vendor_txt_map()
+ with indent_log():
+ show_actual_vendor_versions(vendor_txt_versions)
+
+
+def show_tags(options: Values) -> None:
+ tag_limit = 10
+
+ target_python = make_target_python(options)
+ tags = target_python.get_sorted_tags()
+
+ # Display the target options that were explicitly provided.
+ formatted_target = target_python.format_given()
+ suffix = ""
+ if formatted_target:
+ suffix = f" (target: {formatted_target})"
+
+ msg = f"Compatible tags: {len(tags)}{suffix}"
+ logger.info(msg)
+
+ if options.verbose < 1 and len(tags) > tag_limit:
+ tags_limited = True
+ tags = tags[:tag_limit]
+ else:
+ tags_limited = False
+
+ with indent_log():
+ for tag in tags:
+ logger.info(str(tag))
+
+ if tags_limited:
+ msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
+ logger.info(msg)
+
+
+def ca_bundle_info(config: Configuration) -> str:
+ levels = {key.split(".", 1)[0] for key, _ in config.items()}
+ if not levels:
+ return "Not specified"
+
+ levels_that_override_global = ["install", "wheel", "download"]
+ global_overriding_level = [
+ level for level in levels if level in levels_that_override_global
+ ]
+ if not global_overriding_level:
+ return "global"
+
+ if "global" in levels:
+ levels.remove("global")
+ return ", ".join(levels)
+
+
+class DebugCommand(Command):
+ """
+ Display debug information.
+ """
+
+ usage = """
+ %prog """
+ ignore_require_venv = True
+
+ def add_options(self) -> None:
+ cmdoptions.add_target_python_options(self.cmd_opts)
+ self.parser.insert_option_group(0, self.cmd_opts)
+ self.parser.config.load()
+
+ def run(self, options: Values, args: List[str]) -> int:
+ logger.warning(
+ "This command is only meant for debugging. "
+ "Do not use this with automation for parsing and getting these "
+ "details, since the output and options of this command may "
+ "change without notice."
+ )
+ show_value("pip version", get_pip_version())
+ show_value("sys.version", sys.version)
+ show_value("sys.executable", sys.executable)
+ show_value("sys.getdefaultencoding", sys.getdefaultencoding())
+ show_value("sys.getfilesystemencoding", sys.getfilesystemencoding())
+ show_value(
+ "locale.getpreferredencoding",
+ locale.getpreferredencoding(),
+ )
+ show_value("sys.platform", sys.platform)
+ show_sys_implementation()
+
+ show_value("'cert' config value", ca_bundle_info(self.parser.config))
+ show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE"))
+ show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE"))
+ show_value("pip._vendor.certifi.where()", where())
+ show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED)
+
+ show_vendor_versions()
+
+ show_tags(options)
+
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/download.py b/env/Lib/site-packages/pip/_internal/commands/download.py
new file mode 100644
index 0000000000000000000000000000000000000000..917bbb91d83cbb81a5814d510b5dd3bdd6c0f510
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/download.py
@@ -0,0 +1,146 @@
+import logging
+import os
+from optparse import Values
+from typing import List
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.cmdoptions import make_target_python
+from pip._internal.cli.req_command import RequirementCommand, with_cleanup
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.operations.build.build_tracker import get_build_tracker
+from pip._internal.req.req_install import check_legacy_setup_py_options
+from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
+from pip._internal.utils.temp_dir import TempDirectory
+
+logger = logging.getLogger(__name__)
+
+
+class DownloadCommand(RequirementCommand):
+ """
+ Download packages from:
+
+ - PyPI (and other indexes) using requirement specifiers.
+ - VCS project urls.
+ - Local project directories.
+ - Local or remote source archives.
+
+ pip also supports downloading from "requirements files", which provide
+ an easy way to specify a whole environment to be downloaded.
+ """
+
+ usage = """
+ %prog [options] [package-index-options] ...
+ %prog [options] -r [package-index-options] ...
+ %prog [options] ...
+ %prog [options] ...
+ %prog [options] ..."""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(cmdoptions.constraints())
+ self.cmd_opts.add_option(cmdoptions.requirements())
+ self.cmd_opts.add_option(cmdoptions.no_deps())
+ self.cmd_opts.add_option(cmdoptions.global_options())
+ self.cmd_opts.add_option(cmdoptions.no_binary())
+ self.cmd_opts.add_option(cmdoptions.only_binary())
+ self.cmd_opts.add_option(cmdoptions.prefer_binary())
+ self.cmd_opts.add_option(cmdoptions.src())
+ self.cmd_opts.add_option(cmdoptions.pre())
+ self.cmd_opts.add_option(cmdoptions.require_hashes())
+ self.cmd_opts.add_option(cmdoptions.progress_bar())
+ self.cmd_opts.add_option(cmdoptions.no_build_isolation())
+ self.cmd_opts.add_option(cmdoptions.use_pep517())
+ self.cmd_opts.add_option(cmdoptions.no_use_pep517())
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
+
+ self.cmd_opts.add_option(
+ "-d",
+ "--dest",
+ "--destination-dir",
+ "--destination-directory",
+ dest="download_dir",
+ metavar="dir",
+ default=os.curdir,
+ help="Download packages into .",
+ )
+
+ cmdoptions.add_target_python_options(self.cmd_opts)
+
+ index_opts = cmdoptions.make_option_group(
+ cmdoptions.index_group,
+ self.parser,
+ )
+
+ self.parser.insert_option_group(0, index_opts)
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ @with_cleanup
+ def run(self, options: Values, args: List[str]) -> int:
+ options.ignore_installed = True
+ # editable doesn't really make sense for `pip download`, but the bowels
+ # of the RequirementSet code require that property.
+ options.editables = []
+
+ cmdoptions.check_dist_restriction(options)
+
+ options.download_dir = normalize_path(options.download_dir)
+ ensure_dir(options.download_dir)
+
+ session = self.get_default_session(options)
+
+ target_python = make_target_python(options)
+ finder = self._build_package_finder(
+ options=options,
+ session=session,
+ target_python=target_python,
+ ignore_requires_python=options.ignore_requires_python,
+ )
+
+ build_tracker = self.enter_context(get_build_tracker())
+
+ directory = TempDirectory(
+ delete=not options.no_clean,
+ kind="download",
+ globally_managed=True,
+ )
+
+ reqs = self.get_requirements(args, options, finder, session)
+ check_legacy_setup_py_options(options, reqs)
+
+ preparer = self.make_requirement_preparer(
+ temp_build_dir=directory,
+ options=options,
+ build_tracker=build_tracker,
+ session=session,
+ finder=finder,
+ download_dir=options.download_dir,
+ use_user_site=False,
+ verbosity=self.verbosity,
+ )
+
+ resolver = self.make_resolver(
+ preparer=preparer,
+ finder=finder,
+ options=options,
+ ignore_requires_python=options.ignore_requires_python,
+ use_pep517=options.use_pep517,
+ py_version_info=options.python_version,
+ )
+
+ self.trace_basic_info(finder)
+
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
+
+ downloaded: List[str] = []
+ for req in requirement_set.requirements.values():
+ if req.satisfied_by is None:
+ assert req.name is not None
+ preparer.save_linked_requirement(req)
+ downloaded.append(req.name)
+
+ preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
+
+ if downloaded:
+ write_output("Successfully downloaded %s", " ".join(downloaded))
+
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/freeze.py b/env/Lib/site-packages/pip/_internal/commands/freeze.py
new file mode 100644
index 0000000000000000000000000000000000000000..885fdfeb83b837b27db08e4abc3df2e7b580dc2b
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/freeze.py
@@ -0,0 +1,109 @@
+import sys
+from optparse import Values
+from typing import AbstractSet, List
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.operations.freeze import freeze
+from pip._internal.utils.compat import stdlib_pkgs
+
+
+def _should_suppress_build_backends() -> bool:
+ return sys.version_info < (3, 12)
+
+
+def _dev_pkgs() -> AbstractSet[str]:
+ pkgs = {"pip"}
+
+ if _should_suppress_build_backends():
+ pkgs |= {"setuptools", "distribute", "wheel"}
+
+ return pkgs
+
+
+class FreezeCommand(Command):
+ """
+ Output installed packages in requirements format.
+
+ packages are listed in a case-insensitive sorted order.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog [options]"""
+ log_streams = ("ext://sys.stderr", "ext://sys.stderr")
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
+ default=[],
+ metavar="file",
+ help=(
+ "Use the order in the given requirements file and its "
+ "comments when generating output. This option can be "
+ "used multiple times."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "-l",
+ "--local",
+ dest="local",
+ action="store_true",
+ default=False,
+ help=(
+ "If in a virtualenv that has global access, do not output "
+ "globally-installed packages."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "--user",
+ dest="user",
+ action="store_true",
+ default=False,
+ help="Only output packages installed in user-site.",
+ )
+ self.cmd_opts.add_option(cmdoptions.list_path())
+ self.cmd_opts.add_option(
+ "--all",
+ dest="freeze_all",
+ action="store_true",
+ help=(
+ "Do not skip these packages in the output:"
+ " {}".format(", ".join(_dev_pkgs()))
+ ),
+ )
+ self.cmd_opts.add_option(
+ "--exclude-editable",
+ dest="exclude_editable",
+ action="store_true",
+ help="Exclude editable package from output.",
+ )
+ self.cmd_opts.add_option(cmdoptions.list_exclude())
+
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ skip = set(stdlib_pkgs)
+ if not options.freeze_all:
+ skip.update(_dev_pkgs())
+
+ if options.excludes:
+ skip.update(options.excludes)
+
+ cmdoptions.check_list_path_option(options)
+
+ for line in freeze(
+ requirement=options.requirements,
+ local_only=options.local,
+ user_only=options.user,
+ paths=options.path,
+ isolated=options.isolated_mode,
+ skip=skip,
+ exclude_editable=options.exclude_editable,
+ ):
+ sys.stdout.write(line + "\n")
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/hash.py b/env/Lib/site-packages/pip/_internal/commands/hash.py
new file mode 100644
index 0000000000000000000000000000000000000000..042dac813e74b8187c3754cb9a937c7f7183e331
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/hash.py
@@ -0,0 +1,59 @@
+import hashlib
+import logging
+import sys
+from optparse import Values
+from typing import List
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
+from pip._internal.utils.misc import read_chunks, write_output
+
+logger = logging.getLogger(__name__)
+
+
+class HashCommand(Command):
+ """
+ Compute a hash of a local package archive.
+
+ These can be used with --hash in a requirements file to do repeatable
+ installs.
+ """
+
+ usage = "%prog [options] ..."
+ ignore_require_venv = True
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-a",
+ "--algorithm",
+ dest="algorithm",
+ choices=STRONG_HASHES,
+ action="store",
+ default=FAVORITE_HASH,
+ help="The hash algorithm to use: one of {}".format(
+ ", ".join(STRONG_HASHES)
+ ),
+ )
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ if not args:
+ self.parser.print_usage(sys.stderr)
+ return ERROR
+
+ algorithm = options.algorithm
+ for path in args:
+ write_output(
+ "%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm)
+ )
+ return SUCCESS
+
+
+def _hash_of_file(path: str, algorithm: str) -> str:
+ """Return the hash digest of a file."""
+ with open(path, "rb") as archive:
+ hash = hashlib.new(algorithm)
+ for chunk in read_chunks(archive):
+ hash.update(chunk)
+ return hash.hexdigest()
diff --git a/env/Lib/site-packages/pip/_internal/commands/help.py b/env/Lib/site-packages/pip/_internal/commands/help.py
new file mode 100644
index 0000000000000000000000000000000000000000..62066318b74dcc5c32bcd24b9493fb34d1ce52d7
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/help.py
@@ -0,0 +1,41 @@
+from optparse import Values
+from typing import List
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.exceptions import CommandError
+
+
+class HelpCommand(Command):
+ """Show help for commands"""
+
+ usage = """
+ %prog """
+ ignore_require_venv = True
+
+ def run(self, options: Values, args: List[str]) -> int:
+ from pip._internal.commands import (
+ commands_dict,
+ create_command,
+ get_similar_commands,
+ )
+
+ try:
+ # 'pip help' with no args is handled by pip.__init__.parseopt()
+ cmd_name = args[0] # the command we need help for
+ except IndexError:
+ return SUCCESS
+
+ if cmd_name not in commands_dict:
+ guess = get_similar_commands(cmd_name)
+
+ msg = [f'unknown command "{cmd_name}"']
+ if guess:
+ msg.append(f'maybe you meant "{guess}"')
+
+ raise CommandError(" - ".join(msg))
+
+ command = create_command(cmd_name)
+ command.parser.print_help()
+
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/index.py b/env/Lib/site-packages/pip/_internal/commands/index.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e2661bba710fb2cc255ba6ee01ea743fdbf540e
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/index.py
@@ -0,0 +1,139 @@
+import logging
+from optparse import Values
+from typing import Any, Iterable, List, Optional
+
+from pip._vendor.packaging.version import Version
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.req_command import IndexGroupCommand
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.commands.search import print_dist_installation_info
+from pip._internal.exceptions import CommandError, DistributionNotFound, PipError
+from pip._internal.index.collector import LinkCollector
+from pip._internal.index.package_finder import PackageFinder
+from pip._internal.models.selection_prefs import SelectionPreferences
+from pip._internal.models.target_python import TargetPython
+from pip._internal.network.session import PipSession
+from pip._internal.utils.misc import write_output
+
+logger = logging.getLogger(__name__)
+
+
+class IndexCommand(IndexGroupCommand):
+ """
+ Inspect information available from package indexes.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog versions
+ """
+
+ def add_options(self) -> None:
+ cmdoptions.add_target_python_options(self.cmd_opts)
+
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
+ self.cmd_opts.add_option(cmdoptions.pre())
+ self.cmd_opts.add_option(cmdoptions.no_binary())
+ self.cmd_opts.add_option(cmdoptions.only_binary())
+
+ index_opts = cmdoptions.make_option_group(
+ cmdoptions.index_group,
+ self.parser,
+ )
+
+ self.parser.insert_option_group(0, index_opts)
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ handlers = {
+ "versions": self.get_available_package_versions,
+ }
+
+ logger.warning(
+ "pip index is currently an experimental command. "
+ "It may be removed/changed in a future release "
+ "without prior warning."
+ )
+
+ # Determine action
+ if not args or args[0] not in handlers:
+ logger.error(
+ "Need an action (%s) to perform.",
+ ", ".join(sorted(handlers)),
+ )
+ return ERROR
+
+ action = args[0]
+
+ # Error handling happens here, not in the action-handlers.
+ try:
+ handlers[action](options, args[1:])
+ except PipError as e:
+ logger.error(e.args[0])
+ return ERROR
+
+ return SUCCESS
+
+ def _build_package_finder(
+ self,
+ options: Values,
+ session: PipSession,
+ target_python: Optional[TargetPython] = None,
+ ignore_requires_python: Optional[bool] = None,
+ ) -> PackageFinder:
+ """
+ Create a package finder appropriate to the index command.
+ """
+ link_collector = LinkCollector.create(session, options=options)
+
+ # Pass allow_yanked=False to ignore yanked versions.
+ selection_prefs = SelectionPreferences(
+ allow_yanked=False,
+ allow_all_prereleases=options.pre,
+ ignore_requires_python=ignore_requires_python,
+ )
+
+ return PackageFinder.create(
+ link_collector=link_collector,
+ selection_prefs=selection_prefs,
+ target_python=target_python,
+ )
+
+ def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
+ if len(args) != 1:
+ raise CommandError("You need to specify exactly one argument")
+
+ target_python = cmdoptions.make_target_python(options)
+ query = args[0]
+
+ with self._build_session(options) as session:
+ finder = self._build_package_finder(
+ options=options,
+ session=session,
+ target_python=target_python,
+ ignore_requires_python=options.ignore_requires_python,
+ )
+
+ versions: Iterable[Version] = (
+ candidate.version for candidate in finder.find_all_candidates(query)
+ )
+
+ if not options.pre:
+ # Remove prereleases
+ versions = (
+ version for version in versions if not version.is_prerelease
+ )
+ versions = set(versions)
+
+ if not versions:
+ raise DistributionNotFound(
+ f"No matching distribution found for {query}"
+ )
+
+ formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
+ latest = formatted_versions[0]
+
+ write_output(f"{query} ({latest})")
+ write_output("Available versions: {}".format(", ".join(formatted_versions)))
+ print_dist_installation_info(query, latest)
diff --git a/env/Lib/site-packages/pip/_internal/commands/inspect.py b/env/Lib/site-packages/pip/_internal/commands/inspect.py
new file mode 100644
index 0000000000000000000000000000000000000000..e810c13166bbdf0e38c94bc3ef66af7aeda2bba7
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/inspect.py
@@ -0,0 +1,92 @@
+import logging
+from optparse import Values
+from typing import Any, Dict, List
+
+from pip._vendor.packaging.markers import default_environment
+from pip._vendor.rich import print_json
+
+from pip import __version__
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.metadata import BaseDistribution, get_environment
+from pip._internal.utils.compat import stdlib_pkgs
+from pip._internal.utils.urls import path_to_url
+
+logger = logging.getLogger(__name__)
+
+
+class InspectCommand(Command):
+ """
+ Inspect the content of a Python environment and produce a report in JSON format.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog [options]"""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "--local",
+ action="store_true",
+ default=False,
+ help=(
+ "If in a virtualenv that has global access, do not list "
+ "globally-installed packages."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "--user",
+ dest="user",
+ action="store_true",
+ default=False,
+ help="Only output packages installed in user-site.",
+ )
+ self.cmd_opts.add_option(cmdoptions.list_path())
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ cmdoptions.check_list_path_option(options)
+ dists = get_environment(options.path).iter_installed_distributions(
+ local_only=options.local,
+ user_only=options.user,
+ skip=set(stdlib_pkgs),
+ )
+ output = {
+ "version": "1",
+ "pip_version": __version__,
+ "installed": [self._dist_to_dict(dist) for dist in dists],
+ "environment": default_environment(),
+ # TODO tags? scheme?
+ }
+ print_json(data=output)
+ return SUCCESS
+
+ def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
+ res: Dict[str, Any] = {
+ "metadata": dist.metadata_dict,
+ "metadata_location": dist.info_location,
+ }
+ # direct_url. Note that we don't have download_info (as in the installation
+ # report) since it is not recorded in installed metadata.
+ direct_url = dist.direct_url
+ if direct_url is not None:
+ res["direct_url"] = direct_url.to_dict()
+ else:
+ # Emulate direct_url for legacy editable installs.
+ editable_project_location = dist.editable_project_location
+ if editable_project_location is not None:
+ res["direct_url"] = {
+ "url": path_to_url(editable_project_location),
+ "dir_info": {
+ "editable": True,
+ },
+ }
+ # installer
+ installer = dist.installer
+ if dist.installer:
+ res["installer"] = installer
+ # requested
+ if dist.installed_with_dist_info:
+ res["requested"] = dist.requested
+ return res
diff --git a/env/Lib/site-packages/pip/_internal/commands/install.py b/env/Lib/site-packages/pip/_internal/commands/install.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad45a2f2a578c0bbd3ec7d440280127ba55ce424
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/install.py
@@ -0,0 +1,783 @@
+import errno
+import json
+import operator
+import os
+import shutil
+import site
+from optparse import SUPPRESS_HELP, Values
+from typing import List, Optional
+
+from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.rich import print_json
+
+from pip._internal.cache import WheelCache
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.cmdoptions import make_target_python
+from pip._internal.cli.req_command import (
+ RequirementCommand,
+ with_cleanup,
+)
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.exceptions import CommandError, InstallationError
+from pip._internal.locations import get_scheme
+from pip._internal.metadata import get_environment
+from pip._internal.models.installation_report import InstallationReport
+from pip._internal.operations.build.build_tracker import get_build_tracker
+from pip._internal.operations.check import ConflictDetails, check_install_conflicts
+from pip._internal.req import install_given_reqs
+from pip._internal.req.req_install import (
+ InstallRequirement,
+ check_legacy_setup_py_options,
+)
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.filesystem import test_writable_dir
+from pip._internal.utils.logging import getLogger
+from pip._internal.utils.misc import (
+ check_externally_managed,
+ ensure_dir,
+ get_pip_version,
+ protect_pip_from_modification_on_windows,
+ warn_if_run_as_root,
+ write_output,
+)
+from pip._internal.utils.temp_dir import TempDirectory
+from pip._internal.utils.virtualenv import (
+ running_under_virtualenv,
+ virtualenv_no_global,
+)
+from pip._internal.wheel_builder import build, should_build_for_install_command
+
+logger = getLogger(__name__)
+
+
+class InstallCommand(RequirementCommand):
+ """
+ Install packages from:
+
+ - PyPI (and other indexes) using requirement specifiers.
+ - VCS project urls.
+ - Local project directories.
+ - Local or remote source archives.
+
+ pip also supports installing from "requirements files", which provide
+ an easy way to specify a whole environment to be installed.
+ """
+
+ usage = """
+ %prog [options] [package-index-options] ...
+ %prog [options] -r [package-index-options] ...
+ %prog [options] [-e] ...
+ %prog [options] [-e] ...
+ %prog [options] ..."""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(cmdoptions.requirements())
+ self.cmd_opts.add_option(cmdoptions.constraints())
+ self.cmd_opts.add_option(cmdoptions.no_deps())
+ self.cmd_opts.add_option(cmdoptions.pre())
+
+ self.cmd_opts.add_option(cmdoptions.editable())
+ self.cmd_opts.add_option(
+ "--dry-run",
+ action="store_true",
+ dest="dry_run",
+ default=False,
+ help=(
+ "Don't actually install anything, just print what would be. "
+ "Can be used in combination with --ignore-installed "
+ "to 'resolve' the requirements."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "-t",
+ "--target",
+ dest="target_dir",
+ metavar="dir",
+ default=None,
+ help=(
+ "Install packages into . "
+ "By default this will not replace existing files/folders in "
+ ". Use --upgrade to replace existing packages in "
+ "with new versions."
+ ),
+ )
+ cmdoptions.add_target_python_options(self.cmd_opts)
+
+ self.cmd_opts.add_option(
+ "--user",
+ dest="use_user_site",
+ action="store_true",
+ help=(
+ "Install to the Python user install directory for your "
+ "platform. Typically ~/.local/, or %APPDATA%\\Python on "
+ "Windows. (See the Python documentation for site.USER_BASE "
+ "for full details.)"
+ ),
+ )
+ self.cmd_opts.add_option(
+ "--no-user",
+ dest="use_user_site",
+ action="store_false",
+ help=SUPPRESS_HELP,
+ )
+ self.cmd_opts.add_option(
+ "--root",
+ dest="root_path",
+ metavar="dir",
+ default=None,
+ help="Install everything relative to this alternate root directory.",
+ )
+ self.cmd_opts.add_option(
+ "--prefix",
+ dest="prefix_path",
+ metavar="dir",
+ default=None,
+ help=(
+ "Installation prefix where lib, bin and other top-level "
+ "folders are placed. Note that the resulting installation may "
+ "contain scripts and other resources which reference the "
+ "Python interpreter of pip, and not that of ``--prefix``. "
+ "See also the ``--python`` option if the intention is to "
+ "install packages into another (possibly pip-free) "
+ "environment."
+ ),
+ )
+
+ self.cmd_opts.add_option(cmdoptions.src())
+
+ self.cmd_opts.add_option(
+ "-U",
+ "--upgrade",
+ dest="upgrade",
+ action="store_true",
+ help=(
+ "Upgrade all specified packages to the newest available "
+ "version. The handling of dependencies depends on the "
+ "upgrade-strategy used."
+ ),
+ )
+
+ self.cmd_opts.add_option(
+ "--upgrade-strategy",
+ dest="upgrade_strategy",
+ default="only-if-needed",
+ choices=["only-if-needed", "eager"],
+ help=(
+ "Determines how dependency upgrading should be handled "
+ "[default: %default]. "
+ '"eager" - dependencies are upgraded regardless of '
+ "whether the currently installed version satisfies the "
+ "requirements of the upgraded package(s). "
+ '"only-if-needed" - are upgraded only when they do not '
+ "satisfy the requirements of the upgraded package(s)."
+ ),
+ )
+
+ self.cmd_opts.add_option(
+ "--force-reinstall",
+ dest="force_reinstall",
+ action="store_true",
+ help="Reinstall all packages even if they are already up-to-date.",
+ )
+
+ self.cmd_opts.add_option(
+ "-I",
+ "--ignore-installed",
+ dest="ignore_installed",
+ action="store_true",
+ help=(
+ "Ignore the installed packages, overwriting them. "
+ "This can break your system if the existing package "
+ "is of a different version or was installed "
+ "with a different package manager!"
+ ),
+ )
+
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
+ self.cmd_opts.add_option(cmdoptions.no_build_isolation())
+ self.cmd_opts.add_option(cmdoptions.use_pep517())
+ self.cmd_opts.add_option(cmdoptions.no_use_pep517())
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
+ self.cmd_opts.add_option(cmdoptions.override_externally_managed())
+
+ self.cmd_opts.add_option(cmdoptions.config_settings())
+ self.cmd_opts.add_option(cmdoptions.global_options())
+
+ self.cmd_opts.add_option(
+ "--compile",
+ action="store_true",
+ dest="compile",
+ default=True,
+ help="Compile Python source files to bytecode",
+ )
+
+ self.cmd_opts.add_option(
+ "--no-compile",
+ action="store_false",
+ dest="compile",
+ help="Do not compile Python source files to bytecode",
+ )
+
+ self.cmd_opts.add_option(
+ "--no-warn-script-location",
+ action="store_false",
+ dest="warn_script_location",
+ default=True,
+ help="Do not warn when installing scripts outside PATH",
+ )
+ self.cmd_opts.add_option(
+ "--no-warn-conflicts",
+ action="store_false",
+ dest="warn_about_conflicts",
+ default=True,
+ help="Do not warn about broken dependencies",
+ )
+ self.cmd_opts.add_option(cmdoptions.no_binary())
+ self.cmd_opts.add_option(cmdoptions.only_binary())
+ self.cmd_opts.add_option(cmdoptions.prefer_binary())
+ self.cmd_opts.add_option(cmdoptions.require_hashes())
+ self.cmd_opts.add_option(cmdoptions.progress_bar())
+ self.cmd_opts.add_option(cmdoptions.root_user_action())
+
+ index_opts = cmdoptions.make_option_group(
+ cmdoptions.index_group,
+ self.parser,
+ )
+
+ self.parser.insert_option_group(0, index_opts)
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ self.cmd_opts.add_option(
+ "--report",
+ dest="json_report_file",
+ metavar="file",
+ default=None,
+ help=(
+ "Generate a JSON file describing what pip did to install "
+ "the provided requirements. "
+ "Can be used in combination with --dry-run and --ignore-installed "
+ "to 'resolve' the requirements. "
+ "When - is used as file name it writes to stdout. "
+ "When writing to stdout, please combine with the --quiet option "
+ "to avoid mixing pip logging output with JSON output."
+ ),
+ )
+
+ @with_cleanup
+ def run(self, options: Values, args: List[str]) -> int:
+ if options.use_user_site and options.target_dir is not None:
+ raise CommandError("Can not combine '--user' and '--target'")
+
+ # Check whether the environment we're installing into is externally
+ # managed, as specified in PEP 668. Specifying --root, --target, or
+ # --prefix disables the check, since there's no reliable way to locate
+ # the EXTERNALLY-MANAGED file for those cases. An exception is also
+ # made specifically for "--dry-run --report" for convenience.
+ installing_into_current_environment = (
+ not (options.dry_run and options.json_report_file)
+ and options.root_path is None
+ and options.target_dir is None
+ and options.prefix_path is None
+ )
+ if (
+ installing_into_current_environment
+ and not options.override_externally_managed
+ ):
+ check_externally_managed()
+
+ upgrade_strategy = "to-satisfy-only"
+ if options.upgrade:
+ upgrade_strategy = options.upgrade_strategy
+
+ cmdoptions.check_dist_restriction(options, check_target=True)
+
+ logger.verbose("Using %s", get_pip_version())
+ options.use_user_site = decide_user_install(
+ options.use_user_site,
+ prefix_path=options.prefix_path,
+ target_dir=options.target_dir,
+ root_path=options.root_path,
+ isolated_mode=options.isolated_mode,
+ )
+
+ target_temp_dir: Optional[TempDirectory] = None
+ target_temp_dir_path: Optional[str] = None
+ if options.target_dir:
+ options.ignore_installed = True
+ options.target_dir = os.path.abspath(options.target_dir)
+ if (
+ # fmt: off
+ os.path.exists(options.target_dir) and
+ not os.path.isdir(options.target_dir)
+ # fmt: on
+ ):
+ raise CommandError(
+ "Target path exists but is not a directory, will not continue."
+ )
+
+ # Create a target directory for using with the target option
+ target_temp_dir = TempDirectory(kind="target")
+ target_temp_dir_path = target_temp_dir.path
+ self.enter_context(target_temp_dir)
+
+ global_options = options.global_options or []
+
+ session = self.get_default_session(options)
+
+ target_python = make_target_python(options)
+ finder = self._build_package_finder(
+ options=options,
+ session=session,
+ target_python=target_python,
+ ignore_requires_python=options.ignore_requires_python,
+ )
+ build_tracker = self.enter_context(get_build_tracker())
+
+ directory = TempDirectory(
+ delete=not options.no_clean,
+ kind="install",
+ globally_managed=True,
+ )
+
+ try:
+ reqs = self.get_requirements(args, options, finder, session)
+ check_legacy_setup_py_options(options, reqs)
+
+ wheel_cache = WheelCache(options.cache_dir)
+
+ # Only when installing is it permitted to use PEP 660.
+ # In other circumstances (pip wheel, pip download) we generate
+ # regular (i.e. non editable) metadata and wheels.
+ for req in reqs:
+ req.permit_editable_wheels = True
+
+ preparer = self.make_requirement_preparer(
+ temp_build_dir=directory,
+ options=options,
+ build_tracker=build_tracker,
+ session=session,
+ finder=finder,
+ use_user_site=options.use_user_site,
+ verbosity=self.verbosity,
+ )
+ resolver = self.make_resolver(
+ preparer=preparer,
+ finder=finder,
+ options=options,
+ wheel_cache=wheel_cache,
+ use_user_site=options.use_user_site,
+ ignore_installed=options.ignore_installed,
+ ignore_requires_python=options.ignore_requires_python,
+ force_reinstall=options.force_reinstall,
+ upgrade_strategy=upgrade_strategy,
+ use_pep517=options.use_pep517,
+ py_version_info=options.python_version,
+ )
+
+ self.trace_basic_info(finder)
+
+ requirement_set = resolver.resolve(
+ reqs, check_supported_wheels=not options.target_dir
+ )
+
+ if options.json_report_file:
+ report = InstallationReport(requirement_set.requirements_to_install)
+ if options.json_report_file == "-":
+ print_json(data=report.to_dict())
+ else:
+ with open(options.json_report_file, "w", encoding="utf-8") as f:
+ json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
+
+ if options.dry_run:
+ would_install_items = sorted(
+ (r.metadata["name"], r.metadata["version"])
+ for r in requirement_set.requirements_to_install
+ )
+ if would_install_items:
+ write_output(
+ "Would install %s",
+ " ".join("-".join(item) for item in would_install_items),
+ )
+ return SUCCESS
+
+ try:
+ pip_req = requirement_set.get_requirement("pip")
+ except KeyError:
+ modifying_pip = False
+ else:
+ # If we're not replacing an already installed pip,
+ # we're not modifying it.
+ modifying_pip = pip_req.satisfied_by is None
+ if modifying_pip:
+ # Eagerly import this module to avoid crashes. Otherwise, this
+ # module would be imported *after* pip was replaced, resulting in
+ # crashes if the new self_outdated_check module was incompatible
+ # with the rest of pip that's already imported.
+ import pip._internal.self_outdated_check # noqa: F401
+ protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
+
+ reqs_to_build = [
+ r
+ for r in requirement_set.requirements.values()
+ if should_build_for_install_command(r)
+ ]
+
+ _, build_failures = build(
+ reqs_to_build,
+ wheel_cache=wheel_cache,
+ verify=True,
+ build_options=[],
+ global_options=global_options,
+ )
+
+ if build_failures:
+ raise InstallationError(
+ "ERROR: Failed to build installable wheels for some "
+ "pyproject.toml based projects ({})".format(
+ ", ".join(r.name for r in build_failures) # type: ignore
+ )
+ )
+
+ to_install = resolver.get_installation_order(requirement_set)
+
+ # Check for conflicts in the package set we're installing.
+ conflicts: Optional[ConflictDetails] = None
+ should_warn_about_conflicts = (
+ not options.ignore_dependencies and options.warn_about_conflicts
+ )
+ if should_warn_about_conflicts:
+ conflicts = self._determine_conflicts(to_install)
+
+ # Don't warn about script install locations if
+ # --target or --prefix has been specified
+ warn_script_location = options.warn_script_location
+ if options.target_dir or options.prefix_path:
+ warn_script_location = False
+
+ installed = install_given_reqs(
+ to_install,
+ global_options,
+ root=options.root_path,
+ home=target_temp_dir_path,
+ prefix=options.prefix_path,
+ warn_script_location=warn_script_location,
+ use_user_site=options.use_user_site,
+ pycompile=options.compile,
+ )
+
+ lib_locations = get_lib_location_guesses(
+ user=options.use_user_site,
+ home=target_temp_dir_path,
+ root=options.root_path,
+ prefix=options.prefix_path,
+ isolated=options.isolated_mode,
+ )
+ env = get_environment(lib_locations)
+
+ # Display a summary of installed packages, with extra care to
+ # display a package name as it was requested by the user.
+ installed.sort(key=operator.attrgetter("name"))
+ summary = []
+ installed_versions = {}
+ for distribution in env.iter_all_distributions():
+ installed_versions[distribution.canonical_name] = distribution.version
+ for package in installed:
+ display_name = package.name
+ version = installed_versions.get(canonicalize_name(display_name), None)
+ if version:
+ text = f"{display_name}-{version}"
+ else:
+ text = display_name
+ summary.append(text)
+
+ if conflicts is not None:
+ self._warn_about_conflicts(
+ conflicts,
+ resolver_variant=self.determine_resolver_variant(options),
+ )
+
+ installed_desc = " ".join(summary)
+ if installed_desc:
+ write_output(
+ "Successfully installed %s",
+ installed_desc,
+ )
+ except OSError as error:
+ show_traceback = self.verbosity >= 1
+
+ message = create_os_error_message(
+ error,
+ show_traceback,
+ options.use_user_site,
+ )
+ logger.error(message, exc_info=show_traceback)
+
+ return ERROR
+
+ if options.target_dir:
+ assert target_temp_dir
+ self._handle_target_dir(
+ options.target_dir, target_temp_dir, options.upgrade
+ )
+ if options.root_user_action == "warn":
+ warn_if_run_as_root()
+ return SUCCESS
+
+ def _handle_target_dir(
+ self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool
+ ) -> None:
+ ensure_dir(target_dir)
+
+ # Checking both purelib and platlib directories for installed
+ # packages to be moved to target directory
+ lib_dir_list = []
+
+ # Checking both purelib and platlib directories for installed
+ # packages to be moved to target directory
+ scheme = get_scheme("", home=target_temp_dir.path)
+ purelib_dir = scheme.purelib
+ platlib_dir = scheme.platlib
+ data_dir = scheme.data
+
+ if os.path.exists(purelib_dir):
+ lib_dir_list.append(purelib_dir)
+ if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
+ lib_dir_list.append(platlib_dir)
+ if os.path.exists(data_dir):
+ lib_dir_list.append(data_dir)
+
+ for lib_dir in lib_dir_list:
+ for item in os.listdir(lib_dir):
+ if lib_dir == data_dir:
+ ddir = os.path.join(data_dir, item)
+ if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
+ continue
+ target_item_dir = os.path.join(target_dir, item)
+ if os.path.exists(target_item_dir):
+ if not upgrade:
+ logger.warning(
+ "Target directory %s already exists. Specify "
+ "--upgrade to force replacement.",
+ target_item_dir,
+ )
+ continue
+ if os.path.islink(target_item_dir):
+ logger.warning(
+ "Target directory %s already exists and is "
+ "a link. pip will not automatically replace "
+ "links, please remove if replacement is "
+ "desired.",
+ target_item_dir,
+ )
+ continue
+ if os.path.isdir(target_item_dir):
+ shutil.rmtree(target_item_dir)
+ else:
+ os.remove(target_item_dir)
+
+ shutil.move(os.path.join(lib_dir, item), target_item_dir)
+
+ def _determine_conflicts(
+ self, to_install: List[InstallRequirement]
+ ) -> Optional[ConflictDetails]:
+ try:
+ return check_install_conflicts(to_install)
+ except Exception:
+ logger.exception(
+ "Error while checking for conflicts. Please file an issue on "
+ "pip's issue tracker: https://github.com/pypa/pip/issues/new"
+ )
+ return None
+
+ def _warn_about_conflicts(
+ self, conflict_details: ConflictDetails, resolver_variant: str
+ ) -> None:
+ package_set, (missing, conflicting) = conflict_details
+ if not missing and not conflicting:
+ return
+
+ parts: List[str] = []
+ if resolver_variant == "legacy":
+ parts.append(
+ "pip's legacy dependency resolver does not consider dependency "
+ "conflicts when selecting packages. This behaviour is the "
+ "source of the following dependency conflicts."
+ )
+ else:
+ assert resolver_variant == "resolvelib"
+ parts.append(
+ "pip's dependency resolver does not currently take into account "
+ "all the packages that are installed. This behaviour is the "
+ "source of the following dependency conflicts."
+ )
+
+ # NOTE: There is some duplication here, with commands/check.py
+ for project_name in missing:
+ version = package_set[project_name][0]
+ for dependency in missing[project_name]:
+ message = (
+ f"{project_name} {version} requires {dependency[1]}, "
+ "which is not installed."
+ )
+ parts.append(message)
+
+ for project_name in conflicting:
+ version = package_set[project_name][0]
+ for dep_name, dep_version, req in conflicting[project_name]:
+ message = (
+ "{name} {version} requires {requirement}, but {you} have "
+ "{dep_name} {dep_version} which is incompatible."
+ ).format(
+ name=project_name,
+ version=version,
+ requirement=req,
+ dep_name=dep_name,
+ dep_version=dep_version,
+ you=("you" if resolver_variant == "resolvelib" else "you'll"),
+ )
+ parts.append(message)
+
+ logger.critical("\n".join(parts))
+
+
+def get_lib_location_guesses(
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ isolated: bool = False,
+ prefix: Optional[str] = None,
+) -> List[str]:
+ scheme = get_scheme(
+ "",
+ user=user,
+ home=home,
+ root=root,
+ isolated=isolated,
+ prefix=prefix,
+ )
+ return [scheme.purelib, scheme.platlib]
+
+
+def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
+ return all(
+ test_writable_dir(d)
+ for d in set(get_lib_location_guesses(root=root, isolated=isolated))
+ )
+
+
+def decide_user_install(
+ use_user_site: Optional[bool],
+ prefix_path: Optional[str] = None,
+ target_dir: Optional[str] = None,
+ root_path: Optional[str] = None,
+ isolated_mode: bool = False,
+) -> bool:
+ """Determine whether to do a user install based on the input options.
+
+ If use_user_site is False, no additional checks are done.
+ If use_user_site is True, it is checked for compatibility with other
+ options.
+ If use_user_site is None, the default behaviour depends on the environment,
+ which is provided by the other arguments.
+ """
+ # In some cases (config from tox), use_user_site can be set to an integer
+ # rather than a bool, which 'use_user_site is False' wouldn't catch.
+ if (use_user_site is not None) and (not use_user_site):
+ logger.debug("Non-user install by explicit request")
+ return False
+
+ if use_user_site:
+ if prefix_path:
+ raise CommandError(
+ "Can not combine '--user' and '--prefix' as they imply "
+ "different installation locations"
+ )
+ if virtualenv_no_global():
+ raise InstallationError(
+ "Can not perform a '--user' install. User site-packages "
+ "are not visible in this virtualenv."
+ )
+ logger.debug("User install by explicit request")
+ return True
+
+ # If we are here, user installs have not been explicitly requested/avoided
+ assert use_user_site is None
+
+ # user install incompatible with --prefix/--target
+ if prefix_path or target_dir:
+ logger.debug("Non-user install due to --prefix or --target option")
+ return False
+
+ # If user installs are not enabled, choose a non-user install
+ if not site.ENABLE_USER_SITE:
+ logger.debug("Non-user install because user site-packages disabled")
+ return False
+
+ # If we have permission for a non-user install, do that,
+ # otherwise do a user install.
+ if site_packages_writable(root=root_path, isolated=isolated_mode):
+ logger.debug("Non-user install because site-packages writeable")
+ return False
+
+ logger.info(
+ "Defaulting to user installation because normal site-packages "
+ "is not writeable"
+ )
+ return True
+
+
+def create_os_error_message(
+ error: OSError, show_traceback: bool, using_user_site: bool
+) -> str:
+ """Format an error message for an OSError
+
+ It may occur anytime during the execution of the install command.
+ """
+ parts = []
+
+ # Mention the error if we are not going to show a traceback
+ parts.append("Could not install packages due to an OSError")
+ if not show_traceback:
+ parts.append(": ")
+ parts.append(str(error))
+ else:
+ parts.append(".")
+
+ # Spilt the error indication from a helper message (if any)
+ parts[-1] += "\n"
+
+ # Suggest useful actions to the user:
+ # (1) using user site-packages or (2) verifying the permissions
+ if error.errno == errno.EACCES:
+ user_option_part = "Consider using the `--user` option"
+ permissions_part = "Check the permissions"
+
+ if not running_under_virtualenv() and not using_user_site:
+ parts.extend(
+ [
+ user_option_part,
+ " or ",
+ permissions_part.lower(),
+ ]
+ )
+ else:
+ parts.append(permissions_part)
+ parts.append(".\n")
+
+ # Suggest the user to enable Long Paths if path length is
+ # more than 260
+ if (
+ WINDOWS
+ and error.errno == errno.ENOENT
+ and error.filename
+ and len(error.filename) > 260
+ ):
+ parts.append(
+ "HINT: This error might have occurred since "
+ "this system does not have Windows Long Path "
+ "support enabled. You can find information on "
+ "how to enable this at "
+ "https://pip.pypa.io/warnings/enable-long-paths\n"
+ )
+
+ return "".join(parts).strip() + "\n"
diff --git a/env/Lib/site-packages/pip/_internal/commands/list.py b/env/Lib/site-packages/pip/_internal/commands/list.py
new file mode 100644
index 0000000000000000000000000000000000000000..8494370241064698d5aa891a10fce6bf3b8c1722
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/list.py
@@ -0,0 +1,375 @@
+import json
+import logging
+from optparse import Values
+from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
+
+from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.packaging.version import Version
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.index_command import IndexGroupCommand
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.exceptions import CommandError
+from pip._internal.metadata import BaseDistribution, get_environment
+from pip._internal.models.selection_prefs import SelectionPreferences
+from pip._internal.utils.compat import stdlib_pkgs
+from pip._internal.utils.misc import tabulate, write_output
+
+if TYPE_CHECKING:
+ from pip._internal.index.package_finder import PackageFinder
+ from pip._internal.network.session import PipSession
+
+ class _DistWithLatestInfo(BaseDistribution):
+ """Give the distribution object a couple of extra fields.
+
+ These will be populated during ``get_outdated()``. This is dirty but
+ makes the rest of the code much cleaner.
+ """
+
+ latest_version: Version
+ latest_filetype: str
+
+ _ProcessedDists = Sequence[_DistWithLatestInfo]
+
+
+logger = logging.getLogger(__name__)
+
+
+class ListCommand(IndexGroupCommand):
+ """
+ List installed packages, including editables.
+
+ Packages are listed in a case-insensitive sorted order.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog [options]"""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-o",
+ "--outdated",
+ action="store_true",
+ default=False,
+ help="List outdated packages",
+ )
+ self.cmd_opts.add_option(
+ "-u",
+ "--uptodate",
+ action="store_true",
+ default=False,
+ help="List uptodate packages",
+ )
+ self.cmd_opts.add_option(
+ "-e",
+ "--editable",
+ action="store_true",
+ default=False,
+ help="List editable projects.",
+ )
+ self.cmd_opts.add_option(
+ "-l",
+ "--local",
+ action="store_true",
+ default=False,
+ help=(
+ "If in a virtualenv that has global access, do not list "
+ "globally-installed packages."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "--user",
+ dest="user",
+ action="store_true",
+ default=False,
+ help="Only output packages installed in user-site.",
+ )
+ self.cmd_opts.add_option(cmdoptions.list_path())
+ self.cmd_opts.add_option(
+ "--pre",
+ action="store_true",
+ default=False,
+ help=(
+ "Include pre-release and development versions. By default, "
+ "pip only finds stable versions."
+ ),
+ )
+
+ self.cmd_opts.add_option(
+ "--format",
+ action="store",
+ dest="list_format",
+ default="columns",
+ choices=("columns", "freeze", "json"),
+ help=(
+ "Select the output format among: columns (default), freeze, or json. "
+ "The 'freeze' format cannot be used with the --outdated option."
+ ),
+ )
+
+ self.cmd_opts.add_option(
+ "--not-required",
+ action="store_true",
+ dest="not_required",
+ help="List packages that are not dependencies of installed packages.",
+ )
+
+ self.cmd_opts.add_option(
+ "--exclude-editable",
+ action="store_false",
+ dest="include_editable",
+ help="Exclude editable package from output.",
+ )
+ self.cmd_opts.add_option(
+ "--include-editable",
+ action="store_true",
+ dest="include_editable",
+ help="Include editable package from output.",
+ default=True,
+ )
+ self.cmd_opts.add_option(cmdoptions.list_exclude())
+ index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser)
+
+ self.parser.insert_option_group(0, index_opts)
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def handle_pip_version_check(self, options: Values) -> None:
+ if options.outdated or options.uptodate:
+ super().handle_pip_version_check(options)
+
+ def _build_package_finder(
+ self, options: Values, session: "PipSession"
+ ) -> "PackageFinder":
+ """
+ Create a package finder appropriate to this list command.
+ """
+ # Lazy import the heavy index modules as most list invocations won't need 'em.
+ from pip._internal.index.collector import LinkCollector
+ from pip._internal.index.package_finder import PackageFinder
+
+ link_collector = LinkCollector.create(session, options=options)
+
+ # Pass allow_yanked=False to ignore yanked versions.
+ selection_prefs = SelectionPreferences(
+ allow_yanked=False,
+ allow_all_prereleases=options.pre,
+ )
+
+ return PackageFinder.create(
+ link_collector=link_collector,
+ selection_prefs=selection_prefs,
+ )
+
+ def run(self, options: Values, args: List[str]) -> int:
+ if options.outdated and options.uptodate:
+ raise CommandError("Options --outdated and --uptodate cannot be combined.")
+
+ if options.outdated and options.list_format == "freeze":
+ raise CommandError(
+ "List format 'freeze' cannot be used with the --outdated option."
+ )
+
+ cmdoptions.check_list_path_option(options)
+
+ skip = set(stdlib_pkgs)
+ if options.excludes:
+ skip.update(canonicalize_name(n) for n in options.excludes)
+
+ packages: _ProcessedDists = [
+ cast("_DistWithLatestInfo", d)
+ for d in get_environment(options.path).iter_installed_distributions(
+ local_only=options.local,
+ user_only=options.user,
+ editables_only=options.editable,
+ include_editables=options.include_editable,
+ skip=skip,
+ )
+ ]
+
+ # get_not_required must be called firstly in order to find and
+ # filter out all dependencies correctly. Otherwise a package
+ # can't be identified as requirement because some parent packages
+ # could be filtered out before.
+ if options.not_required:
+ packages = self.get_not_required(packages, options)
+
+ if options.outdated:
+ packages = self.get_outdated(packages, options)
+ elif options.uptodate:
+ packages = self.get_uptodate(packages, options)
+
+ self.output_package_listing(packages, options)
+ return SUCCESS
+
+ def get_outdated(
+ self, packages: "_ProcessedDists", options: Values
+ ) -> "_ProcessedDists":
+ return [
+ dist
+ for dist in self.iter_packages_latest_infos(packages, options)
+ if dist.latest_version > dist.version
+ ]
+
+ def get_uptodate(
+ self, packages: "_ProcessedDists", options: Values
+ ) -> "_ProcessedDists":
+ return [
+ dist
+ for dist in self.iter_packages_latest_infos(packages, options)
+ if dist.latest_version == dist.version
+ ]
+
+ def get_not_required(
+ self, packages: "_ProcessedDists", options: Values
+ ) -> "_ProcessedDists":
+ dep_keys = {
+ canonicalize_name(dep.name)
+ for dist in packages
+ for dep in (dist.iter_dependencies() or ())
+ }
+
+ # Create a set to remove duplicate packages, and cast it to a list
+ # to keep the return type consistent with get_outdated and
+ # get_uptodate
+ return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys})
+
+ def iter_packages_latest_infos(
+ self, packages: "_ProcessedDists", options: Values
+ ) -> Generator["_DistWithLatestInfo", None, None]:
+ with self._build_session(options) as session:
+ finder = self._build_package_finder(options, session)
+
+ def latest_info(
+ dist: "_DistWithLatestInfo",
+ ) -> Optional["_DistWithLatestInfo"]:
+ all_candidates = finder.find_all_candidates(dist.canonical_name)
+ if not options.pre:
+ # Remove prereleases
+ all_candidates = [
+ candidate
+ for candidate in all_candidates
+ if not candidate.version.is_prerelease
+ ]
+
+ evaluator = finder.make_candidate_evaluator(
+ project_name=dist.canonical_name,
+ )
+ best_candidate = evaluator.sort_best_candidate(all_candidates)
+ if best_candidate is None:
+ return None
+
+ remote_version = best_candidate.version
+ if best_candidate.link.is_wheel:
+ typ = "wheel"
+ else:
+ typ = "sdist"
+ dist.latest_version = remote_version
+ dist.latest_filetype = typ
+ return dist
+
+ for dist in map(latest_info, packages):
+ if dist is not None:
+ yield dist
+
+ def output_package_listing(
+ self, packages: "_ProcessedDists", options: Values
+ ) -> None:
+ packages = sorted(
+ packages,
+ key=lambda dist: dist.canonical_name,
+ )
+ if options.list_format == "columns" and packages:
+ data, header = format_for_columns(packages, options)
+ self.output_package_listing_columns(data, header)
+ elif options.list_format == "freeze":
+ for dist in packages:
+ if options.verbose >= 1:
+ write_output(
+ "%s==%s (%s)", dist.raw_name, dist.version, dist.location
+ )
+ else:
+ write_output("%s==%s", dist.raw_name, dist.version)
+ elif options.list_format == "json":
+ write_output(format_for_json(packages, options))
+
+ def output_package_listing_columns(
+ self, data: List[List[str]], header: List[str]
+ ) -> None:
+ # insert the header first: we need to know the size of column names
+ if len(data) > 0:
+ data.insert(0, header)
+
+ pkg_strings, sizes = tabulate(data)
+
+ # Create and add a separator.
+ if len(data) > 0:
+ pkg_strings.insert(1, " ".join("-" * x for x in sizes))
+
+ for val in pkg_strings:
+ write_output(val)
+
+
+def format_for_columns(
+ pkgs: "_ProcessedDists", options: Values
+) -> Tuple[List[List[str]], List[str]]:
+ """
+ Convert the package data into something usable
+ by output_package_listing_columns.
+ """
+ header = ["Package", "Version"]
+
+ running_outdated = options.outdated
+ if running_outdated:
+ header.extend(["Latest", "Type"])
+
+ has_editables = any(x.editable for x in pkgs)
+ if has_editables:
+ header.append("Editable project location")
+
+ if options.verbose >= 1:
+ header.append("Location")
+ if options.verbose >= 1:
+ header.append("Installer")
+
+ data = []
+ for proj in pkgs:
+ # if we're working on the 'outdated' list, separate out the
+ # latest_version and type
+ row = [proj.raw_name, proj.raw_version]
+
+ if running_outdated:
+ row.append(str(proj.latest_version))
+ row.append(proj.latest_filetype)
+
+ if has_editables:
+ row.append(proj.editable_project_location or "")
+
+ if options.verbose >= 1:
+ row.append(proj.location or "")
+ if options.verbose >= 1:
+ row.append(proj.installer)
+
+ data.append(row)
+
+ return data, header
+
+
+def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
+ data = []
+ for dist in packages:
+ info = {
+ "name": dist.raw_name,
+ "version": str(dist.version),
+ }
+ if options.verbose >= 1:
+ info["location"] = dist.location or ""
+ info["installer"] = dist.installer
+ if options.outdated:
+ info["latest_version"] = str(dist.latest_version)
+ info["latest_filetype"] = dist.latest_filetype
+ editable_project_location = dist.editable_project_location
+ if editable_project_location:
+ info["editable_project_location"] = editable_project_location
+ data.append(info)
+ return json.dumps(data)
diff --git a/env/Lib/site-packages/pip/_internal/commands/search.py b/env/Lib/site-packages/pip/_internal/commands/search.py
new file mode 100644
index 0000000000000000000000000000000000000000..74b8d656b4749d15cc47aebccd9ca22f74357f25
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/search.py
@@ -0,0 +1,172 @@
+import logging
+import shutil
+import sys
+import textwrap
+import xmlrpc.client
+from collections import OrderedDict
+from optparse import Values
+from typing import TYPE_CHECKING, Dict, List, Optional, TypedDict
+
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.req_command import SessionCommandMixin
+from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
+from pip._internal.exceptions import CommandError
+from pip._internal.metadata import get_default_environment
+from pip._internal.models.index import PyPI
+from pip._internal.network.xmlrpc import PipXmlrpcTransport
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.misc import write_output
+
+if TYPE_CHECKING:
+
+ class TransformedHit(TypedDict):
+ name: str
+ summary: str
+ versions: List[str]
+
+
+logger = logging.getLogger(__name__)
+
+
+class SearchCommand(Command, SessionCommandMixin):
+ """Search for PyPI packages whose name or summary contains ."""
+
+ usage = """
+ %prog [options] """
+ ignore_require_venv = True
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-i",
+ "--index",
+ dest="index",
+ metavar="URL",
+ default=PyPI.pypi_url,
+ help="Base URL of Python Package Index (default %default)",
+ )
+
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ if not args:
+ raise CommandError("Missing required argument (search query).")
+ query = args
+ pypi_hits = self.search(query, options)
+ hits = transform_hits(pypi_hits)
+
+ terminal_width = None
+ if sys.stdout.isatty():
+ terminal_width = shutil.get_terminal_size()[0]
+
+ print_results(hits, terminal_width=terminal_width)
+ if pypi_hits:
+ return SUCCESS
+ return NO_MATCHES_FOUND
+
+ def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
+ index_url = options.index
+
+ session = self.get_default_session(options)
+
+ transport = PipXmlrpcTransport(index_url, session)
+ pypi = xmlrpc.client.ServerProxy(index_url, transport)
+ try:
+ hits = pypi.search({"name": query, "summary": query}, "or")
+ except xmlrpc.client.Fault as fault:
+ message = (
+ f"XMLRPC request failed [code: {fault.faultCode}]\n{fault.faultString}"
+ )
+ raise CommandError(message)
+ assert isinstance(hits, list)
+ return hits
+
+
+def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
+ """
+ The list from pypi is really a list of versions. We want a list of
+ packages with the list of versions stored inline. This converts the
+ list from pypi into one we can use.
+ """
+ packages: Dict[str, TransformedHit] = OrderedDict()
+ for hit in hits:
+ name = hit["name"]
+ summary = hit["summary"]
+ version = hit["version"]
+
+ if name not in packages.keys():
+ packages[name] = {
+ "name": name,
+ "summary": summary,
+ "versions": [version],
+ }
+ else:
+ packages[name]["versions"].append(version)
+
+ # if this is the highest version, replace summary and score
+ if version == highest_version(packages[name]["versions"]):
+ packages[name]["summary"] = summary
+
+ return list(packages.values())
+
+
+def print_dist_installation_info(name: str, latest: str) -> None:
+ env = get_default_environment()
+ dist = env.get_distribution(name)
+ if dist is not None:
+ with indent_log():
+ if dist.version == latest:
+ write_output("INSTALLED: %s (latest)", dist.version)
+ else:
+ write_output("INSTALLED: %s", dist.version)
+ if parse_version(latest).pre:
+ write_output(
+ "LATEST: %s (pre-release; install"
+ " with `pip install --pre`)",
+ latest,
+ )
+ else:
+ write_output("LATEST: %s", latest)
+
+
+def print_results(
+ hits: List["TransformedHit"],
+ name_column_width: Optional[int] = None,
+ terminal_width: Optional[int] = None,
+) -> None:
+ if not hits:
+ return
+ if name_column_width is None:
+ name_column_width = (
+ max(
+ [
+ len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))
+ for hit in hits
+ ]
+ )
+ + 4
+ )
+
+ for hit in hits:
+ name = hit["name"]
+ summary = hit["summary"] or ""
+ latest = highest_version(hit.get("versions", ["-"]))
+ if terminal_width is not None:
+ target_width = terminal_width - name_column_width - 5
+ if target_width > 10:
+ # wrap and indent summary to fit terminal
+ summary_lines = textwrap.wrap(summary, target_width)
+ summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines)
+
+ name_latest = f"{name} ({latest})"
+ line = f"{name_latest:{name_column_width}} - {summary}"
+ try:
+ write_output(line)
+ print_dist_installation_info(name, latest)
+ except UnicodeEncodeError:
+ pass
+
+
+def highest_version(versions: List[str]) -> str:
+ return max(versions, key=parse_version)
diff --git a/env/Lib/site-packages/pip/_internal/commands/show.py b/env/Lib/site-packages/pip/_internal/commands/show.py
new file mode 100644
index 0000000000000000000000000000000000000000..c54d548f5fb4c0fa9fa64c15861ba2fa7eda4bdc
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/show.py
@@ -0,0 +1,217 @@
+import logging
+from optparse import Values
+from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
+
+from pip._vendor.packaging.requirements import InvalidRequirement
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.status_codes import ERROR, SUCCESS
+from pip._internal.metadata import BaseDistribution, get_default_environment
+from pip._internal.utils.misc import write_output
+
+logger = logging.getLogger(__name__)
+
+
+class ShowCommand(Command):
+ """
+ Show information about one or more installed packages.
+
+ The output is in RFC-compliant mail header format.
+ """
+
+ usage = """
+ %prog [options] ..."""
+ ignore_require_venv = True
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-f",
+ "--files",
+ dest="files",
+ action="store_true",
+ default=False,
+ help="Show the full list of installed files for each package.",
+ )
+
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ if not args:
+ logger.warning("ERROR: Please provide a package name or names.")
+ return ERROR
+ query = args
+
+ results = search_packages_info(query)
+ if not print_results(
+ results, list_files=options.files, verbose=options.verbose
+ ):
+ return ERROR
+ return SUCCESS
+
+
+class _PackageInfo(NamedTuple):
+ name: str
+ version: str
+ location: str
+ editable_project_location: Optional[str]
+ requires: List[str]
+ required_by: List[str]
+ installer: str
+ metadata_version: str
+ classifiers: List[str]
+ summary: str
+ homepage: str
+ project_urls: List[str]
+ author: str
+ author_email: str
+ license: str
+ entry_points: List[str]
+ files: Optional[List[str]]
+
+
+def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
+ """
+ Gather details from installed distributions. Print distribution name,
+ version, location, and installed files. Installed files requires a
+ pip generated 'installed-files.txt' in the distributions '.egg-info'
+ directory.
+ """
+ env = get_default_environment()
+
+ installed = {dist.canonical_name: dist for dist in env.iter_all_distributions()}
+ query_names = [canonicalize_name(name) for name in query]
+ missing = sorted(
+ [name for name, pkg in zip(query, query_names) if pkg not in installed]
+ )
+ if missing:
+ logger.warning("Package(s) not found: %s", ", ".join(missing))
+
+ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]:
+ return (
+ dist.metadata["Name"] or "UNKNOWN"
+ for dist in installed.values()
+ if current_dist.canonical_name
+ in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
+ )
+
+ for query_name in query_names:
+ try:
+ dist = installed[query_name]
+ except KeyError:
+ continue
+
+ try:
+ requires = sorted(
+ # Avoid duplicates in requirements (e.g. due to environment markers).
+ {req.name for req in dist.iter_dependencies()},
+ key=str.lower,
+ )
+ except InvalidRequirement:
+ requires = sorted(dist.iter_raw_dependencies(), key=str.lower)
+
+ try:
+ required_by = sorted(_get_requiring_packages(dist), key=str.lower)
+ except InvalidRequirement:
+ required_by = ["#N/A"]
+
+ try:
+ entry_points_text = dist.read_text("entry_points.txt")
+ entry_points = entry_points_text.splitlines(keepends=False)
+ except FileNotFoundError:
+ entry_points = []
+
+ files_iter = dist.iter_declared_entries()
+ if files_iter is None:
+ files: Optional[List[str]] = None
+ else:
+ files = sorted(files_iter)
+
+ metadata = dist.metadata
+
+ project_urls = metadata.get_all("Project-URL", [])
+ homepage = metadata.get("Home-page", "")
+ if not homepage:
+ # It's common that there is a "homepage" Project-URL, but Home-page
+ # remains unset (especially as PEP 621 doesn't surface the field).
+ #
+ # This logic was taken from PyPI's codebase.
+ for url in project_urls:
+ url_label, url = url.split(",", maxsplit=1)
+ normalized_label = (
+ url_label.casefold().replace("-", "").replace("_", "").strip()
+ )
+ if normalized_label == "homepage":
+ homepage = url.strip()
+ break
+
+ yield _PackageInfo(
+ name=dist.raw_name,
+ version=dist.raw_version,
+ location=dist.location or "",
+ editable_project_location=dist.editable_project_location,
+ requires=requires,
+ required_by=required_by,
+ installer=dist.installer,
+ metadata_version=dist.metadata_version or "",
+ classifiers=metadata.get_all("Classifier", []),
+ summary=metadata.get("Summary", ""),
+ homepage=homepage,
+ project_urls=project_urls,
+ author=metadata.get("Author", ""),
+ author_email=metadata.get("Author-email", ""),
+ license=metadata.get("License", ""),
+ entry_points=entry_points,
+ files=files,
+ )
+
+
+def print_results(
+ distributions: Iterable[_PackageInfo],
+ list_files: bool,
+ verbose: bool,
+) -> bool:
+ """
+ Print the information from installed distributions found.
+ """
+ results_printed = False
+ for i, dist in enumerate(distributions):
+ results_printed = True
+ if i > 0:
+ write_output("---")
+
+ write_output("Name: %s", dist.name)
+ write_output("Version: %s", dist.version)
+ write_output("Summary: %s", dist.summary)
+ write_output("Home-page: %s", dist.homepage)
+ write_output("Author: %s", dist.author)
+ write_output("Author-email: %s", dist.author_email)
+ write_output("License: %s", dist.license)
+ write_output("Location: %s", dist.location)
+ if dist.editable_project_location is not None:
+ write_output(
+ "Editable project location: %s", dist.editable_project_location
+ )
+ write_output("Requires: %s", ", ".join(dist.requires))
+ write_output("Required-by: %s", ", ".join(dist.required_by))
+
+ if verbose:
+ write_output("Metadata-Version: %s", dist.metadata_version)
+ write_output("Installer: %s", dist.installer)
+ write_output("Classifiers:")
+ for classifier in dist.classifiers:
+ write_output(" %s", classifier)
+ write_output("Entry-points:")
+ for entry in dist.entry_points:
+ write_output(" %s", entry.strip())
+ write_output("Project-URLs:")
+ for project_url in dist.project_urls:
+ write_output(" %s", project_url)
+ if list_files:
+ write_output("Files:")
+ if dist.files is None:
+ write_output("Cannot locate RECORD or installed-files.txt")
+ else:
+ for line in dist.files:
+ write_output(" %s", line.strip())
+ return results_printed
diff --git a/env/Lib/site-packages/pip/_internal/commands/uninstall.py b/env/Lib/site-packages/pip/_internal/commands/uninstall.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc0edeac9fb74e4dfb6d7a095483c11557d268ba
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/uninstall.py
@@ -0,0 +1,114 @@
+import logging
+from optparse import Values
+from typing import List
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.base_command import Command
+from pip._internal.cli.index_command import SessionCommandMixin
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.exceptions import InstallationError
+from pip._internal.req import parse_requirements
+from pip._internal.req.constructors import (
+ install_req_from_line,
+ install_req_from_parsed_requirement,
+)
+from pip._internal.utils.misc import (
+ check_externally_managed,
+ protect_pip_from_modification_on_windows,
+ warn_if_run_as_root,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class UninstallCommand(Command, SessionCommandMixin):
+ """
+ Uninstall packages.
+
+ pip is able to uninstall most installed packages. Known exceptions are:
+
+ - Pure distutils packages installed with ``python setup.py install``, which
+ leave behind no metadata to determine what files were installed.
+ - Script wrappers installed by ``python setup.py develop``.
+ """
+
+ usage = """
+ %prog [options] ...
+ %prog [options] -r ..."""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
+ default=[],
+ metavar="file",
+ help=(
+ "Uninstall all the packages listed in the given requirements "
+ "file. This option can be used multiple times."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "-y",
+ "--yes",
+ dest="yes",
+ action="store_true",
+ help="Don't ask for confirmation of uninstall deletions.",
+ )
+ self.cmd_opts.add_option(cmdoptions.root_user_action())
+ self.cmd_opts.add_option(cmdoptions.override_externally_managed())
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ session = self.get_default_session(options)
+
+ reqs_to_uninstall = {}
+ for name in args:
+ req = install_req_from_line(
+ name,
+ isolated=options.isolated_mode,
+ )
+ if req.name:
+ reqs_to_uninstall[canonicalize_name(req.name)] = req
+ else:
+ logger.warning(
+ "Invalid requirement: %r ignored -"
+ " the uninstall command expects named"
+ " requirements.",
+ name,
+ )
+ for filename in options.requirements:
+ for parsed_req in parse_requirements(
+ filename, options=options, session=session
+ ):
+ req = install_req_from_parsed_requirement(
+ parsed_req, isolated=options.isolated_mode
+ )
+ if req.name:
+ reqs_to_uninstall[canonicalize_name(req.name)] = req
+ if not reqs_to_uninstall:
+ raise InstallationError(
+ f"You must give at least one requirement to {self.name} (see "
+ f'"pip help {self.name}")'
+ )
+
+ if not options.override_externally_managed:
+ check_externally_managed()
+
+ protect_pip_from_modification_on_windows(
+ modifying_pip="pip" in reqs_to_uninstall
+ )
+
+ for req in reqs_to_uninstall.values():
+ uninstall_pathset = req.uninstall(
+ auto_confirm=options.yes,
+ verbose=self.verbosity > 0,
+ )
+ if uninstall_pathset:
+ uninstall_pathset.commit()
+ if options.root_user_action == "warn":
+ warn_if_run_as_root()
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/commands/wheel.py b/env/Lib/site-packages/pip/_internal/commands/wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..278719f4e0c6643e5f01bccdd440b1504bf5f7c9
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/commands/wheel.py
@@ -0,0 +1,182 @@
+import logging
+import os
+import shutil
+from optparse import Values
+from typing import List
+
+from pip._internal.cache import WheelCache
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.req_command import RequirementCommand, with_cleanup
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.exceptions import CommandError
+from pip._internal.operations.build.build_tracker import get_build_tracker
+from pip._internal.req.req_install import (
+ InstallRequirement,
+ check_legacy_setup_py_options,
+)
+from pip._internal.utils.misc import ensure_dir, normalize_path
+from pip._internal.utils.temp_dir import TempDirectory
+from pip._internal.wheel_builder import build, should_build_for_wheel_command
+
+logger = logging.getLogger(__name__)
+
+
+class WheelCommand(RequirementCommand):
+ """
+ Build Wheel archives for your requirements and dependencies.
+
+ Wheel is a built-package format, and offers the advantage of not
+ recompiling your software during every install. For more details, see the
+ wheel docs: https://wheel.readthedocs.io/en/latest/
+
+ 'pip wheel' uses the build system interface as described here:
+ https://pip.pypa.io/en/stable/reference/build-system/
+
+ """
+
+ usage = """
+ %prog [options] ...
+ %prog [options] -r ...
+ %prog [options] [-e] ...
+ %prog [options] [-e] ...
+ %prog [options] ..."""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "-w",
+ "--wheel-dir",
+ dest="wheel_dir",
+ metavar="dir",
+ default=os.curdir,
+ help=(
+ "Build wheels into , where the default is the "
+ "current working directory."
+ ),
+ )
+ self.cmd_opts.add_option(cmdoptions.no_binary())
+ self.cmd_opts.add_option(cmdoptions.only_binary())
+ self.cmd_opts.add_option(cmdoptions.prefer_binary())
+ self.cmd_opts.add_option(cmdoptions.no_build_isolation())
+ self.cmd_opts.add_option(cmdoptions.use_pep517())
+ self.cmd_opts.add_option(cmdoptions.no_use_pep517())
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
+ self.cmd_opts.add_option(cmdoptions.constraints())
+ self.cmd_opts.add_option(cmdoptions.editable())
+ self.cmd_opts.add_option(cmdoptions.requirements())
+ self.cmd_opts.add_option(cmdoptions.src())
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
+ self.cmd_opts.add_option(cmdoptions.no_deps())
+ self.cmd_opts.add_option(cmdoptions.progress_bar())
+
+ self.cmd_opts.add_option(
+ "--no-verify",
+ dest="no_verify",
+ action="store_true",
+ default=False,
+ help="Don't verify if built wheel is valid.",
+ )
+
+ self.cmd_opts.add_option(cmdoptions.config_settings())
+ self.cmd_opts.add_option(cmdoptions.build_options())
+ self.cmd_opts.add_option(cmdoptions.global_options())
+
+ self.cmd_opts.add_option(
+ "--pre",
+ action="store_true",
+ default=False,
+ help=(
+ "Include pre-release and development versions. By default, "
+ "pip only finds stable versions."
+ ),
+ )
+
+ self.cmd_opts.add_option(cmdoptions.require_hashes())
+
+ index_opts = cmdoptions.make_option_group(
+ cmdoptions.index_group,
+ self.parser,
+ )
+
+ self.parser.insert_option_group(0, index_opts)
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ @with_cleanup
+ def run(self, options: Values, args: List[str]) -> int:
+ session = self.get_default_session(options)
+
+ finder = self._build_package_finder(options, session)
+
+ options.wheel_dir = normalize_path(options.wheel_dir)
+ ensure_dir(options.wheel_dir)
+
+ build_tracker = self.enter_context(get_build_tracker())
+
+ directory = TempDirectory(
+ delete=not options.no_clean,
+ kind="wheel",
+ globally_managed=True,
+ )
+
+ reqs = self.get_requirements(args, options, finder, session)
+ check_legacy_setup_py_options(options, reqs)
+
+ wheel_cache = WheelCache(options.cache_dir)
+
+ preparer = self.make_requirement_preparer(
+ temp_build_dir=directory,
+ options=options,
+ build_tracker=build_tracker,
+ session=session,
+ finder=finder,
+ download_dir=options.wheel_dir,
+ use_user_site=False,
+ verbosity=self.verbosity,
+ )
+
+ resolver = self.make_resolver(
+ preparer=preparer,
+ finder=finder,
+ options=options,
+ wheel_cache=wheel_cache,
+ ignore_requires_python=options.ignore_requires_python,
+ use_pep517=options.use_pep517,
+ )
+
+ self.trace_basic_info(finder)
+
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
+
+ reqs_to_build: List[InstallRequirement] = []
+ for req in requirement_set.requirements.values():
+ if req.is_wheel:
+ preparer.save_linked_requirement(req)
+ elif should_build_for_wheel_command(req):
+ reqs_to_build.append(req)
+
+ preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
+
+ # build wheels
+ build_successes, build_failures = build(
+ reqs_to_build,
+ wheel_cache=wheel_cache,
+ verify=(not options.no_verify),
+ build_options=options.build_options or [],
+ global_options=options.global_options or [],
+ )
+ for req in build_successes:
+ assert req.link and req.link.is_wheel
+ assert req.local_file_path
+ # copy from cache to target directory
+ try:
+ shutil.copy(req.local_file_path, options.wheel_dir)
+ except OSError as e:
+ logger.warning(
+ "Building wheel for %s failed: %s",
+ req.name,
+ e,
+ )
+ build_failures.append(req)
+ if len(build_failures) != 0:
+ raise CommandError("Failed to build one or more wheels")
+
+ return SUCCESS
diff --git a/env/Lib/site-packages/pip/_internal/configuration.py b/env/Lib/site-packages/pip/_internal/configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..c25273d5f0be0c2a95948853cd3442d14ea954b6
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/configuration.py
@@ -0,0 +1,383 @@
+"""Configuration management setup
+
+Some terminology:
+- name
+ As written in config files.
+- value
+ Value associated with a name
+- key
+ Name combined with it's section (section.name)
+- variant
+ A single word describing where the configuration key-value pair came from
+"""
+
+import configparser
+import locale
+import os
+import sys
+from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
+
+from pip._internal.exceptions import (
+ ConfigurationError,
+ ConfigurationFileCouldNotBeLoaded,
+)
+from pip._internal.utils import appdirs
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.logging import getLogger
+from pip._internal.utils.misc import ensure_dir, enum
+
+RawConfigParser = configparser.RawConfigParser # Shorthand
+Kind = NewType("Kind", str)
+
+CONFIG_BASENAME = "pip.ini" if WINDOWS else "pip.conf"
+ENV_NAMES_IGNORED = "version", "help"
+
+# The kinds of configurations there are.
+kinds = enum(
+ USER="user", # User Specific
+ GLOBAL="global", # System Wide
+ SITE="site", # [Virtual] Environment Specific
+ ENV="env", # from PIP_CONFIG_FILE
+ ENV_VAR="env-var", # from Environment Variables
+)
+OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
+VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
+
+logger = getLogger(__name__)
+
+
+# NOTE: Maybe use the optionx attribute to normalize keynames.
+def _normalize_name(name: str) -> str:
+ """Make a name consistent regardless of source (environment or file)"""
+ name = name.lower().replace("_", "-")
+ if name.startswith("--"):
+ name = name[2:] # only prefer long opts
+ return name
+
+
+def _disassemble_key(name: str) -> List[str]:
+ if "." not in name:
+ error_message = (
+ "Key does not contain dot separated section and key. "
+ f"Perhaps you wanted to use 'global.{name}' instead?"
+ )
+ raise ConfigurationError(error_message)
+ return name.split(".", 1)
+
+
+def get_configuration_files() -> Dict[Kind, List[str]]:
+ global_config_files = [
+ os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
+ ]
+
+ site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
+ legacy_config_file = os.path.join(
+ os.path.expanduser("~"),
+ "pip" if WINDOWS else ".pip",
+ CONFIG_BASENAME,
+ )
+ new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
+ return {
+ kinds.GLOBAL: global_config_files,
+ kinds.SITE: [site_config_file],
+ kinds.USER: [legacy_config_file, new_config_file],
+ }
+
+
+class Configuration:
+ """Handles management of configuration.
+
+ Provides an interface to accessing and managing configuration files.
+
+ This class converts provides an API that takes "section.key-name" style
+ keys and stores the value associated with it as "key-name" under the
+ section "section".
+
+ This allows for a clean interface wherein the both the section and the
+ key-name are preserved in an easy to manage form in the configuration files
+ and the data stored is also nice.
+ """
+
+ def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None:
+ super().__init__()
+
+ if load_only is not None and load_only not in VALID_LOAD_ONLY:
+ raise ConfigurationError(
+ "Got invalid value for load_only - should be one of {}".format(
+ ", ".join(map(repr, VALID_LOAD_ONLY))
+ )
+ )
+ self.isolated = isolated
+ self.load_only = load_only
+
+ # Because we keep track of where we got the data from
+ self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
+ variant: [] for variant in OVERRIDE_ORDER
+ }
+ self._config: Dict[Kind, Dict[str, Any]] = {
+ variant: {} for variant in OVERRIDE_ORDER
+ }
+ self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
+
+ def load(self) -> None:
+ """Loads configuration from configuration files and environment"""
+ self._load_config_files()
+ if not self.isolated:
+ self._load_environment_vars()
+
+ def get_file_to_edit(self) -> Optional[str]:
+ """Returns the file with highest priority in configuration"""
+ assert self.load_only is not None, "Need to be specified a file to be editing"
+
+ try:
+ return self._get_parser_to_modify()[0]
+ except IndexError:
+ return None
+
+ def items(self) -> Iterable[Tuple[str, Any]]:
+ """Returns key-value pairs like dict.items() representing the loaded
+ configuration
+ """
+ return self._dictionary.items()
+
+ def get_value(self, key: str) -> Any:
+ """Get a value from the configuration."""
+ orig_key = key
+ key = _normalize_name(key)
+ try:
+ return self._dictionary[key]
+ except KeyError:
+ # disassembling triggers a more useful error message than simply
+ # "No such key" in the case that the key isn't in the form command.option
+ _disassemble_key(key)
+ raise ConfigurationError(f"No such key - {orig_key}")
+
+ def set_value(self, key: str, value: Any) -> None:
+ """Modify a value in the configuration."""
+ key = _normalize_name(key)
+ self._ensure_have_load_only()
+
+ assert self.load_only
+ fname, parser = self._get_parser_to_modify()
+
+ if parser is not None:
+ section, name = _disassemble_key(key)
+
+ # Modify the parser and the configuration
+ if not parser.has_section(section):
+ parser.add_section(section)
+ parser.set(section, name, value)
+
+ self._config[self.load_only][key] = value
+ self._mark_as_modified(fname, parser)
+
+ def unset_value(self, key: str) -> None:
+ """Unset a value in the configuration."""
+ orig_key = key
+ key = _normalize_name(key)
+ self._ensure_have_load_only()
+
+ assert self.load_only
+ if key not in self._config[self.load_only]:
+ raise ConfigurationError(f"No such key - {orig_key}")
+
+ fname, parser = self._get_parser_to_modify()
+
+ if parser is not None:
+ section, name = _disassemble_key(key)
+ if not (
+ parser.has_section(section) and parser.remove_option(section, name)
+ ):
+ # The option was not removed.
+ raise ConfigurationError(
+ "Fatal Internal error [id=1]. Please report as a bug."
+ )
+
+ # The section may be empty after the option was removed.
+ if not parser.items(section):
+ parser.remove_section(section)
+ self._mark_as_modified(fname, parser)
+
+ del self._config[self.load_only][key]
+
+ def save(self) -> None:
+ """Save the current in-memory state."""
+ self._ensure_have_load_only()
+
+ for fname, parser in self._modified_parsers:
+ logger.info("Writing to %s", fname)
+
+ # Ensure directory exists.
+ ensure_dir(os.path.dirname(fname))
+
+ # Ensure directory's permission(need to be writeable)
+ try:
+ with open(fname, "w") as f:
+ parser.write(f)
+ except OSError as error:
+ raise ConfigurationError(
+ f"An error occurred while writing to the configuration file "
+ f"{fname}: {error}"
+ )
+
+ #
+ # Private routines
+ #
+
+ def _ensure_have_load_only(self) -> None:
+ if self.load_only is None:
+ raise ConfigurationError("Needed a specific file to be modifying.")
+ logger.debug("Will be working with %s variant only", self.load_only)
+
+ @property
+ def _dictionary(self) -> Dict[str, Any]:
+ """A dictionary representing the loaded configuration."""
+ # NOTE: Dictionaries are not populated if not loaded. So, conditionals
+ # are not needed here.
+ retval = {}
+
+ for variant in OVERRIDE_ORDER:
+ retval.update(self._config[variant])
+
+ return retval
+
+ def _load_config_files(self) -> None:
+ """Loads configuration from configuration files"""
+ config_files = dict(self.iter_config_files())
+ if config_files[kinds.ENV][0:1] == [os.devnull]:
+ logger.debug(
+ "Skipping loading configuration files due to "
+ "environment's PIP_CONFIG_FILE being os.devnull"
+ )
+ return
+
+ for variant, files in config_files.items():
+ for fname in files:
+ # If there's specific variant set in `load_only`, load only
+ # that variant, not the others.
+ if self.load_only is not None and variant != self.load_only:
+ logger.debug("Skipping file '%s' (variant: %s)", fname, variant)
+ continue
+
+ parser = self._load_file(variant, fname)
+
+ # Keeping track of the parsers used
+ self._parsers[variant].append((fname, parser))
+
+ def _load_file(self, variant: Kind, fname: str) -> RawConfigParser:
+ logger.verbose("For variant '%s', will try loading '%s'", variant, fname)
+ parser = self._construct_parser(fname)
+
+ for section in parser.sections():
+ items = parser.items(section)
+ self._config[variant].update(self._normalized_keys(section, items))
+
+ return parser
+
+ def _construct_parser(self, fname: str) -> RawConfigParser:
+ parser = configparser.RawConfigParser()
+ # If there is no such file, don't bother reading it but create the
+ # parser anyway, to hold the data.
+ # Doing this is useful when modifying and saving files, where we don't
+ # need to construct a parser.
+ if os.path.exists(fname):
+ locale_encoding = locale.getpreferredencoding(False)
+ try:
+ parser.read(fname, encoding=locale_encoding)
+ except UnicodeDecodeError:
+ # See https://github.com/pypa/pip/issues/4963
+ raise ConfigurationFileCouldNotBeLoaded(
+ reason=f"contains invalid {locale_encoding} characters",
+ fname=fname,
+ )
+ except configparser.Error as error:
+ # See https://github.com/pypa/pip/issues/4893
+ raise ConfigurationFileCouldNotBeLoaded(error=error)
+ return parser
+
+ def _load_environment_vars(self) -> None:
+ """Loads configuration from environment variables"""
+ self._config[kinds.ENV_VAR].update(
+ self._normalized_keys(":env:", self.get_environ_vars())
+ )
+
+ def _normalized_keys(
+ self, section: str, items: Iterable[Tuple[str, Any]]
+ ) -> Dict[str, Any]:
+ """Normalizes items to construct a dictionary with normalized keys.
+
+ This routine is where the names become keys and are made the same
+ regardless of source - configuration files or environment.
+ """
+ normalized = {}
+ for name, val in items:
+ key = section + "." + _normalize_name(name)
+ normalized[key] = val
+ return normalized
+
+ def get_environ_vars(self) -> Iterable[Tuple[str, str]]:
+ """Returns a generator with all environmental vars with prefix PIP_"""
+ for key, val in os.environ.items():
+ if key.startswith("PIP_"):
+ name = key[4:].lower()
+ if name not in ENV_NAMES_IGNORED:
+ yield name, val
+
+ # XXX: This is patched in the tests.
+ def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
+ """Yields variant and configuration files associated with it.
+
+ This should be treated like items of a dictionary. The order
+ here doesn't affect what gets overridden. That is controlled
+ by OVERRIDE_ORDER. However this does control the order they are
+ displayed to the user. It's probably most ergononmic to display
+ things in the same order as OVERRIDE_ORDER
+ """
+ # SMELL: Move the conditions out of this function
+
+ env_config_file = os.environ.get("PIP_CONFIG_FILE", None)
+ config_files = get_configuration_files()
+
+ yield kinds.GLOBAL, config_files[kinds.GLOBAL]
+
+ # per-user config is not loaded when env_config_file exists
+ should_load_user_config = not self.isolated and not (
+ env_config_file and os.path.exists(env_config_file)
+ )
+ if should_load_user_config:
+ # The legacy config file is overridden by the new config file
+ yield kinds.USER, config_files[kinds.USER]
+
+ # virtualenv config
+ yield kinds.SITE, config_files[kinds.SITE]
+
+ if env_config_file is not None:
+ yield kinds.ENV, [env_config_file]
+ else:
+ yield kinds.ENV, []
+
+ def get_values_in_config(self, variant: Kind) -> Dict[str, Any]:
+ """Get values present in a config file"""
+ return self._config[variant]
+
+ def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]:
+ # Determine which parser to modify
+ assert self.load_only
+ parsers = self._parsers[self.load_only]
+ if not parsers:
+ # This should not happen if everything works correctly.
+ raise ConfigurationError(
+ "Fatal Internal error [id=2]. Please report as a bug."
+ )
+
+ # Use the highest priority parser.
+ return parsers[-1]
+
+ # XXX: This is patched in the tests.
+ def _mark_as_modified(self, fname: str, parser: RawConfigParser) -> None:
+ file_parser_tuple = (fname, parser)
+ if file_parser_tuple not in self._modified_parsers:
+ self._modified_parsers.append(file_parser_tuple)
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self._dictionary!r})"
diff --git a/env/Lib/site-packages/pip/_internal/distributions/__init__.py b/env/Lib/site-packages/pip/_internal/distributions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a89a838b9a5cb264e9ae9d269fbedca6e2d6333
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/distributions/__init__.py
@@ -0,0 +1,21 @@
+from pip._internal.distributions.base import AbstractDistribution
+from pip._internal.distributions.sdist import SourceDistribution
+from pip._internal.distributions.wheel import WheelDistribution
+from pip._internal.req.req_install import InstallRequirement
+
+
+def make_distribution_for_install_requirement(
+ install_req: InstallRequirement,
+) -> AbstractDistribution:
+ """Returns a Distribution for the given InstallRequirement"""
+ # Editable requirements will always be source distributions. They use the
+ # legacy logic until we create a modern standard for them.
+ if install_req.editable:
+ return SourceDistribution(install_req)
+
+ # If it's a wheel, it's a WheelDistribution
+ if install_req.is_wheel:
+ return WheelDistribution(install_req)
+
+ # Otherwise, a SourceDistribution
+ return SourceDistribution(install_req)
diff --git a/env/Lib/site-packages/pip/_internal/distributions/base.py b/env/Lib/site-packages/pip/_internal/distributions/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e4d0c91a901c46ab20be813af083cd19809318a
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/distributions/base.py
@@ -0,0 +1,53 @@
+import abc
+from typing import TYPE_CHECKING, Optional
+
+from pip._internal.metadata.base import BaseDistribution
+from pip._internal.req import InstallRequirement
+
+if TYPE_CHECKING:
+ from pip._internal.index.package_finder import PackageFinder
+
+
+class AbstractDistribution(metaclass=abc.ABCMeta):
+ """A base class for handling installable artifacts.
+
+ The requirements for anything installable are as follows:
+
+ - we must be able to determine the requirement name
+ (or we can't correctly handle the non-upgrade case).
+
+ - for packages with setup requirements, we must also be able
+ to determine their requirements without installing additional
+ packages (for the same reason as run-time dependencies)
+
+ - we must be able to create a Distribution object exposing the
+ above metadata.
+
+ - if we need to do work in the build tracker, we must be able to generate a unique
+ string to identify the requirement in the build tracker.
+ """
+
+ def __init__(self, req: InstallRequirement) -> None:
+ super().__init__()
+ self.req = req
+
+ @abc.abstractproperty
+ def build_tracker_id(self) -> Optional[str]:
+ """A string that uniquely identifies this requirement to the build tracker.
+
+ If None, then this dist has no work to do in the build tracker, and
+ ``.prepare_distribution_metadata()`` will not be called."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_metadata_distribution(self) -> BaseDistribution:
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def prepare_distribution_metadata(
+ self,
+ finder: "PackageFinder",
+ build_isolation: bool,
+ check_build_deps: bool,
+ ) -> None:
+ raise NotImplementedError()
diff --git a/env/Lib/site-packages/pip/_internal/distributions/installed.py b/env/Lib/site-packages/pip/_internal/distributions/installed.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab8d53be7408626719c27aa29fdc2e143b7c380a
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/distributions/installed.py
@@ -0,0 +1,29 @@
+from typing import Optional
+
+from pip._internal.distributions.base import AbstractDistribution
+from pip._internal.index.package_finder import PackageFinder
+from pip._internal.metadata import BaseDistribution
+
+
+class InstalledDistribution(AbstractDistribution):
+ """Represents an installed package.
+
+ This does not need any preparation as the required information has already
+ been computed.
+ """
+
+ @property
+ def build_tracker_id(self) -> Optional[str]:
+ return None
+
+ def get_metadata_distribution(self) -> BaseDistribution:
+ assert self.req.satisfied_by is not None, "not actually installed"
+ return self.req.satisfied_by
+
+ def prepare_distribution_metadata(
+ self,
+ finder: PackageFinder,
+ build_isolation: bool,
+ check_build_deps: bool,
+ ) -> None:
+ pass
diff --git a/env/Lib/site-packages/pip/_internal/distributions/sdist.py b/env/Lib/site-packages/pip/_internal/distributions/sdist.py
new file mode 100644
index 0000000000000000000000000000000000000000..28ea5cea16cdf9b740809553cbf2d3bf8d626e1e
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/distributions/sdist.py
@@ -0,0 +1,158 @@
+import logging
+from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
+
+from pip._internal.build_env import BuildEnvironment
+from pip._internal.distributions.base import AbstractDistribution
+from pip._internal.exceptions import InstallationError
+from pip._internal.metadata import BaseDistribution
+from pip._internal.utils.subprocess import runner_with_spinner_message
+
+if TYPE_CHECKING:
+ from pip._internal.index.package_finder import PackageFinder
+
+logger = logging.getLogger(__name__)
+
+
+class SourceDistribution(AbstractDistribution):
+ """Represents a source distribution.
+
+ The preparation step for these needs metadata for the packages to be
+ generated, either using PEP 517 or using the legacy `setup.py egg_info`.
+ """
+
+ @property
+ def build_tracker_id(self) -> Optional[str]:
+ """Identify this requirement uniquely by its link."""
+ assert self.req.link
+ return self.req.link.url_without_fragment
+
+ def get_metadata_distribution(self) -> BaseDistribution:
+ return self.req.get_dist()
+
+ def prepare_distribution_metadata(
+ self,
+ finder: "PackageFinder",
+ build_isolation: bool,
+ check_build_deps: bool,
+ ) -> None:
+ # Load pyproject.toml, to determine whether PEP 517 is to be used
+ self.req.load_pyproject_toml()
+
+ # Set up the build isolation, if this requirement should be isolated
+ should_isolate = self.req.use_pep517 and build_isolation
+ if should_isolate:
+ # Setup an isolated environment and install the build backend static
+ # requirements in it.
+ self._prepare_build_backend(finder)
+ # Check that if the requirement is editable, it either supports PEP 660 or
+ # has a setup.py or a setup.cfg. This cannot be done earlier because we need
+ # to setup the build backend to verify it supports build_editable, nor can
+ # it be done later, because we want to avoid installing build requirements
+ # needlessly. Doing it here also works around setuptools generating
+ # UNKNOWN.egg-info when running get_requires_for_build_wheel on a directory
+ # without setup.py nor setup.cfg.
+ self.req.isolated_editable_sanity_check()
+ # Install the dynamic build requirements.
+ self._install_build_reqs(finder)
+ # Check if the current environment provides build dependencies
+ should_check_deps = self.req.use_pep517 and check_build_deps
+ if should_check_deps:
+ pyproject_requires = self.req.pyproject_requires
+ assert pyproject_requires is not None
+ conflicting, missing = self.req.build_env.check_requirements(
+ pyproject_requires
+ )
+ if conflicting:
+ self._raise_conflicts("the backend dependencies", conflicting)
+ if missing:
+ self._raise_missing_reqs(missing)
+ self.req.prepare_metadata()
+
+ def _prepare_build_backend(self, finder: "PackageFinder") -> None:
+ # Isolate in a BuildEnvironment and install the build-time
+ # requirements.
+ pyproject_requires = self.req.pyproject_requires
+ assert pyproject_requires is not None
+
+ self.req.build_env = BuildEnvironment()
+ self.req.build_env.install_requirements(
+ finder, pyproject_requires, "overlay", kind="build dependencies"
+ )
+ conflicting, missing = self.req.build_env.check_requirements(
+ self.req.requirements_to_check
+ )
+ if conflicting:
+ self._raise_conflicts("PEP 517/518 supported requirements", conflicting)
+ if missing:
+ logger.warning(
+ "Missing build requirements in pyproject.toml for %s.",
+ self.req,
+ )
+ logger.warning(
+ "The project does not specify a build backend, and "
+ "pip cannot fall back to setuptools without %s.",
+ " and ".join(map(repr, sorted(missing))),
+ )
+
+ def _get_build_requires_wheel(self) -> Iterable[str]:
+ with self.req.build_env:
+ runner = runner_with_spinner_message("Getting requirements to build wheel")
+ backend = self.req.pep517_backend
+ assert backend is not None
+ with backend.subprocess_runner(runner):
+ return backend.get_requires_for_build_wheel()
+
+ def _get_build_requires_editable(self) -> Iterable[str]:
+ with self.req.build_env:
+ runner = runner_with_spinner_message(
+ "Getting requirements to build editable"
+ )
+ backend = self.req.pep517_backend
+ assert backend is not None
+ with backend.subprocess_runner(runner):
+ return backend.get_requires_for_build_editable()
+
+ def _install_build_reqs(self, finder: "PackageFinder") -> None:
+ # Install any extra build dependencies that the backend requests.
+ # This must be done in a second pass, as the pyproject.toml
+ # dependencies must be installed before we can call the backend.
+ if (
+ self.req.editable
+ and self.req.permit_editable_wheels
+ and self.req.supports_pyproject_editable
+ ):
+ build_reqs = self._get_build_requires_editable()
+ else:
+ build_reqs = self._get_build_requires_wheel()
+ conflicting, missing = self.req.build_env.check_requirements(build_reqs)
+ if conflicting:
+ self._raise_conflicts("the backend dependencies", conflicting)
+ self.req.build_env.install_requirements(
+ finder, missing, "normal", kind="backend dependencies"
+ )
+
+ def _raise_conflicts(
+ self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
+ ) -> None:
+ format_string = (
+ "Some build dependencies for {requirement} "
+ "conflict with {conflicting_with}: {description}."
+ )
+ error_message = format_string.format(
+ requirement=self.req,
+ conflicting_with=conflicting_with,
+ description=", ".join(
+ f"{installed} is incompatible with {wanted}"
+ for installed, wanted in sorted(conflicting_reqs)
+ ),
+ )
+ raise InstallationError(error_message)
+
+ def _raise_missing_reqs(self, missing: Set[str]) -> None:
+ format_string = (
+ "Some build dependencies for {requirement} are missing: {missing}."
+ )
+ error_message = format_string.format(
+ requirement=self.req, missing=", ".join(map(repr, sorted(missing)))
+ )
+ raise InstallationError(error_message)
diff --git a/env/Lib/site-packages/pip/_internal/distributions/wheel.py b/env/Lib/site-packages/pip/_internal/distributions/wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfadd39dcb77dfdaa2cca24e8a6db7e5beac181e
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/distributions/wheel.py
@@ -0,0 +1,42 @@
+from typing import TYPE_CHECKING, Optional
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.distributions.base import AbstractDistribution
+from pip._internal.metadata import (
+ BaseDistribution,
+ FilesystemWheel,
+ get_wheel_distribution,
+)
+
+if TYPE_CHECKING:
+ from pip._internal.index.package_finder import PackageFinder
+
+
+class WheelDistribution(AbstractDistribution):
+ """Represents a wheel distribution.
+
+ This does not need any preparation as wheels can be directly unpacked.
+ """
+
+ @property
+ def build_tracker_id(self) -> Optional[str]:
+ return None
+
+ def get_metadata_distribution(self) -> BaseDistribution:
+ """Loads the metadata from the wheel file into memory and returns a
+ Distribution that uses it, not relying on the wheel file or
+ requirement.
+ """
+ assert self.req.local_file_path, "Set as part of preparation during download"
+ assert self.req.name, "Wheels are never unnamed"
+ wheel = FilesystemWheel(self.req.local_file_path)
+ return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
+
+ def prepare_distribution_metadata(
+ self,
+ finder: "PackageFinder",
+ build_isolation: bool,
+ check_build_deps: bool,
+ ) -> None:
+ pass
diff --git a/env/Lib/site-packages/pip/_internal/exceptions.py b/env/Lib/site-packages/pip/_internal/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..45a876a850dfec2295ea982e1eadd5ab26364cd4
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/exceptions.py
@@ -0,0 +1,809 @@
+"""Exceptions used throughout package.
+
+This module MUST NOT try to import from anything within `pip._internal` to
+operate. This is expected to be importable from any/all files within the
+subpackage and, thus, should not depend on them.
+"""
+
+import configparser
+import contextlib
+import locale
+import logging
+import pathlib
+import re
+import sys
+from itertools import chain, groupby, repeat
+from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union
+
+from pip._vendor.packaging.requirements import InvalidRequirement
+from pip._vendor.packaging.version import InvalidVersion
+from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
+from pip._vendor.rich.markup import escape
+from pip._vendor.rich.text import Text
+
+if TYPE_CHECKING:
+ from hashlib import _Hash
+
+ from pip._vendor.requests.models import Request, Response
+
+ from pip._internal.metadata import BaseDistribution
+ from pip._internal.req.req_install import InstallRequirement
+
+logger = logging.getLogger(__name__)
+
+
+#
+# Scaffolding
+#
+def _is_kebab_case(s: str) -> bool:
+ return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
+
+
+def _prefix_with_indent(
+ s: Union[Text, str],
+ console: Console,
+ *,
+ prefix: str,
+ indent: str,
+) -> Text:
+ if isinstance(s, Text):
+ text = s
+ else:
+ text = console.render_str(s)
+
+ return console.render_str(prefix, overflow="ignore") + console.render_str(
+ f"\n{indent}", overflow="ignore"
+ ).join(text.split(allow_blank=True))
+
+
+class PipError(Exception):
+ """The base pip error."""
+
+
+class DiagnosticPipError(PipError):
+ """An error, that presents diagnostic information to the user.
+
+ This contains a bunch of logic, to enable pretty presentation of our error
+ messages. Each error gets a unique reference. Each error can also include
+ additional context, a hint and/or a note -- which are presented with the
+ main error message in a consistent style.
+
+ This is adapted from the error output styling in `sphinx-theme-builder`.
+ """
+
+ reference: str
+
+ def __init__(
+ self,
+ *,
+ kind: 'Literal["error", "warning"]' = "error",
+ reference: Optional[str] = None,
+ message: Union[str, Text],
+ context: Optional[Union[str, Text]],
+ hint_stmt: Optional[Union[str, Text]],
+ note_stmt: Optional[Union[str, Text]] = None,
+ link: Optional[str] = None,
+ ) -> None:
+ # Ensure a proper reference is provided.
+ if reference is None:
+ assert hasattr(self, "reference"), "error reference not provided!"
+ reference = self.reference
+ assert _is_kebab_case(reference), "error reference must be kebab-case!"
+
+ self.kind = kind
+ self.reference = reference
+
+ self.message = message
+ self.context = context
+
+ self.note_stmt = note_stmt
+ self.hint_stmt = hint_stmt
+
+ self.link = link
+
+ super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
+
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__name__}("
+ f"reference={self.reference!r}, "
+ f"message={self.message!r}, "
+ f"context={self.context!r}, "
+ f"note_stmt={self.note_stmt!r}, "
+ f"hint_stmt={self.hint_stmt!r}"
+ ")>"
+ )
+
+ def __rich_console__(
+ self,
+ console: Console,
+ options: ConsoleOptions,
+ ) -> RenderResult:
+ colour = "red" if self.kind == "error" else "yellow"
+
+ yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
+ yield ""
+
+ if not options.ascii_only:
+ # Present the main message, with relevant context indented.
+ if self.context is not None:
+ yield _prefix_with_indent(
+ self.message,
+ console,
+ prefix=f"[{colour}]×[/] ",
+ indent=f"[{colour}]│[/] ",
+ )
+ yield _prefix_with_indent(
+ self.context,
+ console,
+ prefix=f"[{colour}]╰─>[/] ",
+ indent=f"[{colour}] [/] ",
+ )
+ else:
+ yield _prefix_with_indent(
+ self.message,
+ console,
+ prefix="[red]×[/] ",
+ indent=" ",
+ )
+ else:
+ yield self.message
+ if self.context is not None:
+ yield ""
+ yield self.context
+
+ if self.note_stmt is not None or self.hint_stmt is not None:
+ yield ""
+
+ if self.note_stmt is not None:
+ yield _prefix_with_indent(
+ self.note_stmt,
+ console,
+ prefix="[magenta bold]note[/]: ",
+ indent=" ",
+ )
+ if self.hint_stmt is not None:
+ yield _prefix_with_indent(
+ self.hint_stmt,
+ console,
+ prefix="[cyan bold]hint[/]: ",
+ indent=" ",
+ )
+
+ if self.link is not None:
+ yield ""
+ yield f"Link: {self.link}"
+
+
+#
+# Actual Errors
+#
+class ConfigurationError(PipError):
+ """General exception in configuration"""
+
+
+class InstallationError(PipError):
+ """General exception during installation"""
+
+
+class MissingPyProjectBuildRequires(DiagnosticPipError):
+ """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
+
+ reference = "missing-pyproject-build-system-requires"
+
+ def __init__(self, *, package: str) -> None:
+ super().__init__(
+ message=f"Can not process {escape(package)}",
+ context=Text(
+ "This package has an invalid pyproject.toml file.\n"
+ "The [build-system] table is missing the mandatory `requires` key."
+ ),
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
+ )
+
+
+class InvalidPyProjectBuildRequires(DiagnosticPipError):
+ """Raised when pyproject.toml an invalid `build-system.requires`."""
+
+ reference = "invalid-pyproject-build-system-requires"
+
+ def __init__(self, *, package: str, reason: str) -> None:
+ super().__init__(
+ message=f"Can not process {escape(package)}",
+ context=Text(
+ "This package has an invalid `build-system.requires` key in "
+ f"pyproject.toml.\n{reason}"
+ ),
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
+ )
+
+
+class NoneMetadataError(PipError):
+ """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
+
+ This signifies an inconsistency, when the Distribution claims to have
+ the metadata file (if not, raise ``FileNotFoundError`` instead), but is
+ not actually able to produce its content. This may be due to permission
+ errors.
+ """
+
+ def __init__(
+ self,
+ dist: "BaseDistribution",
+ metadata_name: str,
+ ) -> None:
+ """
+ :param dist: A Distribution object.
+ :param metadata_name: The name of the metadata being accessed
+ (can be "METADATA" or "PKG-INFO").
+ """
+ self.dist = dist
+ self.metadata_name = metadata_name
+
+ def __str__(self) -> str:
+ # Use `dist` in the error message because its stringification
+ # includes more information, like the version and location.
+ return f"None {self.metadata_name} metadata found for distribution: {self.dist}"
+
+
+class UserInstallationInvalid(InstallationError):
+ """A --user install is requested on an environment without user site."""
+
+ def __str__(self) -> str:
+ return "User base directory is not specified"
+
+
+class InvalidSchemeCombination(InstallationError):
+ def __str__(self) -> str:
+ before = ", ".join(str(a) for a in self.args[:-1])
+ return f"Cannot set {before} and {self.args[-1]} together"
+
+
+class DistributionNotFound(InstallationError):
+ """Raised when a distribution cannot be found to satisfy a requirement"""
+
+
+class RequirementsFileParseError(InstallationError):
+ """Raised when a general error occurs parsing a requirements file line."""
+
+
+class BestVersionAlreadyInstalled(PipError):
+ """Raised when the most up-to-date version of a package is already
+ installed."""
+
+
+class BadCommand(PipError):
+ """Raised when virtualenv or a command is not found"""
+
+
+class CommandError(PipError):
+ """Raised when there is an error in command-line arguments"""
+
+
+class PreviousBuildDirError(PipError):
+ """Raised when there's a previous conflicting build directory"""
+
+
+class NetworkConnectionError(PipError):
+ """HTTP connection error"""
+
+ def __init__(
+ self,
+ error_msg: str,
+ response: Optional["Response"] = None,
+ request: Optional["Request"] = None,
+ ) -> None:
+ """
+ Initialize NetworkConnectionError with `request` and `response`
+ objects.
+ """
+ self.response = response
+ self.request = request
+ self.error_msg = error_msg
+ if (
+ self.response is not None
+ and not self.request
+ and hasattr(response, "request")
+ ):
+ self.request = self.response.request
+ super().__init__(error_msg, response, request)
+
+ def __str__(self) -> str:
+ return str(self.error_msg)
+
+
+class InvalidWheelFilename(InstallationError):
+ """Invalid wheel filename."""
+
+
+class UnsupportedWheel(InstallationError):
+ """Unsupported wheel."""
+
+
+class InvalidWheel(InstallationError):
+ """Invalid (e.g. corrupt) wheel."""
+
+ def __init__(self, location: str, name: str):
+ self.location = location
+ self.name = name
+
+ def __str__(self) -> str:
+ return f"Wheel '{self.name}' located at {self.location} is invalid."
+
+
+class MetadataInconsistent(InstallationError):
+ """Built metadata contains inconsistent information.
+
+ This is raised when the metadata contains values (e.g. name and version)
+ that do not match the information previously obtained from sdist filename,
+ user-supplied ``#egg=`` value, or an install requirement name.
+ """
+
+ def __init__(
+ self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
+ ) -> None:
+ self.ireq = ireq
+ self.field = field
+ self.f_val = f_val
+ self.m_val = m_val
+
+ def __str__(self) -> str:
+ return (
+ f"Requested {self.ireq} has inconsistent {self.field}: "
+ f"expected {self.f_val!r}, but metadata has {self.m_val!r}"
+ )
+
+
+class MetadataInvalid(InstallationError):
+ """Metadata is invalid."""
+
+ def __init__(self, ireq: "InstallRequirement", error: str) -> None:
+ self.ireq = ireq
+ self.error = error
+
+ def __str__(self) -> str:
+ return f"Requested {self.ireq} has invalid metadata: {self.error}"
+
+
+class InstallationSubprocessError(DiagnosticPipError, InstallationError):
+ """A subprocess call failed."""
+
+ reference = "subprocess-exited-with-error"
+
+ def __init__(
+ self,
+ *,
+ command_description: str,
+ exit_code: int,
+ output_lines: Optional[List[str]],
+ ) -> None:
+ if output_lines is None:
+ output_prompt = Text("See above for output.")
+ else:
+ output_prompt = (
+ Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
+ + Text("".join(output_lines))
+ + Text.from_markup(R"[red]\[end of output][/]")
+ )
+
+ super().__init__(
+ message=(
+ f"[green]{escape(command_description)}[/] did not run successfully.\n"
+ f"exit code: {exit_code}"
+ ),
+ context=output_prompt,
+ hint_stmt=None,
+ note_stmt=(
+ "This error originates from a subprocess, and is likely not a "
+ "problem with pip."
+ ),
+ )
+
+ self.command_description = command_description
+ self.exit_code = exit_code
+
+ def __str__(self) -> str:
+ return f"{self.command_description} exited with {self.exit_code}"
+
+
+class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
+ reference = "metadata-generation-failed"
+
+ def __init__(
+ self,
+ *,
+ package_details: str,
+ ) -> None:
+ super(InstallationSubprocessError, self).__init__(
+ message="Encountered error while generating package metadata.",
+ context=escape(package_details),
+ hint_stmt="See above for details.",
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ )
+
+ def __str__(self) -> str:
+ return "metadata generation failed"
+
+
+class HashErrors(InstallationError):
+ """Multiple HashError instances rolled into one for reporting"""
+
+ def __init__(self) -> None:
+ self.errors: List[HashError] = []
+
+ def append(self, error: "HashError") -> None:
+ self.errors.append(error)
+
+ def __str__(self) -> str:
+ lines = []
+ self.errors.sort(key=lambda e: e.order)
+ for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
+ lines.append(cls.head)
+ lines.extend(e.body() for e in errors_of_cls)
+ if lines:
+ return "\n".join(lines)
+ return ""
+
+ def __bool__(self) -> bool:
+ return bool(self.errors)
+
+
+class HashError(InstallationError):
+ """
+ A failure to verify a package against known-good hashes
+
+ :cvar order: An int sorting hash exception classes by difficulty of
+ recovery (lower being harder), so the user doesn't bother fretting
+ about unpinned packages when he has deeper issues, like VCS
+ dependencies, to deal with. Also keeps error reports in a
+ deterministic order.
+ :cvar head: A section heading for display above potentially many
+ exceptions of this kind
+ :ivar req: The InstallRequirement that triggered this error. This is
+ pasted on after the exception is instantiated, because it's not
+ typically available earlier.
+
+ """
+
+ req: Optional["InstallRequirement"] = None
+ head = ""
+ order: int = -1
+
+ def body(self) -> str:
+ """Return a summary of me for display under the heading.
+
+ This default implementation simply prints a description of the
+ triggering requirement.
+
+ :param req: The InstallRequirement that provoked this error, with
+ its link already populated by the resolver's _populate_link().
+
+ """
+ return f" {self._requirement_name()}"
+
+ def __str__(self) -> str:
+ return f"{self.head}\n{self.body()}"
+
+ def _requirement_name(self) -> str:
+ """Return a description of the requirement that triggered me.
+
+ This default implementation returns long description of the req, with
+ line numbers
+
+ """
+ return str(self.req) if self.req else "unknown package"
+
+
+class VcsHashUnsupported(HashError):
+ """A hash was provided for a version-control-system-based requirement, but
+ we don't have a method for hashing those."""
+
+ order = 0
+ head = (
+ "Can't verify hashes for these requirements because we don't "
+ "have a way to hash version control repositories:"
+ )
+
+
+class DirectoryUrlHashUnsupported(HashError):
+ """A hash was provided for a version-control-system-based requirement, but
+ we don't have a method for hashing those."""
+
+ order = 1
+ head = (
+ "Can't verify hashes for these file:// requirements because they "
+ "point to directories:"
+ )
+
+
+class HashMissing(HashError):
+ """A hash was needed for a requirement but is absent."""
+
+ order = 2
+ head = (
+ "Hashes are required in --require-hashes mode, but they are "
+ "missing from some requirements. Here is a list of those "
+ "requirements along with the hashes their downloaded archives "
+ "actually had. Add lines like these to your requirements files to "
+ "prevent tampering. (If you did not enable --require-hashes "
+ "manually, note that it turns on automatically when any package "
+ "has a hash.)"
+ )
+
+ def __init__(self, gotten_hash: str) -> None:
+ """
+ :param gotten_hash: The hash of the (possibly malicious) archive we
+ just downloaded
+ """
+ self.gotten_hash = gotten_hash
+
+ def body(self) -> str:
+ # Dodge circular import.
+ from pip._internal.utils.hashes import FAVORITE_HASH
+
+ package = None
+ if self.req:
+ # In the case of URL-based requirements, display the original URL
+ # seen in the requirements file rather than the package name,
+ # so the output can be directly copied into the requirements file.
+ package = (
+ self.req.original_link
+ if self.req.is_direct
+ # In case someone feeds something downright stupid
+ # to InstallRequirement's constructor.
+ else getattr(self.req, "req", None)
+ )
+ return " {} --hash={}:{}".format(
+ package or "unknown package", FAVORITE_HASH, self.gotten_hash
+ )
+
+
+class HashUnpinned(HashError):
+ """A requirement had a hash specified but was not pinned to a specific
+ version."""
+
+ order = 3
+ head = (
+ "In --require-hashes mode, all requirements must have their "
+ "versions pinned with ==. These do not:"
+ )
+
+
+class HashMismatch(HashError):
+ """
+ Distribution file hash values don't match.
+
+ :ivar package_name: The name of the package that triggered the hash
+ mismatch. Feel free to write to this after the exception is raise to
+ improve its error message.
+
+ """
+
+ order = 4
+ head = (
+ "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
+ "FILE. If you have updated the package versions, please update "
+ "the hashes. Otherwise, examine the package contents carefully; "
+ "someone may have tampered with them."
+ )
+
+ def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
+ """
+ :param allowed: A dict of algorithm names pointing to lists of allowed
+ hex digests
+ :param gots: A dict of algorithm names pointing to hashes we
+ actually got from the files under suspicion
+ """
+ self.allowed = allowed
+ self.gots = gots
+
+ def body(self) -> str:
+ return f" {self._requirement_name()}:\n{self._hash_comparison()}"
+
+ def _hash_comparison(self) -> str:
+ """
+ Return a comparison of actual and expected hash values.
+
+ Example::
+
+ Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
+ or 123451234512345123451234512345123451234512345
+ Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
+
+ """
+
+ def hash_then_or(hash_name: str) -> "chain[str]":
+ # For now, all the decent hashes have 6-char names, so we can get
+ # away with hard-coding space literals.
+ return chain([hash_name], repeat(" or"))
+
+ lines: List[str] = []
+ for hash_name, expecteds in self.allowed.items():
+ prefix = hash_then_or(hash_name)
+ lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)
+ lines.append(
+ f" Got {self.gots[hash_name].hexdigest()}\n"
+ )
+ return "\n".join(lines)
+
+
+class UnsupportedPythonVersion(InstallationError):
+ """Unsupported python version according to Requires-Python package
+ metadata."""
+
+
+class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
+ """When there are errors while loading a configuration file"""
+
+ def __init__(
+ self,
+ reason: str = "could not be loaded",
+ fname: Optional[str] = None,
+ error: Optional[configparser.Error] = None,
+ ) -> None:
+ super().__init__(error)
+ self.reason = reason
+ self.fname = fname
+ self.error = error
+
+ def __str__(self) -> str:
+ if self.fname is not None:
+ message_part = f" in {self.fname}."
+ else:
+ assert self.error is not None
+ message_part = f".\n{self.error}\n"
+ return f"Configuration file {self.reason}{message_part}"
+
+
+_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
+The Python environment under {sys.prefix} is managed externally, and may not be
+manipulated by the user. Please use specific tooling from the distributor of
+the Python installation to interact with this environment instead.
+"""
+
+
+class ExternallyManagedEnvironment(DiagnosticPipError):
+ """The current environment is externally managed.
+
+ This is raised when the current environment is externally managed, as
+ defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
+ and displayed when the error is bubbled up to the user.
+
+ :param error: The error message read from ``EXTERNALLY-MANAGED``.
+ """
+
+ reference = "externally-managed-environment"
+
+ def __init__(self, error: Optional[str]) -> None:
+ if error is None:
+ context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
+ else:
+ context = Text(error)
+ super().__init__(
+ message="This environment is externally managed",
+ context=context,
+ note_stmt=(
+ "If you believe this is a mistake, please contact your "
+ "Python installation or OS distribution provider. "
+ "You can override this, at the risk of breaking your Python "
+ "installation or OS, by passing --break-system-packages."
+ ),
+ hint_stmt=Text("See PEP 668 for the detailed specification."),
+ )
+
+ @staticmethod
+ def _iter_externally_managed_error_keys() -> Iterator[str]:
+ # LC_MESSAGES is in POSIX, but not the C standard. The most common
+ # platform that does not implement this category is Windows, where
+ # using other categories for console message localization is equally
+ # unreliable, so we fall back to the locale-less vendor message. This
+ # can always be re-evaluated when a vendor proposes a new alternative.
+ try:
+ category = locale.LC_MESSAGES
+ except AttributeError:
+ lang: Optional[str] = None
+ else:
+ lang, _ = locale.getlocale(category)
+ if lang is not None:
+ yield f"Error-{lang}"
+ for sep in ("-", "_"):
+ before, found, _ = lang.partition(sep)
+ if not found:
+ continue
+ yield f"Error-{before}"
+ yield "Error"
+
+ @classmethod
+ def from_config(
+ cls,
+ config: Union[pathlib.Path, str],
+ ) -> "ExternallyManagedEnvironment":
+ parser = configparser.ConfigParser(interpolation=None)
+ try:
+ parser.read(config, encoding="utf-8")
+ section = parser["externally-managed"]
+ for key in cls._iter_externally_managed_error_keys():
+ with contextlib.suppress(KeyError):
+ return cls(section[key])
+ except KeyError:
+ pass
+ except (OSError, UnicodeDecodeError, configparser.ParsingError):
+ from pip._internal.utils._log import VERBOSE
+
+ exc_info = logger.isEnabledFor(VERBOSE)
+ logger.warning("Failed to read %s", config, exc_info=exc_info)
+ return cls(None)
+
+
+class UninstallMissingRecord(DiagnosticPipError):
+ reference = "uninstall-no-record-file"
+
+ def __init__(self, *, distribution: "BaseDistribution") -> None:
+ installer = distribution.installer
+ if not installer or installer == "pip":
+ dep = f"{distribution.raw_name}=={distribution.version}"
+ hint = Text.assemble(
+ "You might be able to recover from this via: ",
+ (f"pip install --force-reinstall --no-deps {dep}", "green"),
+ )
+ else:
+ hint = Text(
+ f"The package was installed by {installer}. "
+ "You should check if it can uninstall the package."
+ )
+
+ super().__init__(
+ message=Text(f"Cannot uninstall {distribution}"),
+ context=(
+ "The package's contents are unknown: "
+ f"no RECORD file was found for {distribution.raw_name}."
+ ),
+ hint_stmt=hint,
+ )
+
+
+class LegacyDistutilsInstall(DiagnosticPipError):
+ reference = "uninstall-distutils-installed-package"
+
+ def __init__(self, *, distribution: "BaseDistribution") -> None:
+ super().__init__(
+ message=Text(f"Cannot uninstall {distribution}"),
+ context=(
+ "It is a distutils installed project and thus we cannot accurately "
+ "determine which files belong to it which would lead to only a partial "
+ "uninstall."
+ ),
+ hint_stmt=None,
+ )
+
+
+class InvalidInstalledPackage(DiagnosticPipError):
+ reference = "invalid-installed-package"
+
+ def __init__(
+ self,
+ *,
+ dist: "BaseDistribution",
+ invalid_exc: Union[InvalidRequirement, InvalidVersion],
+ ) -> None:
+ installed_location = dist.installed_location
+
+ if isinstance(invalid_exc, InvalidRequirement):
+ invalid_type = "requirement"
+ else:
+ invalid_type = "version"
+
+ super().__init__(
+ message=Text(
+ f"Cannot process installed package {dist} "
+ + (f"in {installed_location!r} " if installed_location else "")
+ + f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}"
+ ),
+ context=(
+ "Starting with pip 24.1, packages with invalid "
+ f"{invalid_type}s can not be processed."
+ ),
+ hint_stmt="To proceed this package must be uninstalled.",
+ )
diff --git a/env/Lib/site-packages/pip/_internal/index/__init__.py b/env/Lib/site-packages/pip/_internal/index/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a17b7b3b6ad49157ee41f3da304fec3d32342d3
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/index/__init__.py
@@ -0,0 +1,2 @@
+"""Index interaction code
+"""
diff --git a/env/Lib/site-packages/pip/_internal/index/collector.py b/env/Lib/site-packages/pip/_internal/index/collector.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f8fdee3d46271652d498cbfc865a25c50f2cab0
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/index/collector.py
@@ -0,0 +1,494 @@
+"""
+The main purpose of this module is to expose LinkCollector.collect_sources().
+"""
+
+import collections
+import email.message
+import functools
+import itertools
+import json
+import logging
+import os
+import urllib.parse
+import urllib.request
+from dataclasses import dataclass
+from html.parser import HTMLParser
+from optparse import Values
+from typing import (
+ Callable,
+ Dict,
+ Iterable,
+ List,
+ MutableMapping,
+ NamedTuple,
+ Optional,
+ Protocol,
+ Sequence,
+ Tuple,
+ Union,
+)
+
+from pip._vendor import requests
+from pip._vendor.requests import Response
+from pip._vendor.requests.exceptions import RetryError, SSLError
+
+from pip._internal.exceptions import NetworkConnectionError
+from pip._internal.models.link import Link
+from pip._internal.models.search_scope import SearchScope
+from pip._internal.network.session import PipSession
+from pip._internal.network.utils import raise_for_status
+from pip._internal.utils.filetypes import is_archive_file
+from pip._internal.utils.misc import redact_auth_from_url
+from pip._internal.vcs import vcs
+
+from .sources import CandidatesFromPage, LinkSource, build_source
+
+logger = logging.getLogger(__name__)
+
+ResponseHeaders = MutableMapping[str, str]
+
+
+def _match_vcs_scheme(url: str) -> Optional[str]:
+ """Look for VCS schemes in the URL.
+
+ Returns the matched VCS scheme, or None if there's no match.
+ """
+ for scheme in vcs.schemes:
+ if url.lower().startswith(scheme) and url[len(scheme)] in "+:":
+ return scheme
+ return None
+
+
+class _NotAPIContent(Exception):
+ def __init__(self, content_type: str, request_desc: str) -> None:
+ super().__init__(content_type, request_desc)
+ self.content_type = content_type
+ self.request_desc = request_desc
+
+
+def _ensure_api_header(response: Response) -> None:
+ """
+ Check the Content-Type header to ensure the response contains a Simple
+ API Response.
+
+ Raises `_NotAPIContent` if the content type is not a valid content-type.
+ """
+ content_type = response.headers.get("Content-Type", "Unknown")
+
+ content_type_l = content_type.lower()
+ if content_type_l.startswith(
+ (
+ "text/html",
+ "application/vnd.pypi.simple.v1+html",
+ "application/vnd.pypi.simple.v1+json",
+ )
+ ):
+ return
+
+ raise _NotAPIContent(content_type, response.request.method)
+
+
+class _NotHTTP(Exception):
+ pass
+
+
+def _ensure_api_response(url: str, session: PipSession) -> None:
+ """
+ Send a HEAD request to the URL, and ensure the response contains a simple
+ API Response.
+
+ Raises `_NotHTTP` if the URL is not available for a HEAD request, or
+ `_NotAPIContent` if the content type is not a valid content type.
+ """
+ scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
+ if scheme not in {"http", "https"}:
+ raise _NotHTTP()
+
+ resp = session.head(url, allow_redirects=True)
+ raise_for_status(resp)
+
+ _ensure_api_header(resp)
+
+
+def _get_simple_response(url: str, session: PipSession) -> Response:
+ """Access an Simple API response with GET, and return the response.
+
+ This consists of three parts:
+
+ 1. If the URL looks suspiciously like an archive, send a HEAD first to
+ check the Content-Type is HTML or Simple API, to avoid downloading a
+ large file. Raise `_NotHTTP` if the content type cannot be determined, or
+ `_NotAPIContent` if it is not HTML or a Simple API.
+ 2. Actually perform the request. Raise HTTP exceptions on network failures.
+ 3. Check the Content-Type header to make sure we got a Simple API response,
+ and raise `_NotAPIContent` otherwise.
+ """
+ if is_archive_file(Link(url).filename):
+ _ensure_api_response(url, session=session)
+
+ logger.debug("Getting page %s", redact_auth_from_url(url))
+
+ resp = session.get(
+ url,
+ headers={
+ "Accept": ", ".join(
+ [
+ "application/vnd.pypi.simple.v1+json",
+ "application/vnd.pypi.simple.v1+html; q=0.1",
+ "text/html; q=0.01",
+ ]
+ ),
+ # We don't want to blindly returned cached data for
+ # /simple/, because authors generally expecting that
+ # twine upload && pip install will function, but if
+ # they've done a pip install in the last ~10 minutes
+ # it won't. Thus by setting this to zero we will not
+ # blindly use any cached data, however the benefit of
+ # using max-age=0 instead of no-cache, is that we will
+ # still support conditional requests, so we will still
+ # minimize traffic sent in cases where the page hasn't
+ # changed at all, we will just always incur the round
+ # trip for the conditional GET now instead of only
+ # once per 10 minutes.
+ # For more information, please see pypa/pip#5670.
+ "Cache-Control": "max-age=0",
+ },
+ )
+ raise_for_status(resp)
+
+ # The check for archives above only works if the url ends with
+ # something that looks like an archive. However that is not a
+ # requirement of an url. Unless we issue a HEAD request on every
+ # url we cannot know ahead of time for sure if something is a
+ # Simple API response or not. However we can check after we've
+ # downloaded it.
+ _ensure_api_header(resp)
+
+ logger.debug(
+ "Fetched page %s as %s",
+ redact_auth_from_url(url),
+ resp.headers.get("Content-Type", "Unknown"),
+ )
+
+ return resp
+
+
+def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
+ """Determine if we have any encoding information in our headers."""
+ if headers and "Content-Type" in headers:
+ m = email.message.Message()
+ m["content-type"] = headers["Content-Type"]
+ charset = m.get_param("charset")
+ if charset:
+ return str(charset)
+ return None
+
+
+class CacheablePageContent:
+ def __init__(self, page: "IndexContent") -> None:
+ assert page.cache_link_parsing
+ self.page = page
+
+ def __eq__(self, other: object) -> bool:
+ return isinstance(other, type(self)) and self.page.url == other.page.url
+
+ def __hash__(self) -> int:
+ return hash(self.page.url)
+
+
+class ParseLinks(Protocol):
+ def __call__(self, page: "IndexContent") -> Iterable[Link]: ...
+
+
+def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
+ """
+ Given a function that parses an Iterable[Link] from an IndexContent, cache the
+ function's result (keyed by CacheablePageContent), unless the IndexContent
+ `page` has `page.cache_link_parsing == False`.
+ """
+
+ @functools.lru_cache(maxsize=None)
+ def wrapper(cacheable_page: CacheablePageContent) -> List[Link]:
+ return list(fn(cacheable_page.page))
+
+ @functools.wraps(fn)
+ def wrapper_wrapper(page: "IndexContent") -> List[Link]:
+ if page.cache_link_parsing:
+ return wrapper(CacheablePageContent(page))
+ return list(fn(page))
+
+ return wrapper_wrapper
+
+
+@with_cached_index_content
+def parse_links(page: "IndexContent") -> Iterable[Link]:
+ """
+ Parse a Simple API's Index Content, and yield its anchor elements as Link objects.
+ """
+
+ content_type_l = page.content_type.lower()
+ if content_type_l.startswith("application/vnd.pypi.simple.v1+json"):
+ data = json.loads(page.content)
+ for file in data.get("files", []):
+ link = Link.from_json(file, page.url)
+ if link is None:
+ continue
+ yield link
+ return
+
+ parser = HTMLLinkParser(page.url)
+ encoding = page.encoding or "utf-8"
+ parser.feed(page.content.decode(encoding))
+
+ url = page.url
+ base_url = parser.base_url or url
+ for anchor in parser.anchors:
+ link = Link.from_element(anchor, page_url=url, base_url=base_url)
+ if link is None:
+ continue
+ yield link
+
+
+@dataclass(frozen=True)
+class IndexContent:
+ """Represents one response (or page), along with its URL.
+
+ :param encoding: the encoding to decode the given content.
+ :param url: the URL from which the HTML was downloaded.
+ :param cache_link_parsing: whether links parsed from this page's url
+ should be cached. PyPI index urls should
+ have this set to False, for example.
+ """
+
+ content: bytes
+ content_type: str
+ encoding: Optional[str]
+ url: str
+ cache_link_parsing: bool = True
+
+ def __str__(self) -> str:
+ return redact_auth_from_url(self.url)
+
+
+class HTMLLinkParser(HTMLParser):
+ """
+ HTMLParser that keeps the first base HREF and a list of all anchor
+ elements' attributes.
+ """
+
+ def __init__(self, url: str) -> None:
+ super().__init__(convert_charrefs=True)
+
+ self.url: str = url
+ self.base_url: Optional[str] = None
+ self.anchors: List[Dict[str, Optional[str]]] = []
+
+ def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
+ if tag == "base" and self.base_url is None:
+ href = self.get_href(attrs)
+ if href is not None:
+ self.base_url = href
+ elif tag == "a":
+ self.anchors.append(dict(attrs))
+
+ def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
+ for name, value in attrs:
+ if name == "href":
+ return value
+ return None
+
+
+def _handle_get_simple_fail(
+ link: Link,
+ reason: Union[str, Exception],
+ meth: Optional[Callable[..., None]] = None,
+) -> None:
+ if meth is None:
+ meth = logger.debug
+ meth("Could not fetch URL %s: %s - skipping", link, reason)
+
+
+def _make_index_content(
+ response: Response, cache_link_parsing: bool = True
+) -> IndexContent:
+ encoding = _get_encoding_from_headers(response.headers)
+ return IndexContent(
+ response.content,
+ response.headers["Content-Type"],
+ encoding=encoding,
+ url=response.url,
+ cache_link_parsing=cache_link_parsing,
+ )
+
+
+def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]:
+ url = link.url.split("#", 1)[0]
+
+ # Check for VCS schemes that do not support lookup as web pages.
+ vcs_scheme = _match_vcs_scheme(url)
+ if vcs_scheme:
+ logger.warning(
+ "Cannot look at %s URL %s because it does not support lookup as web pages.",
+ vcs_scheme,
+ link,
+ )
+ return None
+
+ # Tack index.html onto file:// URLs that point to directories
+ scheme, _, path, _, _, _ = urllib.parse.urlparse(url)
+ if scheme == "file" and os.path.isdir(urllib.request.url2pathname(path)):
+ # add trailing slash if not present so urljoin doesn't trim
+ # final segment
+ if not url.endswith("/"):
+ url += "/"
+ # TODO: In the future, it would be nice if pip supported PEP 691
+ # style responses in the file:// URLs, however there's no
+ # standard file extension for application/vnd.pypi.simple.v1+json
+ # so we'll need to come up with something on our own.
+ url = urllib.parse.urljoin(url, "index.html")
+ logger.debug(" file: URL is directory, getting %s", url)
+
+ try:
+ resp = _get_simple_response(url, session=session)
+ except _NotHTTP:
+ logger.warning(
+ "Skipping page %s because it looks like an archive, and cannot "
+ "be checked by a HTTP HEAD request.",
+ link,
+ )
+ except _NotAPIContent as exc:
+ logger.warning(
+ "Skipping page %s because the %s request got Content-Type: %s. "
+ "The only supported Content-Types are application/vnd.pypi.simple.v1+json, "
+ "application/vnd.pypi.simple.v1+html, and text/html",
+ link,
+ exc.request_desc,
+ exc.content_type,
+ )
+ except NetworkConnectionError as exc:
+ _handle_get_simple_fail(link, exc)
+ except RetryError as exc:
+ _handle_get_simple_fail(link, exc)
+ except SSLError as exc:
+ reason = "There was a problem confirming the ssl certificate: "
+ reason += str(exc)
+ _handle_get_simple_fail(link, reason, meth=logger.info)
+ except requests.ConnectionError as exc:
+ _handle_get_simple_fail(link, f"connection error: {exc}")
+ except requests.Timeout:
+ _handle_get_simple_fail(link, "timed out")
+ else:
+ return _make_index_content(resp, cache_link_parsing=link.cache_link_parsing)
+ return None
+
+
+class CollectedSources(NamedTuple):
+ find_links: Sequence[Optional[LinkSource]]
+ index_urls: Sequence[Optional[LinkSource]]
+
+
+class LinkCollector:
+ """
+ Responsible for collecting Link objects from all configured locations,
+ making network requests as needed.
+
+ The class's main method is its collect_sources() method.
+ """
+
+ def __init__(
+ self,
+ session: PipSession,
+ search_scope: SearchScope,
+ ) -> None:
+ self.search_scope = search_scope
+ self.session = session
+
+ @classmethod
+ def create(
+ cls,
+ session: PipSession,
+ options: Values,
+ suppress_no_index: bool = False,
+ ) -> "LinkCollector":
+ """
+ :param session: The Session to use to make requests.
+ :param suppress_no_index: Whether to ignore the --no-index option
+ when constructing the SearchScope object.
+ """
+ index_urls = [options.index_url] + options.extra_index_urls
+ if options.no_index and not suppress_no_index:
+ logger.debug(
+ "Ignoring indexes: %s",
+ ",".join(redact_auth_from_url(url) for url in index_urls),
+ )
+ index_urls = []
+
+ # Make sure find_links is a list before passing to create().
+ find_links = options.find_links or []
+
+ search_scope = SearchScope.create(
+ find_links=find_links,
+ index_urls=index_urls,
+ no_index=options.no_index,
+ )
+ link_collector = LinkCollector(
+ session=session,
+ search_scope=search_scope,
+ )
+ return link_collector
+
+ @property
+ def find_links(self) -> List[str]:
+ return self.search_scope.find_links
+
+ def fetch_response(self, location: Link) -> Optional[IndexContent]:
+ """
+ Fetch an HTML page containing package links.
+ """
+ return _get_index_content(location, session=self.session)
+
+ def collect_sources(
+ self,
+ project_name: str,
+ candidates_from_page: CandidatesFromPage,
+ ) -> CollectedSources:
+ # The OrderedDict calls deduplicate sources by URL.
+ index_url_sources = collections.OrderedDict(
+ build_source(
+ loc,
+ candidates_from_page=candidates_from_page,
+ page_validator=self.session.is_secure_origin,
+ expand_dir=False,
+ cache_link_parsing=False,
+ project_name=project_name,
+ )
+ for loc in self.search_scope.get_index_urls_locations(project_name)
+ ).values()
+ find_links_sources = collections.OrderedDict(
+ build_source(
+ loc,
+ candidates_from_page=candidates_from_page,
+ page_validator=self.session.is_secure_origin,
+ expand_dir=True,
+ cache_link_parsing=True,
+ project_name=project_name,
+ )
+ for loc in self.find_links
+ ).values()
+
+ if logger.isEnabledFor(logging.DEBUG):
+ lines = [
+ f"* {s.link}"
+ for s in itertools.chain(find_links_sources, index_url_sources)
+ if s is not None and s.link is not None
+ ]
+ lines = [
+ f"{len(lines)} location(s) to search "
+ f"for versions of {project_name}:"
+ ] + lines
+ logger.debug("\n".join(lines))
+
+ return CollectedSources(
+ find_links=list(find_links_sources),
+ index_urls=list(index_url_sources),
+ )
diff --git a/env/Lib/site-packages/pip/_internal/index/package_finder.py b/env/Lib/site-packages/pip/_internal/index/package_finder.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d65ce35f37ae7440129f89cf072034fdcc2b44d
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/index/package_finder.py
@@ -0,0 +1,1020 @@
+"""Routines related to PyPI, indexes"""
+
+import enum
+import functools
+import itertools
+import logging
+import re
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union
+
+from pip._vendor.packaging import specifiers
+from pip._vendor.packaging.tags import Tag
+from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.exceptions import (
+ BestVersionAlreadyInstalled,
+ DistributionNotFound,
+ InvalidWheelFilename,
+ UnsupportedWheel,
+)
+from pip._internal.index.collector import LinkCollector, parse_links
+from pip._internal.models.candidate import InstallationCandidate
+from pip._internal.models.format_control import FormatControl
+from pip._internal.models.link import Link
+from pip._internal.models.search_scope import SearchScope
+from pip._internal.models.selection_prefs import SelectionPreferences
+from pip._internal.models.target_python import TargetPython
+from pip._internal.models.wheel import Wheel
+from pip._internal.req import InstallRequirement
+from pip._internal.utils._log import getLogger
+from pip._internal.utils.filetypes import WHEEL_EXTENSION
+from pip._internal.utils.hashes import Hashes
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.misc import build_netloc
+from pip._internal.utils.packaging import check_requires_python
+from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
+
+if TYPE_CHECKING:
+ from pip._vendor.typing_extensions import TypeGuard
+
+__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
+
+
+logger = getLogger(__name__)
+
+BuildTag = Union[Tuple[()], Tuple[int, str]]
+CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
+
+
+def _check_link_requires_python(
+ link: Link,
+ version_info: Tuple[int, int, int],
+ ignore_requires_python: bool = False,
+) -> bool:
+ """
+ Return whether the given Python version is compatible with a link's
+ "Requires-Python" value.
+
+ :param version_info: A 3-tuple of ints representing the Python
+ major-minor-micro version to check.
+ :param ignore_requires_python: Whether to ignore the "Requires-Python"
+ value if the given Python version isn't compatible.
+ """
+ try:
+ is_compatible = check_requires_python(
+ link.requires_python,
+ version_info=version_info,
+ )
+ except specifiers.InvalidSpecifier:
+ logger.debug(
+ "Ignoring invalid Requires-Python (%r) for link: %s",
+ link.requires_python,
+ link,
+ )
+ else:
+ if not is_compatible:
+ version = ".".join(map(str, version_info))
+ if not ignore_requires_python:
+ logger.verbose(
+ "Link requires a different Python (%s not in: %r): %s",
+ version,
+ link.requires_python,
+ link,
+ )
+ return False
+
+ logger.debug(
+ "Ignoring failed Requires-Python check (%s not in: %r) for link: %s",
+ version,
+ link.requires_python,
+ link,
+ )
+
+ return True
+
+
+class LinkType(enum.Enum):
+ candidate = enum.auto()
+ different_project = enum.auto()
+ yanked = enum.auto()
+ format_unsupported = enum.auto()
+ format_invalid = enum.auto()
+ platform_mismatch = enum.auto()
+ requires_python_mismatch = enum.auto()
+
+
+class LinkEvaluator:
+ """
+ Responsible for evaluating links for a particular project.
+ """
+
+ _py_version_re = re.compile(r"-py([123]\.?[0-9]?)$")
+
+ # Don't include an allow_yanked default value to make sure each call
+ # site considers whether yanked releases are allowed. This also causes
+ # that decision to be made explicit in the calling code, which helps
+ # people when reading the code.
+ def __init__(
+ self,
+ project_name: str,
+ canonical_name: str,
+ formats: FrozenSet[str],
+ target_python: TargetPython,
+ allow_yanked: bool,
+ ignore_requires_python: Optional[bool] = None,
+ ) -> None:
+ """
+ :param project_name: The user supplied package name.
+ :param canonical_name: The canonical package name.
+ :param formats: The formats allowed for this package. Should be a set
+ with 'binary' or 'source' or both in it.
+ :param target_python: The target Python interpreter to use when
+ evaluating link compatibility. This is used, for example, to
+ check wheel compatibility, as well as when checking the Python
+ version, e.g. the Python version embedded in a link filename
+ (or egg fragment) and against an HTML link's optional PEP 503
+ "data-requires-python" attribute.
+ :param allow_yanked: Whether files marked as yanked (in the sense
+ of PEP 592) are permitted to be candidates for install.
+ :param ignore_requires_python: Whether to ignore incompatible
+ PEP 503 "data-requires-python" values in HTML links. Defaults
+ to False.
+ """
+ if ignore_requires_python is None:
+ ignore_requires_python = False
+
+ self._allow_yanked = allow_yanked
+ self._canonical_name = canonical_name
+ self._ignore_requires_python = ignore_requires_python
+ self._formats = formats
+ self._target_python = target_python
+
+ self.project_name = project_name
+
+ def evaluate_link(self, link: Link) -> Tuple[LinkType, str]:
+ """
+ Determine whether a link is a candidate for installation.
+
+ :return: A tuple (result, detail), where *result* is an enum
+ representing whether the evaluation found a candidate, or the reason
+ why one is not found. If a candidate is found, *detail* will be the
+ candidate's version string; if one is not found, it contains the
+ reason the link fails to qualify.
+ """
+ version = None
+ if link.is_yanked and not self._allow_yanked:
+ reason = link.yanked_reason or ""
+ return (LinkType.yanked, f"yanked for reason: {reason}")
+
+ if link.egg_fragment:
+ egg_info = link.egg_fragment
+ ext = link.ext
+ else:
+ egg_info, ext = link.splitext()
+ if not ext:
+ return (LinkType.format_unsupported, "not a file")
+ if ext not in SUPPORTED_EXTENSIONS:
+ return (
+ LinkType.format_unsupported,
+ f"unsupported archive format: {ext}",
+ )
+ if "binary" not in self._formats and ext == WHEEL_EXTENSION:
+ reason = f"No binaries permitted for {self.project_name}"
+ return (LinkType.format_unsupported, reason)
+ if "macosx10" in link.path and ext == ".zip":
+ return (LinkType.format_unsupported, "macosx10 one")
+ if ext == WHEEL_EXTENSION:
+ try:
+ wheel = Wheel(link.filename)
+ except InvalidWheelFilename:
+ return (
+ LinkType.format_invalid,
+ "invalid wheel filename",
+ )
+ if canonicalize_name(wheel.name) != self._canonical_name:
+ reason = f"wrong project name (not {self.project_name})"
+ return (LinkType.different_project, reason)
+
+ supported_tags = self._target_python.get_unsorted_tags()
+ if not wheel.supported(supported_tags):
+ # Include the wheel's tags in the reason string to
+ # simplify troubleshooting compatibility issues.
+ file_tags = ", ".join(wheel.get_formatted_file_tags())
+ reason = (
+ f"none of the wheel's tags ({file_tags}) are compatible "
+ f"(run pip debug --verbose to show compatible tags)"
+ )
+ return (LinkType.platform_mismatch, reason)
+
+ version = wheel.version
+
+ # This should be up by the self.ok_binary check, but see issue 2700.
+ if "source" not in self._formats and ext != WHEEL_EXTENSION:
+ reason = f"No sources permitted for {self.project_name}"
+ return (LinkType.format_unsupported, reason)
+
+ if not version:
+ version = _extract_version_from_fragment(
+ egg_info,
+ self._canonical_name,
+ )
+ if not version:
+ reason = f"Missing project version for {self.project_name}"
+ return (LinkType.format_invalid, reason)
+
+ match = self._py_version_re.search(version)
+ if match:
+ version = version[: match.start()]
+ py_version = match.group(1)
+ if py_version != self._target_python.py_version:
+ return (
+ LinkType.platform_mismatch,
+ "Python version is incorrect",
+ )
+
+ supports_python = _check_link_requires_python(
+ link,
+ version_info=self._target_python.py_version_info,
+ ignore_requires_python=self._ignore_requires_python,
+ )
+ if not supports_python:
+ reason = f"{version} Requires-Python {link.requires_python}"
+ return (LinkType.requires_python_mismatch, reason)
+
+ logger.debug("Found link %s, version: %s", link, version)
+
+ return (LinkType.candidate, version)
+
+
+def filter_unallowed_hashes(
+ candidates: List[InstallationCandidate],
+ hashes: Optional[Hashes],
+ project_name: str,
+) -> List[InstallationCandidate]:
+ """
+ Filter out candidates whose hashes aren't allowed, and return a new
+ list of candidates.
+
+ If at least one candidate has an allowed hash, then all candidates with
+ either an allowed hash or no hash specified are returned. Otherwise,
+ the given candidates are returned.
+
+ Including the candidates with no hash specified when there is a match
+ allows a warning to be logged if there is a more preferred candidate
+ with no hash specified. Returning all candidates in the case of no
+ matches lets pip report the hash of the candidate that would otherwise
+ have been installed (e.g. permitting the user to more easily update
+ their requirements file with the desired hash).
+ """
+ if not hashes:
+ logger.debug(
+ "Given no hashes to check %s links for project %r: "
+ "discarding no candidates",
+ len(candidates),
+ project_name,
+ )
+ # Make sure we're not returning back the given value.
+ return list(candidates)
+
+ matches_or_no_digest = []
+ # Collect the non-matches for logging purposes.
+ non_matches = []
+ match_count = 0
+ for candidate in candidates:
+ link = candidate.link
+ if not link.has_hash:
+ pass
+ elif link.is_hash_allowed(hashes=hashes):
+ match_count += 1
+ else:
+ non_matches.append(candidate)
+ continue
+
+ matches_or_no_digest.append(candidate)
+
+ if match_count:
+ filtered = matches_or_no_digest
+ else:
+ # Make sure we're not returning back the given value.
+ filtered = list(candidates)
+
+ if len(filtered) == len(candidates):
+ discard_message = "discarding no candidates"
+ else:
+ discard_message = "discarding {} non-matches:\n {}".format(
+ len(non_matches),
+ "\n ".join(str(candidate.link) for candidate in non_matches),
+ )
+
+ logger.debug(
+ "Checked %s links for project %r against %s hashes "
+ "(%s matches, %s no digest): %s",
+ len(candidates),
+ project_name,
+ hashes.digest_count,
+ match_count,
+ len(matches_or_no_digest) - match_count,
+ discard_message,
+ )
+
+ return filtered
+
+
+@dataclass
+class CandidatePreferences:
+ """
+ Encapsulates some of the preferences for filtering and sorting
+ InstallationCandidate objects.
+ """
+
+ prefer_binary: bool = False
+ allow_all_prereleases: bool = False
+
+
+class BestCandidateResult:
+ """A collection of candidates, returned by `PackageFinder.find_best_candidate`.
+
+ This class is only intended to be instantiated by CandidateEvaluator's
+ `compute_best_candidate()` method.
+ """
+
+ def __init__(
+ self,
+ candidates: List[InstallationCandidate],
+ applicable_candidates: List[InstallationCandidate],
+ best_candidate: Optional[InstallationCandidate],
+ ) -> None:
+ """
+ :param candidates: A sequence of all available candidates found.
+ :param applicable_candidates: The applicable candidates.
+ :param best_candidate: The most preferred candidate found, or None
+ if no applicable candidates were found.
+ """
+ assert set(applicable_candidates) <= set(candidates)
+
+ if best_candidate is None:
+ assert not applicable_candidates
+ else:
+ assert best_candidate in applicable_candidates
+
+ self._applicable_candidates = applicable_candidates
+ self._candidates = candidates
+
+ self.best_candidate = best_candidate
+
+ def iter_all(self) -> Iterable[InstallationCandidate]:
+ """Iterate through all candidates."""
+ return iter(self._candidates)
+
+ def iter_applicable(self) -> Iterable[InstallationCandidate]:
+ """Iterate through the applicable candidates."""
+ return iter(self._applicable_candidates)
+
+
+class CandidateEvaluator:
+ """
+ Responsible for filtering and sorting candidates for installation based
+ on what tags are valid.
+ """
+
+ @classmethod
+ def create(
+ cls,
+ project_name: str,
+ target_python: Optional[TargetPython] = None,
+ prefer_binary: bool = False,
+ allow_all_prereleases: bool = False,
+ specifier: Optional[specifiers.BaseSpecifier] = None,
+ hashes: Optional[Hashes] = None,
+ ) -> "CandidateEvaluator":
+ """Create a CandidateEvaluator object.
+
+ :param target_python: The target Python interpreter to use when
+ checking compatibility. If None (the default), a TargetPython
+ object will be constructed from the running Python.
+ :param specifier: An optional object implementing `filter`
+ (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable
+ versions.
+ :param hashes: An optional collection of allowed hashes.
+ """
+ if target_python is None:
+ target_python = TargetPython()
+ if specifier is None:
+ specifier = specifiers.SpecifierSet()
+
+ supported_tags = target_python.get_sorted_tags()
+
+ return cls(
+ project_name=project_name,
+ supported_tags=supported_tags,
+ specifier=specifier,
+ prefer_binary=prefer_binary,
+ allow_all_prereleases=allow_all_prereleases,
+ hashes=hashes,
+ )
+
+ def __init__(
+ self,
+ project_name: str,
+ supported_tags: List[Tag],
+ specifier: specifiers.BaseSpecifier,
+ prefer_binary: bool = False,
+ allow_all_prereleases: bool = False,
+ hashes: Optional[Hashes] = None,
+ ) -> None:
+ """
+ :param supported_tags: The PEP 425 tags supported by the target
+ Python in order of preference (most preferred first).
+ """
+ self._allow_all_prereleases = allow_all_prereleases
+ self._hashes = hashes
+ self._prefer_binary = prefer_binary
+ self._project_name = project_name
+ self._specifier = specifier
+ self._supported_tags = supported_tags
+ # Since the index of the tag in the _supported_tags list is used
+ # as a priority, precompute a map from tag to index/priority to be
+ # used in wheel.find_most_preferred_tag.
+ self._wheel_tag_preferences = {
+ tag: idx for idx, tag in enumerate(supported_tags)
+ }
+
+ def get_applicable_candidates(
+ self,
+ candidates: List[InstallationCandidate],
+ ) -> List[InstallationCandidate]:
+ """
+ Return the applicable candidates from a list of candidates.
+ """
+ # Using None infers from the specifier instead.
+ allow_prereleases = self._allow_all_prereleases or None
+ specifier = self._specifier
+
+ # We turn the version object into a str here because otherwise
+ # when we're debundled but setuptools isn't, Python will see
+ # packaging.version.Version and
+ # pkg_resources._vendor.packaging.version.Version as different
+ # types. This way we'll use a str as a common data interchange
+ # format. If we stop using the pkg_resources provided specifier
+ # and start using our own, we can drop the cast to str().
+ candidates_and_versions = [(c, str(c.version)) for c in candidates]
+ versions = set(
+ specifier.filter(
+ (v for _, v in candidates_and_versions),
+ prereleases=allow_prereleases,
+ )
+ )
+
+ applicable_candidates = [c for c, v in candidates_and_versions if v in versions]
+ filtered_applicable_candidates = filter_unallowed_hashes(
+ candidates=applicable_candidates,
+ hashes=self._hashes,
+ project_name=self._project_name,
+ )
+
+ return sorted(filtered_applicable_candidates, key=self._sort_key)
+
+ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey:
+ """
+ Function to pass as the `key` argument to a call to sorted() to sort
+ InstallationCandidates by preference.
+
+ Returns a tuple such that tuples sorting as greater using Python's
+ default comparison operator are more preferred.
+
+ The preference is as follows:
+
+ First and foremost, candidates with allowed (matching) hashes are
+ always preferred over candidates without matching hashes. This is
+ because e.g. if the only candidate with an allowed hash is yanked,
+ we still want to use that candidate.
+
+ Second, excepting hash considerations, candidates that have been
+ yanked (in the sense of PEP 592) are always less preferred than
+ candidates that haven't been yanked. Then:
+
+ If not finding wheels, they are sorted by version only.
+ If finding wheels, then the sort order is by version, then:
+ 1. existing installs
+ 2. wheels ordered via Wheel.support_index_min(self._supported_tags)
+ 3. source archives
+ If prefer_binary was set, then all wheels are sorted above sources.
+
+ Note: it was considered to embed this logic into the Link
+ comparison operators, but then different sdist links
+ with the same version, would have to be considered equal
+ """
+ valid_tags = self._supported_tags
+ support_num = len(valid_tags)
+ build_tag: BuildTag = ()
+ binary_preference = 0
+ link = candidate.link
+ if link.is_wheel:
+ # can raise InvalidWheelFilename
+ wheel = Wheel(link.filename)
+ try:
+ pri = -(
+ wheel.find_most_preferred_tag(
+ valid_tags, self._wheel_tag_preferences
+ )
+ )
+ except ValueError:
+ raise UnsupportedWheel(
+ f"{wheel.filename} is not a supported wheel for this platform. It "
+ "can't be sorted."
+ )
+ if self._prefer_binary:
+ binary_preference = 1
+ if wheel.build_tag is not None:
+ match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
+ assert match is not None, "guaranteed by filename validation"
+ build_tag_groups = match.groups()
+ build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
+ else: # sdist
+ pri = -(support_num)
+ has_allowed_hash = int(link.is_hash_allowed(self._hashes))
+ yank_value = -1 * int(link.is_yanked) # -1 for yanked.
+ return (
+ has_allowed_hash,
+ yank_value,
+ binary_preference,
+ candidate.version,
+ pri,
+ build_tag,
+ )
+
+ def sort_best_candidate(
+ self,
+ candidates: List[InstallationCandidate],
+ ) -> Optional[InstallationCandidate]:
+ """
+ Return the best candidate per the instance's sort order, or None if
+ no candidate is acceptable.
+ """
+ if not candidates:
+ return None
+ best_candidate = max(candidates, key=self._sort_key)
+ return best_candidate
+
+ def compute_best_candidate(
+ self,
+ candidates: List[InstallationCandidate],
+ ) -> BestCandidateResult:
+ """
+ Compute and return a `BestCandidateResult` instance.
+ """
+ applicable_candidates = self.get_applicable_candidates(candidates)
+
+ best_candidate = self.sort_best_candidate(applicable_candidates)
+
+ return BestCandidateResult(
+ candidates,
+ applicable_candidates=applicable_candidates,
+ best_candidate=best_candidate,
+ )
+
+
+class PackageFinder:
+ """This finds packages.
+
+ This is meant to match easy_install's technique for looking for
+ packages, by reading pages and looking for appropriate links.
+ """
+
+ def __init__(
+ self,
+ link_collector: LinkCollector,
+ target_python: TargetPython,
+ allow_yanked: bool,
+ format_control: Optional[FormatControl] = None,
+ candidate_prefs: Optional[CandidatePreferences] = None,
+ ignore_requires_python: Optional[bool] = None,
+ ) -> None:
+ """
+ This constructor is primarily meant to be used by the create() class
+ method and from tests.
+
+ :param format_control: A FormatControl object, used to control
+ the selection of source packages / binary packages when consulting
+ the index and links.
+ :param candidate_prefs: Options to use when creating a
+ CandidateEvaluator object.
+ """
+ if candidate_prefs is None:
+ candidate_prefs = CandidatePreferences()
+
+ format_control = format_control or FormatControl(set(), set())
+
+ self._allow_yanked = allow_yanked
+ self._candidate_prefs = candidate_prefs
+ self._ignore_requires_python = ignore_requires_python
+ self._link_collector = link_collector
+ self._target_python = target_python
+
+ self.format_control = format_control
+
+ # These are boring links that have already been logged somehow.
+ self._logged_links: Set[Tuple[Link, LinkType, str]] = set()
+
+ # Don't include an allow_yanked default value to make sure each call
+ # site considers whether yanked releases are allowed. This also causes
+ # that decision to be made explicit in the calling code, which helps
+ # people when reading the code.
+ @classmethod
+ def create(
+ cls,
+ link_collector: LinkCollector,
+ selection_prefs: SelectionPreferences,
+ target_python: Optional[TargetPython] = None,
+ ) -> "PackageFinder":
+ """Create a PackageFinder.
+
+ :param selection_prefs: The candidate selection preferences, as a
+ SelectionPreferences object.
+ :param target_python: The target Python interpreter to use when
+ checking compatibility. If None (the default), a TargetPython
+ object will be constructed from the running Python.
+ """
+ if target_python is None:
+ target_python = TargetPython()
+
+ candidate_prefs = CandidatePreferences(
+ prefer_binary=selection_prefs.prefer_binary,
+ allow_all_prereleases=selection_prefs.allow_all_prereleases,
+ )
+
+ return cls(
+ candidate_prefs=candidate_prefs,
+ link_collector=link_collector,
+ target_python=target_python,
+ allow_yanked=selection_prefs.allow_yanked,
+ format_control=selection_prefs.format_control,
+ ignore_requires_python=selection_prefs.ignore_requires_python,
+ )
+
+ @property
+ def target_python(self) -> TargetPython:
+ return self._target_python
+
+ @property
+ def search_scope(self) -> SearchScope:
+ return self._link_collector.search_scope
+
+ @search_scope.setter
+ def search_scope(self, search_scope: SearchScope) -> None:
+ self._link_collector.search_scope = search_scope
+
+ @property
+ def find_links(self) -> List[str]:
+ return self._link_collector.find_links
+
+ @property
+ def index_urls(self) -> List[str]:
+ return self.search_scope.index_urls
+
+ @property
+ def trusted_hosts(self) -> Iterable[str]:
+ for host_port in self._link_collector.session.pip_trusted_origins:
+ yield build_netloc(*host_port)
+
+ @property
+ def allow_all_prereleases(self) -> bool:
+ return self._candidate_prefs.allow_all_prereleases
+
+ def set_allow_all_prereleases(self) -> None:
+ self._candidate_prefs.allow_all_prereleases = True
+
+ @property
+ def prefer_binary(self) -> bool:
+ return self._candidate_prefs.prefer_binary
+
+ def set_prefer_binary(self) -> None:
+ self._candidate_prefs.prefer_binary = True
+
+ def requires_python_skipped_reasons(self) -> List[str]:
+ reasons = {
+ detail
+ for _, result, detail in self._logged_links
+ if result == LinkType.requires_python_mismatch
+ }
+ return sorted(reasons)
+
+ def make_link_evaluator(self, project_name: str) -> LinkEvaluator:
+ canonical_name = canonicalize_name(project_name)
+ formats = self.format_control.get_allowed_formats(canonical_name)
+
+ return LinkEvaluator(
+ project_name=project_name,
+ canonical_name=canonical_name,
+ formats=formats,
+ target_python=self._target_python,
+ allow_yanked=self._allow_yanked,
+ ignore_requires_python=self._ignore_requires_python,
+ )
+
+ def _sort_links(self, links: Iterable[Link]) -> List[Link]:
+ """
+ Returns elements of links in order, non-egg links first, egg links
+ second, while eliminating duplicates
+ """
+ eggs, no_eggs = [], []
+ seen: Set[Link] = set()
+ for link in links:
+ if link not in seen:
+ seen.add(link)
+ if link.egg_fragment:
+ eggs.append(link)
+ else:
+ no_eggs.append(link)
+ return no_eggs + eggs
+
+ def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None:
+ entry = (link, result, detail)
+ if entry not in self._logged_links:
+ # Put the link at the end so the reason is more visible and because
+ # the link string is usually very long.
+ logger.debug("Skipping link: %s: %s", detail, link)
+ self._logged_links.add(entry)
+
+ def get_install_candidate(
+ self, link_evaluator: LinkEvaluator, link: Link
+ ) -> Optional[InstallationCandidate]:
+ """
+ If the link is a candidate for install, convert it to an
+ InstallationCandidate and return it. Otherwise, return None.
+ """
+ result, detail = link_evaluator.evaluate_link(link)
+ if result != LinkType.candidate:
+ self._log_skipped_link(link, result, detail)
+ return None
+
+ try:
+ return InstallationCandidate(
+ name=link_evaluator.project_name,
+ link=link,
+ version=detail,
+ )
+ except InvalidVersion:
+ return None
+
+ def evaluate_links(
+ self, link_evaluator: LinkEvaluator, links: Iterable[Link]
+ ) -> List[InstallationCandidate]:
+ """
+ Convert links that are candidates to InstallationCandidate objects.
+ """
+ candidates = []
+ for link in self._sort_links(links):
+ candidate = self.get_install_candidate(link_evaluator, link)
+ if candidate is not None:
+ candidates.append(candidate)
+
+ return candidates
+
+ def process_project_url(
+ self, project_url: Link, link_evaluator: LinkEvaluator
+ ) -> List[InstallationCandidate]:
+ logger.debug(
+ "Fetching project page and analyzing links: %s",
+ project_url,
+ )
+ index_response = self._link_collector.fetch_response(project_url)
+ if index_response is None:
+ return []
+
+ page_links = list(parse_links(index_response))
+
+ with indent_log():
+ package_links = self.evaluate_links(
+ link_evaluator,
+ links=page_links,
+ )
+
+ return package_links
+
+ @functools.lru_cache(maxsize=None)
+ def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]:
+ """Find all available InstallationCandidate for project_name
+
+ This checks index_urls and find_links.
+ All versions found are returned as an InstallationCandidate list.
+
+ See LinkEvaluator.evaluate_link() for details on which files
+ are accepted.
+ """
+ link_evaluator = self.make_link_evaluator(project_name)
+
+ collected_sources = self._link_collector.collect_sources(
+ project_name=project_name,
+ candidates_from_page=functools.partial(
+ self.process_project_url,
+ link_evaluator=link_evaluator,
+ ),
+ )
+
+ page_candidates_it = itertools.chain.from_iterable(
+ source.page_candidates()
+ for sources in collected_sources
+ for source in sources
+ if source is not None
+ )
+ page_candidates = list(page_candidates_it)
+
+ file_links_it = itertools.chain.from_iterable(
+ source.file_links()
+ for sources in collected_sources
+ for source in sources
+ if source is not None
+ )
+ file_candidates = self.evaluate_links(
+ link_evaluator,
+ sorted(file_links_it, reverse=True),
+ )
+
+ if logger.isEnabledFor(logging.DEBUG) and file_candidates:
+ paths = []
+ for candidate in file_candidates:
+ assert candidate.link.url # we need to have a URL
+ try:
+ paths.append(candidate.link.file_path)
+ except Exception:
+ paths.append(candidate.link.url) # it's not a local file
+
+ logger.debug("Local files found: %s", ", ".join(paths))
+
+ # This is an intentional priority ordering
+ return file_candidates + page_candidates
+
+ def make_candidate_evaluator(
+ self,
+ project_name: str,
+ specifier: Optional[specifiers.BaseSpecifier] = None,
+ hashes: Optional[Hashes] = None,
+ ) -> CandidateEvaluator:
+ """Create a CandidateEvaluator object to use."""
+ candidate_prefs = self._candidate_prefs
+ return CandidateEvaluator.create(
+ project_name=project_name,
+ target_python=self._target_python,
+ prefer_binary=candidate_prefs.prefer_binary,
+ allow_all_prereleases=candidate_prefs.allow_all_prereleases,
+ specifier=specifier,
+ hashes=hashes,
+ )
+
+ @functools.lru_cache(maxsize=None)
+ def find_best_candidate(
+ self,
+ project_name: str,
+ specifier: Optional[specifiers.BaseSpecifier] = None,
+ hashes: Optional[Hashes] = None,
+ ) -> BestCandidateResult:
+ """Find matches for the given project and specifier.
+
+ :param specifier: An optional object implementing `filter`
+ (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable
+ versions.
+
+ :return: A `BestCandidateResult` instance.
+ """
+ candidates = self.find_all_candidates(project_name)
+ candidate_evaluator = self.make_candidate_evaluator(
+ project_name=project_name,
+ specifier=specifier,
+ hashes=hashes,
+ )
+ return candidate_evaluator.compute_best_candidate(candidates)
+
+ def find_requirement(
+ self, req: InstallRequirement, upgrade: bool
+ ) -> Optional[InstallationCandidate]:
+ """Try to find a Link matching req
+
+ Expects req, an InstallRequirement and upgrade, a boolean
+ Returns a InstallationCandidate if found,
+ Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
+ """
+ hashes = req.hashes(trust_internet=False)
+ best_candidate_result = self.find_best_candidate(
+ req.name,
+ specifier=req.specifier,
+ hashes=hashes,
+ )
+ best_candidate = best_candidate_result.best_candidate
+
+ installed_version: Optional[_BaseVersion] = None
+ if req.satisfied_by is not None:
+ installed_version = req.satisfied_by.version
+
+ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
+ # This repeated parse_version and str() conversion is needed to
+ # handle different vendoring sources from pip and pkg_resources.
+ # If we stop using the pkg_resources provided specifier and start
+ # using our own, we can drop the cast to str().
+ return (
+ ", ".join(
+ sorted(
+ {str(c.version) for c in cand_iter},
+ key=parse_version,
+ )
+ )
+ or "none"
+ )
+
+ if installed_version is None and best_candidate is None:
+ logger.critical(
+ "Could not find a version that satisfies the requirement %s "
+ "(from versions: %s)",
+ req,
+ _format_versions(best_candidate_result.iter_all()),
+ )
+
+ raise DistributionNotFound(f"No matching distribution found for {req}")
+
+ def _should_install_candidate(
+ candidate: Optional[InstallationCandidate],
+ ) -> "TypeGuard[InstallationCandidate]":
+ if installed_version is None:
+ return True
+ if best_candidate is None:
+ return False
+ return best_candidate.version > installed_version
+
+ if not upgrade and installed_version is not None:
+ if _should_install_candidate(best_candidate):
+ logger.debug(
+ "Existing installed version (%s) satisfies requirement "
+ "(most up-to-date version is %s)",
+ installed_version,
+ best_candidate.version,
+ )
+ else:
+ logger.debug(
+ "Existing installed version (%s) is most up-to-date and "
+ "satisfies requirement",
+ installed_version,
+ )
+ return None
+
+ if _should_install_candidate(best_candidate):
+ logger.debug(
+ "Using version %s (newest of versions: %s)",
+ best_candidate.version,
+ _format_versions(best_candidate_result.iter_applicable()),
+ )
+ return best_candidate
+
+ # We have an existing version, and its the best version
+ logger.debug(
+ "Installed version (%s) is most up-to-date (past versions: %s)",
+ installed_version,
+ _format_versions(best_candidate_result.iter_applicable()),
+ )
+ raise BestVersionAlreadyInstalled
+
+
+def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
+ """Find the separator's index based on the package's canonical name.
+
+ :param fragment: A + filename "fragment" (stem) or
+ egg fragment.
+ :param canonical_name: The package's canonical name.
+
+ This function is needed since the canonicalized name does not necessarily
+ have the same length as the egg info's name part. An example::
+
+ >>> fragment = 'foo__bar-1.0'
+ >>> canonical_name = 'foo-bar'
+ >>> _find_name_version_sep(fragment, canonical_name)
+ 8
+ """
+ # Project name and version must be separated by one single dash. Find all
+ # occurrences of dashes; if the string in front of it matches the canonical
+ # name, this is the one separating the name and version parts.
+ for i, c in enumerate(fragment):
+ if c != "-":
+ continue
+ if canonicalize_name(fragment[:i]) == canonical_name:
+ return i
+ raise ValueError(f"{fragment} does not match {canonical_name}")
+
+
+def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]:
+ """Parse the version string from a + filename
+ "fragment" (stem) or egg fragment.
+
+ :param fragment: The string to parse. E.g. foo-2.1
+ :param canonical_name: The canonicalized name of the package this
+ belongs to.
+ """
+ try:
+ version_start = _find_name_version_sep(fragment, canonical_name) + 1
+ except ValueError:
+ return None
+ version = fragment[version_start:]
+ if not version:
+ return None
+ return version
diff --git a/env/Lib/site-packages/pip/_internal/index/sources.py b/env/Lib/site-packages/pip/_internal/index/sources.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dafb30e6eb843ac56315dd5d0ab223bf4f740b8
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/index/sources.py
@@ -0,0 +1,284 @@
+import logging
+import mimetypes
+import os
+from collections import defaultdict
+from typing import Callable, Dict, Iterable, List, Optional, Tuple
+
+from pip._vendor.packaging.utils import (
+ InvalidSdistFilename,
+ InvalidWheelFilename,
+ canonicalize_name,
+ parse_sdist_filename,
+ parse_wheel_filename,
+)
+
+from pip._internal.models.candidate import InstallationCandidate
+from pip._internal.models.link import Link
+from pip._internal.utils.urls import path_to_url, url_to_path
+from pip._internal.vcs import is_url
+
+logger = logging.getLogger(__name__)
+
+FoundCandidates = Iterable[InstallationCandidate]
+FoundLinks = Iterable[Link]
+CandidatesFromPage = Callable[[Link], Iterable[InstallationCandidate]]
+PageValidator = Callable[[Link], bool]
+
+
+class LinkSource:
+ @property
+ def link(self) -> Optional[Link]:
+ """Returns the underlying link, if there's one."""
+ raise NotImplementedError()
+
+ def page_candidates(self) -> FoundCandidates:
+ """Candidates found by parsing an archive listing HTML file."""
+ raise NotImplementedError()
+
+ def file_links(self) -> FoundLinks:
+ """Links found by specifying archives directly."""
+ raise NotImplementedError()
+
+
+def _is_html_file(file_url: str) -> bool:
+ return mimetypes.guess_type(file_url, strict=False)[0] == "text/html"
+
+
+class _FlatDirectoryToUrls:
+ """Scans directory and caches results"""
+
+ def __init__(self, path: str) -> None:
+ self._path = path
+ self._page_candidates: List[str] = []
+ self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list)
+ self._scanned_directory = False
+
+ def _scan_directory(self) -> None:
+ """Scans directory once and populates both page_candidates
+ and project_name_to_urls at the same time
+ """
+ for entry in os.scandir(self._path):
+ url = path_to_url(entry.path)
+ if _is_html_file(url):
+ self._page_candidates.append(url)
+ continue
+
+ # File must have a valid wheel or sdist name,
+ # otherwise not worth considering as a package
+ try:
+ project_filename = parse_wheel_filename(entry.name)[0]
+ except InvalidWheelFilename:
+ try:
+ project_filename = parse_sdist_filename(entry.name)[0]
+ except InvalidSdistFilename:
+ continue
+
+ self._project_name_to_urls[project_filename].append(url)
+ self._scanned_directory = True
+
+ @property
+ def page_candidates(self) -> List[str]:
+ if not self._scanned_directory:
+ self._scan_directory()
+
+ return self._page_candidates
+
+ @property
+ def project_name_to_urls(self) -> Dict[str, List[str]]:
+ if not self._scanned_directory:
+ self._scan_directory()
+
+ return self._project_name_to_urls
+
+
+class _FlatDirectorySource(LinkSource):
+ """Link source specified by ``--find-links=``.
+
+ This looks the content of the directory, and returns:
+
+ * ``page_candidates``: Links listed on each HTML file in the directory.
+ * ``file_candidates``: Archives in the directory.
+ """
+
+ _paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {}
+
+ def __init__(
+ self,
+ candidates_from_page: CandidatesFromPage,
+ path: str,
+ project_name: str,
+ ) -> None:
+ self._candidates_from_page = candidates_from_page
+ self._project_name = canonicalize_name(project_name)
+
+ # Get existing instance of _FlatDirectoryToUrls if it exists
+ if path in self._paths_to_urls:
+ self._path_to_urls = self._paths_to_urls[path]
+ else:
+ self._path_to_urls = _FlatDirectoryToUrls(path=path)
+ self._paths_to_urls[path] = self._path_to_urls
+
+ @property
+ def link(self) -> Optional[Link]:
+ return None
+
+ def page_candidates(self) -> FoundCandidates:
+ for url in self._path_to_urls.page_candidates:
+ yield from self._candidates_from_page(Link(url))
+
+ def file_links(self) -> FoundLinks:
+ for url in self._path_to_urls.project_name_to_urls[self._project_name]:
+ yield Link(url)
+
+
+class _LocalFileSource(LinkSource):
+ """``--find-links=`` or ``--[extra-]index-url=``.
+
+ If a URL is supplied, it must be a ``file:`` URL. If a path is supplied to
+ the option, it is converted to a URL first. This returns:
+
+ * ``page_candidates``: Links listed on an HTML file.
+ * ``file_candidates``: The non-HTML file.
+ """
+
+ def __init__(
+ self,
+ candidates_from_page: CandidatesFromPage,
+ link: Link,
+ ) -> None:
+ self._candidates_from_page = candidates_from_page
+ self._link = link
+
+ @property
+ def link(self) -> Optional[Link]:
+ return self._link
+
+ def page_candidates(self) -> FoundCandidates:
+ if not _is_html_file(self._link.url):
+ return
+ yield from self._candidates_from_page(self._link)
+
+ def file_links(self) -> FoundLinks:
+ if _is_html_file(self._link.url):
+ return
+ yield self._link
+
+
+class _RemoteFileSource(LinkSource):
+ """``--find-links=`` or ``--[extra-]index-url=``.
+
+ This returns:
+
+ * ``page_candidates``: Links listed on an HTML file.
+ * ``file_candidates``: The non-HTML file.
+ """
+
+ def __init__(
+ self,
+ candidates_from_page: CandidatesFromPage,
+ page_validator: PageValidator,
+ link: Link,
+ ) -> None:
+ self._candidates_from_page = candidates_from_page
+ self._page_validator = page_validator
+ self._link = link
+
+ @property
+ def link(self) -> Optional[Link]:
+ return self._link
+
+ def page_candidates(self) -> FoundCandidates:
+ if not self._page_validator(self._link):
+ return
+ yield from self._candidates_from_page(self._link)
+
+ def file_links(self) -> FoundLinks:
+ yield self._link
+
+
+class _IndexDirectorySource(LinkSource):
+ """``--[extra-]index-url=``.
+
+ This is treated like a remote URL; ``candidates_from_page`` contains logic
+ for this by appending ``index.html`` to the link.
+ """
+
+ def __init__(
+ self,
+ candidates_from_page: CandidatesFromPage,
+ link: Link,
+ ) -> None:
+ self._candidates_from_page = candidates_from_page
+ self._link = link
+
+ @property
+ def link(self) -> Optional[Link]:
+ return self._link
+
+ def page_candidates(self) -> FoundCandidates:
+ yield from self._candidates_from_page(self._link)
+
+ def file_links(self) -> FoundLinks:
+ return ()
+
+
+def build_source(
+ location: str,
+ *,
+ candidates_from_page: CandidatesFromPage,
+ page_validator: PageValidator,
+ expand_dir: bool,
+ cache_link_parsing: bool,
+ project_name: str,
+) -> Tuple[Optional[str], Optional[LinkSource]]:
+ path: Optional[str] = None
+ url: Optional[str] = None
+ if os.path.exists(location): # Is a local path.
+ url = path_to_url(location)
+ path = location
+ elif location.startswith("file:"): # A file: URL.
+ url = location
+ path = url_to_path(location)
+ elif is_url(location):
+ url = location
+
+ if url is None:
+ msg = (
+ "Location '%s' is ignored: "
+ "it is either a non-existing path or lacks a specific scheme."
+ )
+ logger.warning(msg, location)
+ return (None, None)
+
+ if path is None:
+ source: LinkSource = _RemoteFileSource(
+ candidates_from_page=candidates_from_page,
+ page_validator=page_validator,
+ link=Link(url, cache_link_parsing=cache_link_parsing),
+ )
+ return (url, source)
+
+ if os.path.isdir(path):
+ if expand_dir:
+ source = _FlatDirectorySource(
+ candidates_from_page=candidates_from_page,
+ path=path,
+ project_name=project_name,
+ )
+ else:
+ source = _IndexDirectorySource(
+ candidates_from_page=candidates_from_page,
+ link=Link(url, cache_link_parsing=cache_link_parsing),
+ )
+ return (url, source)
+ elif os.path.isfile(path):
+ source = _LocalFileSource(
+ candidates_from_page=candidates_from_page,
+ link=Link(url, cache_link_parsing=cache_link_parsing),
+ )
+ return (url, source)
+ logger.warning(
+ "Location '%s' is ignored: it is neither a file nor a directory.",
+ location,
+ )
+ return (url, None)
diff --git a/env/Lib/site-packages/pip/_internal/locations/__init__.py b/env/Lib/site-packages/pip/_internal/locations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..32382be7fe5f6781047e9774679aa8bcf5bbce8a
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/locations/__init__.py
@@ -0,0 +1,456 @@
+import functools
+import logging
+import os
+import pathlib
+import sys
+import sysconfig
+from typing import Any, Dict, Generator, Optional, Tuple
+
+from pip._internal.models.scheme import SCHEME_KEYS, Scheme
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.virtualenv import running_under_virtualenv
+
+from . import _sysconfig
+from .base import (
+ USER_CACHE_DIR,
+ get_major_minor_version,
+ get_src_prefix,
+ is_osx_framework,
+ site_packages,
+ user_site,
+)
+
+__all__ = [
+ "USER_CACHE_DIR",
+ "get_bin_prefix",
+ "get_bin_user",
+ "get_major_minor_version",
+ "get_platlib",
+ "get_purelib",
+ "get_scheme",
+ "get_src_prefix",
+ "site_packages",
+ "user_site",
+]
+
+
+logger = logging.getLogger(__name__)
+
+
+_PLATLIBDIR: str = getattr(sys, "platlibdir", "lib")
+
+_USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10)
+
+
+def _should_use_sysconfig() -> bool:
+ """This function determines the value of _USE_SYSCONFIG.
+
+ By default, pip uses sysconfig on Python 3.10+.
+ But Python distributors can override this decision by setting:
+ sysconfig._PIP_USE_SYSCONFIG = True / False
+ Rationale in https://github.com/pypa/pip/issues/10647
+
+ This is a function for testability, but should be constant during any one
+ run.
+ """
+ return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT))
+
+
+_USE_SYSCONFIG = _should_use_sysconfig()
+
+if not _USE_SYSCONFIG:
+ # Import distutils lazily to avoid deprecation warnings,
+ # but import it soon enough that it is in memory and available during
+ # a pip reinstall.
+ from . import _distutils
+
+# Be noisy about incompatibilities if this platforms "should" be using
+# sysconfig, but is explicitly opting out and using distutils instead.
+if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG:
+ _MISMATCH_LEVEL = logging.WARNING
+else:
+ _MISMATCH_LEVEL = logging.DEBUG
+
+
+def _looks_like_bpo_44860() -> bool:
+ """The resolution to bpo-44860 will change this incorrect platlib.
+
+ See .
+ """
+ from distutils.command.install import INSTALL_SCHEMES
+
+ try:
+ unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
+ except KeyError:
+ return False
+ return unix_user_platlib == "$usersite"
+
+
+def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
+ platlib = scheme["platlib"]
+ if "/$platlibdir/" in platlib:
+ platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
+ if "/lib64/" not in platlib:
+ return False
+ unpatched = platlib.replace("/lib64/", "/lib/")
+ return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_red_hat_lib() -> bool:
+ """Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
+
+ This is the only way I can see to tell a Red Hat-patched Python.
+ """
+ from distutils.command.install import INSTALL_SCHEMES
+
+ return all(
+ k in INSTALL_SCHEMES
+ and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k])
+ for k in ("unix_prefix", "unix_home")
+ )
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_debian_scheme() -> bool:
+ """Debian adds two additional schemes."""
+ from distutils.command.install import INSTALL_SCHEMES
+
+ return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_red_hat_scheme() -> bool:
+ """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
+
+ Red Hat's ``00251-change-user-install-location.patch`` changes the install
+ command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is
+ (fortunately?) done quite unconditionally, so we create a default command
+ object without any configuration to detect this.
+ """
+ from distutils.command.install import install
+ from distutils.dist import Distribution
+
+ cmd: Any = install(Distribution())
+ cmd.finalize_options()
+ return (
+ cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
+ and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local"
+ )
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_slackware_scheme() -> bool:
+ """Slackware patches sysconfig but fails to patch distutils and site.
+
+ Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
+ path, but does not do the same to the site module.
+ """
+ if user_site is None: # User-site not available.
+ return False
+ try:
+ paths = sysconfig.get_paths(scheme="posix_user", expand=False)
+ except KeyError: # User-site not available.
+ return False
+ return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_msys2_mingw_scheme() -> bool:
+ """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
+
+ However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is
+ likely going to be included in their 3.10 release, so we ignore the warning.
+ See msys2/MINGW-packages#9319.
+
+ MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
+ and is missing the final ``"site-packages"``.
+ """
+ paths = sysconfig.get_paths("nt", expand=False)
+ return all(
+ "Lib" not in p and "lib" in p and not p.endswith("site-packages")
+ for p in (paths[key] for key in ("platlib", "purelib"))
+ )
+
+
+def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]:
+ ldversion = sysconfig.get_config_var("LDVERSION")
+ abiflags = getattr(sys, "abiflags", None)
+
+ # LDVERSION does not end with sys.abiflags. Just return the path unchanged.
+ if not ldversion or not abiflags or not ldversion.endswith(abiflags):
+ yield from parts
+ return
+
+ # Strip sys.abiflags from LDVERSION-based path components.
+ for part in parts:
+ if part.endswith(ldversion):
+ part = part[: (0 - len(abiflags))]
+ yield part
+
+
+@functools.lru_cache(maxsize=None)
+def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
+ issue_url = "https://github.com/pypa/pip/issues/10151"
+ message = (
+ "Value for %s does not match. Please report this to <%s>"
+ "\ndistutils: %s"
+ "\nsysconfig: %s"
+ )
+ logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new)
+
+
+def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool:
+ if old == new:
+ return False
+ _warn_mismatched(old, new, key=key)
+ return True
+
+
+@functools.lru_cache(maxsize=None)
+def _log_context(
+ *,
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ prefix: Optional[str] = None,
+) -> None:
+ parts = [
+ "Additional context:",
+ "user = %r",
+ "home = %r",
+ "root = %r",
+ "prefix = %r",
+ ]
+
+ logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix)
+
+
+def get_scheme(
+ dist_name: str,
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ isolated: bool = False,
+ prefix: Optional[str] = None,
+) -> Scheme:
+ new = _sysconfig.get_scheme(
+ dist_name,
+ user=user,
+ home=home,
+ root=root,
+ isolated=isolated,
+ prefix=prefix,
+ )
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_scheme(
+ dist_name,
+ user=user,
+ home=home,
+ root=root,
+ isolated=isolated,
+ prefix=prefix,
+ )
+
+ warning_contexts = []
+ for k in SCHEME_KEYS:
+ old_v = pathlib.Path(getattr(old, k))
+ new_v = pathlib.Path(getattr(new, k))
+
+ if old_v == new_v:
+ continue
+
+ # distutils incorrectly put PyPy packages under ``site-packages/python``
+ # in the ``posix_home`` scheme, but PyPy devs said they expect the
+ # directory name to be ``pypy`` instead. So we treat this as a bug fix
+ # and not warn about it. See bpo-43307 and python/cpython#24628.
+ skip_pypy_special_case = (
+ sys.implementation.name == "pypy"
+ and home is not None
+ and k in ("platlib", "purelib")
+ and old_v.parent == new_v.parent
+ and old_v.name.startswith("python")
+ and new_v.name.startswith("pypy")
+ )
+ if skip_pypy_special_case:
+ continue
+
+ # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in
+ # the ``include`` value, but distutils's ``headers`` does. We'll let
+ # CPython decide whether this is a bug or feature. See bpo-43948.
+ skip_osx_framework_user_special_case = (
+ user
+ and is_osx_framework()
+ and k == "headers"
+ and old_v.parent.parent == new_v.parent
+ and old_v.parent.name.startswith("python")
+ )
+ if skip_osx_framework_user_special_case:
+ continue
+
+ # On Red Hat and derived Linux distributions, distutils is patched to
+ # use "lib64" instead of "lib" for platlib.
+ if k == "platlib" and _looks_like_red_hat_lib():
+ continue
+
+ # On Python 3.9+, sysconfig's posix_user scheme sets platlib against
+ # sys.platlibdir, but distutils's unix_user incorrectly coninutes
+ # using the same $usersite for both platlib and purelib. This creates a
+ # mismatch when sys.platlibdir is not "lib".
+ skip_bpo_44860 = (
+ user
+ and k == "platlib"
+ and not WINDOWS
+ and sys.version_info >= (3, 9)
+ and _PLATLIBDIR != "lib"
+ and _looks_like_bpo_44860()
+ )
+ if skip_bpo_44860:
+ continue
+
+ # Slackware incorrectly patches posix_user to use lib64 instead of lib,
+ # but not usersite to match the location.
+ skip_slackware_user_scheme = (
+ user
+ and k in ("platlib", "purelib")
+ and not WINDOWS
+ and _looks_like_slackware_scheme()
+ )
+ if skip_slackware_user_scheme:
+ continue
+
+ # Both Debian and Red Hat patch Python to place the system site under
+ # /usr/local instead of /usr. Debian also places lib in dist-packages
+ # instead of site-packages, but the /usr/local check should cover it.
+ skip_linux_system_special_case = (
+ not (user or home or prefix or running_under_virtualenv())
+ and old_v.parts[1:3] == ("usr", "local")
+ and len(new_v.parts) > 1
+ and new_v.parts[1] == "usr"
+ and (len(new_v.parts) < 3 or new_v.parts[2] != "local")
+ and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
+ )
+ if skip_linux_system_special_case:
+ continue
+
+ # MSYS2 MINGW's sysconfig patch does not include the "site-packages"
+ # part of the path. This is incorrect and will be fixed in MSYS.
+ skip_msys2_mingw_bug = (
+ WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme()
+ )
+ if skip_msys2_mingw_bug:
+ continue
+
+ # CPython's POSIX install script invokes pip (via ensurepip) against the
+ # interpreter located in the source tree, not the install site. This
+ # triggers special logic in sysconfig that's not present in distutils.
+ # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194
+ skip_cpython_build = (
+ sysconfig.is_python_build(check_home=True)
+ and not WINDOWS
+ and k in ("headers", "include", "platinclude")
+ )
+ if skip_cpython_build:
+ continue
+
+ warning_contexts.append((old_v, new_v, f"scheme.{k}"))
+
+ if not warning_contexts:
+ return old
+
+ # Check if this path mismatch is caused by distutils config files. Those
+ # files will no longer work once we switch to sysconfig, so this raises a
+ # deprecation message for them.
+ default_old = _distutils.distutils_scheme(
+ dist_name,
+ user,
+ home,
+ root,
+ isolated,
+ prefix,
+ ignore_config_files=True,
+ )
+ if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS):
+ deprecated(
+ reason=(
+ "Configuring installation scheme with distutils config files "
+ "is deprecated and will no longer work in the near future. If you "
+ "are using a Homebrew or Linuxbrew Python, please see discussion "
+ "at https://github.com/Homebrew/homebrew-core/issues/76621"
+ ),
+ replacement=None,
+ gone_in=None,
+ )
+ return old
+
+ # Post warnings about this mismatch so user can report them back.
+ for old_v, new_v, key in warning_contexts:
+ _warn_mismatched(old_v, new_v, key=key)
+ _log_context(user=user, home=home, root=root, prefix=prefix)
+
+ return old
+
+
+def get_bin_prefix() -> str:
+ new = _sysconfig.get_bin_prefix()
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_bin_prefix()
+ if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"):
+ _log_context()
+ return old
+
+
+def get_bin_user() -> str:
+ return _sysconfig.get_scheme("", user=True).scripts
+
+
+def _looks_like_deb_system_dist_packages(value: str) -> bool:
+ """Check if the value is Debian's APT-controlled dist-packages.
+
+ Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the
+ default package path controlled by APT, but does not patch ``sysconfig`` to
+ do the same. This is similar to the bug worked around in ``get_scheme()``,
+ but here the default is ``deb_system`` instead of ``unix_local``. Ultimately
+ we can't do anything about this Debian bug, and this detection allows us to
+ skip the warning when needed.
+ """
+ if not _looks_like_debian_scheme():
+ return False
+ if value == "/usr/lib/python3/dist-packages":
+ return True
+ return False
+
+
+def get_purelib() -> str:
+ """Return the default pure-Python lib location."""
+ new = _sysconfig.get_purelib()
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_purelib()
+ if _looks_like_deb_system_dist_packages(old):
+ return old
+ if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"):
+ _log_context()
+ return old
+
+
+def get_platlib() -> str:
+ """Return the default platform-shared lib location."""
+ new = _sysconfig.get_platlib()
+ if _USE_SYSCONFIG:
+ return new
+
+ from . import _distutils
+
+ old = _distutils.get_platlib()
+ if _looks_like_deb_system_dist_packages(old):
+ return old
+ if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
+ _log_context()
+ return old
diff --git a/env/Lib/site-packages/pip/_internal/locations/_distutils.py b/env/Lib/site-packages/pip/_internal/locations/_distutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d856256986f68b1bc38d012cfc96f8075268493
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/locations/_distutils.py
@@ -0,0 +1,172 @@
+"""Locations where we look for configs, install stuff, etc"""
+
+# The following comment should be removed at some point in the future.
+# mypy: strict-optional=False
+
+# If pip's going to use distutils, it should not be using the copy that setuptools
+# might have injected into the environment. This is done by removing the injected
+# shim, if it's injected.
+#
+# See https://github.com/pypa/pip/issues/8761 for the original discussion and
+# rationale for why this is done within pip.
+try:
+ __import__("_distutils_hack").remove_shim()
+except (ImportError, AttributeError):
+ pass
+
+import logging
+import os
+import sys
+from distutils.cmd import Command as DistutilsCommand
+from distutils.command.install import SCHEME_KEYS
+from distutils.command.install import install as distutils_install_command
+from distutils.sysconfig import get_python_lib
+from typing import Dict, List, Optional, Union
+
+from pip._internal.models.scheme import Scheme
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.virtualenv import running_under_virtualenv
+
+from .base import get_major_minor_version
+
+logger = logging.getLogger(__name__)
+
+
+def distutils_scheme(
+ dist_name: str,
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ isolated: bool = False,
+ prefix: Optional[str] = None,
+ *,
+ ignore_config_files: bool = False,
+) -> Dict[str, str]:
+ """
+ Return a distutils install scheme
+ """
+ from distutils.dist import Distribution
+
+ dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name}
+ if isolated:
+ dist_args["script_args"] = ["--no-user-cfg"]
+
+ d = Distribution(dist_args)
+ if not ignore_config_files:
+ try:
+ d.parse_config_files()
+ except UnicodeDecodeError:
+ paths = d.find_config_files()
+ logger.warning(
+ "Ignore distutils configs in %s due to encoding errors.",
+ ", ".join(os.path.basename(p) for p in paths),
+ )
+ obj: Optional[DistutilsCommand] = None
+ obj = d.get_command_obj("install", create=True)
+ assert obj is not None
+ i: distutils_install_command = obj
+ # NOTE: setting user or home has the side-effect of creating the home dir
+ # or user base for installations during finalize_options()
+ # ideally, we'd prefer a scheme class that has no side-effects.
+ assert not (user and prefix), f"user={user} prefix={prefix}"
+ assert not (home and prefix), f"home={home} prefix={prefix}"
+ i.user = user or i.user
+ if user or home:
+ i.prefix = ""
+ i.prefix = prefix or i.prefix
+ i.home = home or i.home
+ i.root = root or i.root
+ i.finalize_options()
+
+ scheme: Dict[str, str] = {}
+ for key in SCHEME_KEYS:
+ scheme[key] = getattr(i, "install_" + key)
+
+ # install_lib specified in setup.cfg should install *everything*
+ # into there (i.e. it takes precedence over both purelib and
+ # platlib). Note, i.install_lib is *always* set after
+ # finalize_options(); we only want to override here if the user
+ # has explicitly requested it hence going back to the config
+ if "install_lib" in d.get_option_dict("install"):
+ scheme.update({"purelib": i.install_lib, "platlib": i.install_lib})
+
+ if running_under_virtualenv():
+ if home:
+ prefix = home
+ elif user:
+ prefix = i.install_userbase
+ else:
+ prefix = i.prefix
+ scheme["headers"] = os.path.join(
+ prefix,
+ "include",
+ "site",
+ f"python{get_major_minor_version()}",
+ dist_name,
+ )
+
+ if root is not None:
+ path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1]
+ scheme["headers"] = os.path.join(root, path_no_drive[1:])
+
+ return scheme
+
+
+def get_scheme(
+ dist_name: str,
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ isolated: bool = False,
+ prefix: Optional[str] = None,
+) -> Scheme:
+ """
+ Get the "scheme" corresponding to the input parameters. The distutils
+ documentation provides the context for the available schemes:
+ https://docs.python.org/3/install/index.html#alternate-installation
+
+ :param dist_name: the name of the package to retrieve the scheme for, used
+ in the headers scheme path
+ :param user: indicates to use the "user" scheme
+ :param home: indicates to use the "home" scheme and provides the base
+ directory for the same
+ :param root: root under which other directories are re-based
+ :param isolated: equivalent to --no-user-cfg, i.e. do not consider
+ ~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for
+ scheme paths
+ :param prefix: indicates to use the "prefix" scheme and provides the
+ base directory for the same
+ """
+ scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix)
+ return Scheme(
+ platlib=scheme["platlib"],
+ purelib=scheme["purelib"],
+ headers=scheme["headers"],
+ scripts=scheme["scripts"],
+ data=scheme["data"],
+ )
+
+
+def get_bin_prefix() -> str:
+ # XXX: In old virtualenv versions, sys.prefix can contain '..' components,
+ # so we need to call normpath to eliminate them.
+ prefix = os.path.normpath(sys.prefix)
+ if WINDOWS:
+ bin_py = os.path.join(prefix, "Scripts")
+ # buildout uses 'bin' on Windows too?
+ if not os.path.exists(bin_py):
+ bin_py = os.path.join(prefix, "bin")
+ return bin_py
+ # Forcing to use /usr/local/bin for standard macOS framework installs
+ # Also log to ~/Library/Logs/ for use with the Console.app log viewer
+ if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/":
+ return "/usr/local/bin"
+ return os.path.join(prefix, "bin")
+
+
+def get_purelib() -> str:
+ return get_python_lib(plat_specific=False)
+
+
+def get_platlib() -> str:
+ return get_python_lib(plat_specific=True)
diff --git a/env/Lib/site-packages/pip/_internal/locations/_sysconfig.py b/env/Lib/site-packages/pip/_internal/locations/_sysconfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca860ea562c2c0c30982ba6cff654355e9f21c8a
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/locations/_sysconfig.py
@@ -0,0 +1,214 @@
+import logging
+import os
+import sys
+import sysconfig
+import typing
+
+from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
+from pip._internal.models.scheme import SCHEME_KEYS, Scheme
+from pip._internal.utils.virtualenv import running_under_virtualenv
+
+from .base import change_root, get_major_minor_version, is_osx_framework
+
+logger = logging.getLogger(__name__)
+
+
+# Notes on _infer_* functions.
+# Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no
+# way to ask things like "what is the '_prefix' scheme on this platform". These
+# functions try to answer that with some heuristics while accounting for ad-hoc
+# platforms not covered by CPython's default sysconfig implementation. If the
+# ad-hoc implementation does not fully implement sysconfig, we'll fall back to
+# a POSIX scheme.
+
+_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
+
+_PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None)
+
+
+def _should_use_osx_framework_prefix() -> bool:
+ """Check for Apple's ``osx_framework_library`` scheme.
+
+ Python distributed by Apple's Command Line Tools has this special scheme
+ that's used when:
+
+ * This is a framework build.
+ * We are installing into the system prefix.
+
+ This does not account for ``pip install --prefix`` (also means we're not
+ installing to the system prefix), which should use ``posix_prefix``, but
+ logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But
+ since ``prefix`` is not available for ``sysconfig.get_default_scheme()``,
+ which is the stdlib replacement for ``_infer_prefix()``, presumably Apple
+ wouldn't be able to magically switch between ``osx_framework_library`` and
+ ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library``
+ means its behavior is consistent whether we use the stdlib implementation
+ or our own, and we deal with this special case in ``get_scheme()`` instead.
+ """
+ return (
+ "osx_framework_library" in _AVAILABLE_SCHEMES
+ and not running_under_virtualenv()
+ and is_osx_framework()
+ )
+
+
+def _infer_prefix() -> str:
+ """Try to find a prefix scheme for the current platform.
+
+ This tries:
+
+ * A special ``osx_framework_library`` for Python distributed by Apple's
+ Command Line Tools, when not running in a virtual environment.
+ * Implementation + OS, used by PyPy on Windows (``pypy_nt``).
+ * Implementation without OS, used by PyPy on POSIX (``pypy``).
+ * OS + "prefix", used by CPython on POSIX (``posix_prefix``).
+ * Just the OS name, used by CPython on Windows (``nt``).
+
+ If none of the above works, fall back to ``posix_prefix``.
+ """
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("prefix")
+ if _should_use_osx_framework_prefix():
+ return "osx_framework_library"
+ implementation_suffixed = f"{sys.implementation.name}_{os.name}"
+ if implementation_suffixed in _AVAILABLE_SCHEMES:
+ return implementation_suffixed
+ if sys.implementation.name in _AVAILABLE_SCHEMES:
+ return sys.implementation.name
+ suffixed = f"{os.name}_prefix"
+ if suffixed in _AVAILABLE_SCHEMES:
+ return suffixed
+ if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt".
+ return os.name
+ return "posix_prefix"
+
+
+def _infer_user() -> str:
+ """Try to find a user scheme for the current platform."""
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("user")
+ if is_osx_framework() and not running_under_virtualenv():
+ suffixed = "osx_framework_user"
+ else:
+ suffixed = f"{os.name}_user"
+ if suffixed in _AVAILABLE_SCHEMES:
+ return suffixed
+ if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable.
+ raise UserInstallationInvalid()
+ return "posix_user"
+
+
+def _infer_home() -> str:
+ """Try to find a home for the current platform."""
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("home")
+ suffixed = f"{os.name}_home"
+ if suffixed in _AVAILABLE_SCHEMES:
+ return suffixed
+ return "posix_home"
+
+
+# Update these keys if the user sets a custom home.
+_HOME_KEYS = [
+ "installed_base",
+ "base",
+ "installed_platbase",
+ "platbase",
+ "prefix",
+ "exec_prefix",
+]
+if sysconfig.get_config_var("userbase") is not None:
+ _HOME_KEYS.append("userbase")
+
+
+def get_scheme(
+ dist_name: str,
+ user: bool = False,
+ home: typing.Optional[str] = None,
+ root: typing.Optional[str] = None,
+ isolated: bool = False,
+ prefix: typing.Optional[str] = None,
+) -> Scheme:
+ """
+ Get the "scheme" corresponding to the input parameters.
+
+ :param dist_name: the name of the package to retrieve the scheme for, used
+ in the headers scheme path
+ :param user: indicates to use the "user" scheme
+ :param home: indicates to use the "home" scheme
+ :param root: root under which other directories are re-based
+ :param isolated: ignored, but kept for distutils compatibility (where
+ this controls whether the user-site pydistutils.cfg is honored)
+ :param prefix: indicates to use the "prefix" scheme and provides the
+ base directory for the same
+ """
+ if user and prefix:
+ raise InvalidSchemeCombination("--user", "--prefix")
+ if home and prefix:
+ raise InvalidSchemeCombination("--home", "--prefix")
+
+ if home is not None:
+ scheme_name = _infer_home()
+ elif user:
+ scheme_name = _infer_user()
+ else:
+ scheme_name = _infer_prefix()
+
+ # Special case: When installing into a custom prefix, use posix_prefix
+ # instead of osx_framework_library. See _should_use_osx_framework_prefix()
+ # docstring for details.
+ if prefix is not None and scheme_name == "osx_framework_library":
+ scheme_name = "posix_prefix"
+
+ if home is not None:
+ variables = {k: home for k in _HOME_KEYS}
+ elif prefix is not None:
+ variables = {k: prefix for k in _HOME_KEYS}
+ else:
+ variables = {}
+
+ paths = sysconfig.get_paths(scheme=scheme_name, vars=variables)
+
+ # Logic here is very arbitrary, we're doing it for compatibility, don't ask.
+ # 1. Pip historically uses a special header path in virtual environments.
+ # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We
+ # only do the same when not running in a virtual environment because
+ # pip's historical header path logic (see point 1) did not do this.
+ if running_under_virtualenv():
+ if user:
+ base = variables.get("userbase", sys.prefix)
+ else:
+ base = variables.get("base", sys.prefix)
+ python_xy = f"python{get_major_minor_version()}"
+ paths["include"] = os.path.join(base, "include", "site", python_xy)
+ elif not dist_name:
+ dist_name = "UNKNOWN"
+
+ scheme = Scheme(
+ platlib=paths["platlib"],
+ purelib=paths["purelib"],
+ headers=os.path.join(paths["include"], dist_name),
+ scripts=paths["scripts"],
+ data=paths["data"],
+ )
+ if root is not None:
+ converted_keys = {}
+ for key in SCHEME_KEYS:
+ converted_keys[key] = change_root(root, getattr(scheme, key))
+ scheme = Scheme(**converted_keys)
+ return scheme
+
+
+def get_bin_prefix() -> str:
+ # Forcing to use /usr/local/bin for standard macOS framework installs.
+ if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
+ return "/usr/local/bin"
+ return sysconfig.get_paths()["scripts"]
+
+
+def get_purelib() -> str:
+ return sysconfig.get_paths()["purelib"]
+
+
+def get_platlib() -> str:
+ return sysconfig.get_paths()["platlib"]
diff --git a/env/Lib/site-packages/pip/_internal/locations/base.py b/env/Lib/site-packages/pip/_internal/locations/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f9f896e632e929a63e9724ab80ecdfc9761b795
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/locations/base.py
@@ -0,0 +1,81 @@
+import functools
+import os
+import site
+import sys
+import sysconfig
+import typing
+
+from pip._internal.exceptions import InstallationError
+from pip._internal.utils import appdirs
+from pip._internal.utils.virtualenv import running_under_virtualenv
+
+# Application Directories
+USER_CACHE_DIR = appdirs.user_cache_dir("pip")
+
+# FIXME doesn't account for venv linked to global site-packages
+site_packages: str = sysconfig.get_path("purelib")
+
+
+def get_major_minor_version() -> str:
+ """
+ Return the major-minor version of the current Python as a string, e.g.
+ "3.7" or "3.10".
+ """
+ return "{}.{}".format(*sys.version_info)
+
+
+def change_root(new_root: str, pathname: str) -> str:
+ """Return 'pathname' with 'new_root' prepended.
+
+ If 'pathname' is relative, this is equivalent to os.path.join(new_root, pathname).
+ Otherwise, it requires making 'pathname' relative and then joining the
+ two, which is tricky on DOS/Windows and Mac OS.
+
+ This is borrowed from Python's standard library's distutils module.
+ """
+ if os.name == "posix":
+ if not os.path.isabs(pathname):
+ return os.path.join(new_root, pathname)
+ else:
+ return os.path.join(new_root, pathname[1:])
+
+ elif os.name == "nt":
+ (drive, path) = os.path.splitdrive(pathname)
+ if path[0] == "\\":
+ path = path[1:]
+ return os.path.join(new_root, path)
+
+ else:
+ raise InstallationError(
+ f"Unknown platform: {os.name}\n"
+ "Can not change root path prefix on unknown platform."
+ )
+
+
+def get_src_prefix() -> str:
+ if running_under_virtualenv():
+ src_prefix = os.path.join(sys.prefix, "src")
+ else:
+ # FIXME: keep src in cwd for now (it is not a temporary folder)
+ try:
+ src_prefix = os.path.join(os.getcwd(), "src")
+ except OSError:
+ # In case the current working directory has been renamed or deleted
+ sys.exit("The folder you are executing pip from can no longer be found.")
+
+ # under macOS + virtualenv sys.prefix is not properly resolved
+ # it is something like /path/to/python/bin/..
+ return os.path.abspath(src_prefix)
+
+
+try:
+ # Use getusersitepackages if this is present, as it ensures that the
+ # value is initialised properly.
+ user_site: typing.Optional[str] = site.getusersitepackages()
+except AttributeError:
+ user_site = site.USER_SITE
+
+
+@functools.lru_cache(maxsize=None)
+def is_osx_framework() -> bool:
+ return bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
diff --git a/env/Lib/site-packages/pip/_internal/main.py b/env/Lib/site-packages/pip/_internal/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..33c6d24cd85b55a9fb1b1e6ab784f471e2b135f0
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/main.py
@@ -0,0 +1,12 @@
+from typing import List, Optional
+
+
+def main(args: Optional[List[str]] = None) -> int:
+ """This is preserved for old console scripts that may still be referencing
+ it.
+
+ For additional details, see https://github.com/pypa/pip/issues/7498.
+ """
+ from pip._internal.utils.entrypoints import _wrapper
+
+ return _wrapper(args)
diff --git a/env/Lib/site-packages/pip/_internal/metadata/__init__.py b/env/Lib/site-packages/pip/_internal/metadata/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa232b6cabdbfc835d76a77ea26250a8b7ef0783
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/__init__.py
@@ -0,0 +1,128 @@
+import contextlib
+import functools
+import os
+import sys
+from typing import TYPE_CHECKING, List, Optional, Type, cast
+
+from pip._internal.utils.misc import strtobool
+
+from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
+
+if TYPE_CHECKING:
+ from typing import Literal, Protocol
+else:
+ Protocol = object
+
+__all__ = [
+ "BaseDistribution",
+ "BaseEnvironment",
+ "FilesystemWheel",
+ "MemoryWheel",
+ "Wheel",
+ "get_default_environment",
+ "get_environment",
+ "get_wheel_distribution",
+ "select_backend",
+]
+
+
+def _should_use_importlib_metadata() -> bool:
+ """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
+
+ By default, pip uses ``importlib.metadata`` on Python 3.11+, and
+ ``pkg_resourcess`` otherwise. This can be overridden by a couple of ways:
+
+ * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
+ dictates whether ``importlib.metadata`` is used, regardless of Python
+ version.
+ * On Python 3.11+, Python distributors can patch ``importlib.metadata``
+ to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This
+ makes pip use ``pkg_resources`` (unless the user set the aforementioned
+ environment variable to *True*).
+ """
+ with contextlib.suppress(KeyError, ValueError):
+ return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"]))
+ if sys.version_info < (3, 11):
+ return False
+ import importlib.metadata
+
+ return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True))
+
+
+class Backend(Protocol):
+ NAME: 'Literal["importlib", "pkg_resources"]'
+ Distribution: Type[BaseDistribution]
+ Environment: Type[BaseEnvironment]
+
+
+@functools.lru_cache(maxsize=None)
+def select_backend() -> Backend:
+ if _should_use_importlib_metadata():
+ from . import importlib
+
+ return cast(Backend, importlib)
+ from . import pkg_resources
+
+ return cast(Backend, pkg_resources)
+
+
+def get_default_environment() -> BaseEnvironment:
+ """Get the default representation for the current environment.
+
+ This returns an Environment instance from the chosen backend. The default
+ Environment instance should be built from ``sys.path`` and may use caching
+ to share instance state accorss calls.
+ """
+ return select_backend().Environment.default()
+
+
+def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
+ """Get a representation of the environment specified by ``paths``.
+
+ This returns an Environment instance from the chosen backend based on the
+ given import paths. The backend must build a fresh instance representing
+ the state of installed distributions when this function is called.
+ """
+ return select_backend().Environment.from_paths(paths)
+
+
+def get_directory_distribution(directory: str) -> BaseDistribution:
+ """Get the distribution metadata representation in the specified directory.
+
+ This returns a Distribution instance from the chosen backend based on
+ the given on-disk ``.dist-info`` directory.
+ """
+ return select_backend().Distribution.from_directory(directory)
+
+
+def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
+ """Get the representation of the specified wheel's distribution metadata.
+
+ This returns a Distribution instance from the chosen backend based on
+ the given wheel's ``.dist-info`` directory.
+
+ :param canonical_name: Normalized project name of the given wheel.
+ """
+ return select_backend().Distribution.from_wheel(wheel, canonical_name)
+
+
+def get_metadata_distribution(
+ metadata_contents: bytes,
+ filename: str,
+ canonical_name: str,
+) -> BaseDistribution:
+ """Get the dist representation of the specified METADATA file contents.
+
+ This returns a Distribution instance from the chosen backend sourced from the data
+ in `metadata_contents`.
+
+ :param metadata_contents: Contents of a METADATA file within a dist, or one served
+ via PEP 658.
+ :param filename: Filename for the dist this metadata represents.
+ :param canonical_name: Normalized project name of the given dist.
+ """
+ return select_backend().Distribution.from_metadata_file_contents(
+ metadata_contents,
+ filename,
+ canonical_name,
+ )
diff --git a/env/Lib/site-packages/pip/_internal/metadata/_json.py b/env/Lib/site-packages/pip/_internal/metadata/_json.py
new file mode 100644
index 0000000000000000000000000000000000000000..9097dd58590549f2b5389f1303cd35961526815b
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/_json.py
@@ -0,0 +1,84 @@
+# Extracted from https://github.com/pfmoore/pkg_metadata
+
+from email.header import Header, decode_header, make_header
+from email.message import Message
+from typing import Any, Dict, List, Union, cast
+
+METADATA_FIELDS = [
+ # Name, Multiple-Use
+ ("Metadata-Version", False),
+ ("Name", False),
+ ("Version", False),
+ ("Dynamic", True),
+ ("Platform", True),
+ ("Supported-Platform", True),
+ ("Summary", False),
+ ("Description", False),
+ ("Description-Content-Type", False),
+ ("Keywords", False),
+ ("Home-page", False),
+ ("Download-URL", False),
+ ("Author", False),
+ ("Author-email", False),
+ ("Maintainer", False),
+ ("Maintainer-email", False),
+ ("License", False),
+ ("Classifier", True),
+ ("Requires-Dist", True),
+ ("Requires-Python", False),
+ ("Requires-External", True),
+ ("Project-URL", True),
+ ("Provides-Extra", True),
+ ("Provides-Dist", True),
+ ("Obsoletes-Dist", True),
+]
+
+
+def json_name(field: str) -> str:
+ return field.lower().replace("-", "_")
+
+
+def msg_to_json(msg: Message) -> Dict[str, Any]:
+ """Convert a Message object into a JSON-compatible dictionary."""
+
+ def sanitise_header(h: Union[Header, str]) -> str:
+ if isinstance(h, Header):
+ chunks = []
+ for bytes, encoding in decode_header(h):
+ if encoding == "unknown-8bit":
+ try:
+ # See if UTF-8 works
+ bytes.decode("utf-8")
+ encoding = "utf-8"
+ except UnicodeDecodeError:
+ # If not, latin1 at least won't fail
+ encoding = "latin1"
+ chunks.append((bytes, encoding))
+ return str(make_header(chunks))
+ return str(h)
+
+ result = {}
+ for field, multi in METADATA_FIELDS:
+ if field not in msg:
+ continue
+ key = json_name(field)
+ if multi:
+ value: Union[str, List[str]] = [
+ sanitise_header(v) for v in msg.get_all(field) # type: ignore
+ ]
+ else:
+ value = sanitise_header(msg.get(field)) # type: ignore
+ if key == "keywords":
+ # Accept both comma-separated and space-separated
+ # forms, for better compatibility with old data.
+ if "," in value:
+ value = [v.strip() for v in value.split(",")]
+ else:
+ value = value.split()
+ result[key] = value
+
+ payload = cast(str, msg.get_payload())
+ if payload:
+ result["description"] = payload
+
+ return result
diff --git a/env/Lib/site-packages/pip/_internal/metadata/base.py b/env/Lib/site-packages/pip/_internal/metadata/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..9eabcdb278bd53959f489a8a0fb5ee13a8512f5b
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/base.py
@@ -0,0 +1,688 @@
+import csv
+import email.message
+import functools
+import json
+import logging
+import pathlib
+import re
+import zipfile
+from typing import (
+ IO,
+ Any,
+ Collection,
+ Container,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ NamedTuple,
+ Optional,
+ Protocol,
+ Tuple,
+ Union,
+)
+
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+from pip._vendor.packaging.version import Version
+
+from pip._internal.exceptions import NoneMetadataError
+from pip._internal.locations import site_packages, user_site
+from pip._internal.models.direct_url import (
+ DIRECT_URL_METADATA_NAME,
+ DirectUrl,
+ DirectUrlValidationError,
+)
+from pip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here.
+from pip._internal.utils.egg_link import egg_link_path_from_sys_path
+from pip._internal.utils.misc import is_local, normalize_path
+from pip._internal.utils.urls import url_to_path
+
+from ._json import msg_to_json
+
+InfoPath = Union[str, pathlib.PurePath]
+
+logger = logging.getLogger(__name__)
+
+
+class BaseEntryPoint(Protocol):
+ @property
+ def name(self) -> str:
+ raise NotImplementedError()
+
+ @property
+ def value(self) -> str:
+ raise NotImplementedError()
+
+ @property
+ def group(self) -> str:
+ raise NotImplementedError()
+
+
+def _convert_installed_files_path(
+ entry: Tuple[str, ...],
+ info: Tuple[str, ...],
+) -> str:
+ """Convert a legacy installed-files.txt path into modern RECORD path.
+
+ The legacy format stores paths relative to the info directory, while the
+ modern format stores paths relative to the package root, e.g. the
+ site-packages directory.
+
+ :param entry: Path parts of the installed-files.txt entry.
+ :param info: Path parts of the egg-info directory relative to package root.
+ :returns: The converted entry.
+
+ For best compatibility with symlinks, this does not use ``abspath()`` or
+ ``Path.resolve()``, but tries to work with path parts:
+
+ 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
+ from ``info``; if ``info`` is empty, start appending ``..`` instead.
+ 2. Join the two directly.
+ """
+ while entry and entry[0] == "..":
+ if not info or info[-1] == "..":
+ info += ("..",)
+ else:
+ info = info[:-1]
+ entry = entry[1:]
+ return str(pathlib.Path(*info, *entry))
+
+
+class RequiresEntry(NamedTuple):
+ requirement: str
+ extra: str
+ marker: str
+
+
+class BaseDistribution(Protocol):
+ @classmethod
+ def from_directory(cls, directory: str) -> "BaseDistribution":
+ """Load the distribution from a metadata directory.
+
+ :param directory: Path to a metadata directory, e.g. ``.dist-info``.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def from_metadata_file_contents(
+ cls,
+ metadata_contents: bytes,
+ filename: str,
+ project_name: str,
+ ) -> "BaseDistribution":
+ """Load the distribution from the contents of a METADATA file.
+
+ This is used to implement PEP 658 by generating a "shallow" dist object that can
+ be used for resolution without downloading or building the actual dist yet.
+
+ :param metadata_contents: The contents of a METADATA file.
+ :param filename: File name for the dist with this metadata.
+ :param project_name: Name of the project this dist represents.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution":
+ """Load the distribution from a given wheel.
+
+ :param wheel: A concrete wheel definition.
+ :param name: File name of the wheel.
+
+ :raises InvalidWheel: Whenever loading of the wheel causes a
+ :py:exc:`zipfile.BadZipFile` exception to be thrown.
+ :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
+ internally.
+ """
+ raise NotImplementedError()
+
+ def __repr__(self) -> str:
+ return f"{self.raw_name} {self.raw_version} ({self.location})"
+
+ def __str__(self) -> str:
+ return f"{self.raw_name} {self.raw_version}"
+
+ @property
+ def location(self) -> Optional[str]:
+ """Where the distribution is loaded from.
+
+ A string value is not necessarily a filesystem path, since distributions
+ can be loaded from other sources, e.g. arbitrary zip archives. ``None``
+ means the distribution is created in-memory.
+
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
+ this is a symbolic link, we want to preserve the relative path between
+ it and files in the distribution.
+ """
+ raise NotImplementedError()
+
+ @property
+ def editable_project_location(self) -> Optional[str]:
+ """The project location for editable distributions.
+
+ This is the directory where pyproject.toml or setup.py is located.
+ None if the distribution is not installed in editable mode.
+ """
+ # TODO: this property is relatively costly to compute, memoize it ?
+ direct_url = self.direct_url
+ if direct_url:
+ if direct_url.is_local_editable():
+ return url_to_path(direct_url.url)
+ else:
+ # Search for an .egg-link file by walking sys.path, as it was
+ # done before by dist_is_editable().
+ egg_link_path = egg_link_path_from_sys_path(self.raw_name)
+ if egg_link_path:
+ # TODO: get project location from second line of egg_link file
+ # (https://github.com/pypa/pip/issues/10243)
+ return self.location
+ return None
+
+ @property
+ def installed_location(self) -> Optional[str]:
+ """The distribution's "installed" location.
+
+ This should generally be a ``site-packages`` directory. This is
+ usually ``dist.location``, except for legacy develop-installed packages,
+ where ``dist.location`` is the source code location, and this is where
+ the ``.egg-link`` file is.
+
+ The returned location is normalized (in particular, with symlinks removed).
+ """
+ raise NotImplementedError()
+
+ @property
+ def info_location(self) -> Optional[str]:
+ """Location of the .[egg|dist]-info directory or file.
+
+ Similarly to ``location``, a string value is not necessarily a
+ filesystem path. ``None`` means the distribution is created in-memory.
+
+ For a modern .dist-info installation on disk, this should be something
+ like ``{location}/{raw_name}-{version}.dist-info``.
+
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
+ this is a symbolic link, we want to preserve the relative path between
+ it and other files in the distribution.
+ """
+ raise NotImplementedError()
+
+ @property
+ def installed_by_distutils(self) -> bool:
+ """Whether this distribution is installed with legacy distutils format.
+
+ A distribution installed with "raw" distutils not patched by setuptools
+ uses one single file at ``info_location`` to store metadata. We need to
+ treat this specially on uninstallation.
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ return pathlib.Path(info_location).is_file()
+
+ @property
+ def installed_as_egg(self) -> bool:
+ """Whether this distribution is installed as an egg.
+
+ This usually indicates the distribution was installed by (older versions
+ of) easy_install.
+ """
+ location = self.location
+ if not location:
+ return False
+ return location.endswith(".egg")
+
+ @property
+ def installed_with_setuptools_egg_info(self) -> bool:
+ """Whether this distribution is installed with the ``.egg-info`` format.
+
+ This usually indicates the distribution was installed with setuptools
+ with an old pip version or with ``single-version-externally-managed``.
+
+ Note that this ensure the metadata store is a directory. distutils can
+ also installs an ``.egg-info``, but as a file, not a directory. This
+ property is *False* for that case. Also see ``installed_by_distutils``.
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ if not info_location.endswith(".egg-info"):
+ return False
+ return pathlib.Path(info_location).is_dir()
+
+ @property
+ def installed_with_dist_info(self) -> bool:
+ """Whether this distribution is installed with the "modern format".
+
+ This indicates a "modern" installation, e.g. storing metadata in the
+ ``.dist-info`` directory. This applies to installations made by
+ setuptools (but through pip, not directly), or anything using the
+ standardized build backend interface (PEP 517).
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ if not info_location.endswith(".dist-info"):
+ return False
+ return pathlib.Path(info_location).is_dir()
+
+ @property
+ def canonical_name(self) -> NormalizedName:
+ raise NotImplementedError()
+
+ @property
+ def version(self) -> Version:
+ raise NotImplementedError()
+
+ @property
+ def raw_version(self) -> str:
+ raise NotImplementedError()
+
+ @property
+ def setuptools_filename(self) -> str:
+ """Convert a project name to its setuptools-compatible filename.
+
+ This is a copy of ``pkg_resources.to_filename()`` for compatibility.
+ """
+ return self.raw_name.replace("-", "_")
+
+ @property
+ def direct_url(self) -> Optional[DirectUrl]:
+ """Obtain a DirectUrl from this distribution.
+
+ Returns None if the distribution has no `direct_url.json` metadata,
+ or if `direct_url.json` is invalid.
+ """
+ try:
+ content = self.read_text(DIRECT_URL_METADATA_NAME)
+ except FileNotFoundError:
+ return None
+ try:
+ return DirectUrl.from_json(content)
+ except (
+ UnicodeDecodeError,
+ json.JSONDecodeError,
+ DirectUrlValidationError,
+ ) as e:
+ logger.warning(
+ "Error parsing %s for %s: %s",
+ DIRECT_URL_METADATA_NAME,
+ self.canonical_name,
+ e,
+ )
+ return None
+
+ @property
+ def installer(self) -> str:
+ try:
+ installer_text = self.read_text("INSTALLER")
+ except (OSError, ValueError, NoneMetadataError):
+ return "" # Fail silently if the installer file cannot be read.
+ for line in installer_text.splitlines():
+ cleaned_line = line.strip()
+ if cleaned_line:
+ return cleaned_line
+ return ""
+
+ @property
+ def requested(self) -> bool:
+ return self.is_file("REQUESTED")
+
+ @property
+ def editable(self) -> bool:
+ return bool(self.editable_project_location)
+
+ @property
+ def local(self) -> bool:
+ """If distribution is installed in the current virtual environment.
+
+ Always True if we're not in a virtualenv.
+ """
+ if self.installed_location is None:
+ return False
+ return is_local(self.installed_location)
+
+ @property
+ def in_usersite(self) -> bool:
+ if self.installed_location is None or user_site is None:
+ return False
+ return self.installed_location.startswith(normalize_path(user_site))
+
+ @property
+ def in_site_packages(self) -> bool:
+ if self.installed_location is None or site_packages is None:
+ return False
+ return self.installed_location.startswith(normalize_path(site_packages))
+
+ def is_file(self, path: InfoPath) -> bool:
+ """Check whether an entry in the info directory is a file."""
+ raise NotImplementedError()
+
+ def iter_distutils_script_names(self) -> Iterator[str]:
+ """Find distutils 'scripts' entries metadata.
+
+ If 'scripts' is supplied in ``setup.py``, distutils records those in the
+ installed distribution's ``scripts`` directory, a file for each script.
+ """
+ raise NotImplementedError()
+
+ def read_text(self, path: InfoPath) -> str:
+ """Read a file in the info directory.
+
+ :raise FileNotFoundError: If ``path`` does not exist in the directory.
+ :raise NoneMetadataError: If ``path`` exists in the info directory, but
+ cannot be read.
+ """
+ raise NotImplementedError()
+
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
+ raise NotImplementedError()
+
+ def _metadata_impl(self) -> email.message.Message:
+ raise NotImplementedError()
+
+ @functools.cached_property
+ def metadata(self) -> email.message.Message:
+ """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.
+
+ This should return an empty message if the metadata file is unavailable.
+
+ :raises NoneMetadataError: If the metadata file is available, but does
+ not contain valid metadata.
+ """
+ metadata = self._metadata_impl()
+ self._add_egg_info_requires(metadata)
+ return metadata
+
+ @property
+ def metadata_dict(self) -> Dict[str, Any]:
+ """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO.
+
+ This should return an empty dict if the metadata file is unavailable.
+
+ :raises NoneMetadataError: If the metadata file is available, but does
+ not contain valid metadata.
+ """
+ return msg_to_json(self.metadata)
+
+ @property
+ def metadata_version(self) -> Optional[str]:
+ """Value of "Metadata-Version:" in distribution metadata, if available."""
+ return self.metadata.get("Metadata-Version")
+
+ @property
+ def raw_name(self) -> str:
+ """Value of "Name:" in distribution metadata."""
+ # The metadata should NEVER be missing the Name: key, but if it somehow
+ # does, fall back to the known canonical name.
+ return self.metadata.get("Name", self.canonical_name)
+
+ @property
+ def requires_python(self) -> SpecifierSet:
+ """Value of "Requires-Python:" in distribution metadata.
+
+ If the key does not exist or contains an invalid value, an empty
+ SpecifierSet should be returned.
+ """
+ value = self.metadata.get("Requires-Python")
+ if value is None:
+ return SpecifierSet()
+ try:
+ # Convert to str to satisfy the type checker; this can be a Header object.
+ spec = SpecifierSet(str(value))
+ except InvalidSpecifier as e:
+ message = "Package %r has an invalid Requires-Python: %s"
+ logger.warning(message, self.raw_name, e)
+ return SpecifierSet()
+ return spec
+
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
+ """Dependencies of this distribution.
+
+ For modern .dist-info distributions, this is the collection of
+ "Requires-Dist:" entries in distribution metadata.
+ """
+ raise NotImplementedError()
+
+ def iter_raw_dependencies(self) -> Iterable[str]:
+ """Raw Requires-Dist metadata."""
+ return self.metadata.get_all("Requires-Dist", [])
+
+ def iter_provided_extras(self) -> Iterable[NormalizedName]:
+ """Extras provided by this distribution.
+
+ For modern .dist-info distributions, this is the collection of
+ "Provides-Extra:" entries in distribution metadata.
+
+ The return value of this function is expected to be normalised names,
+ per PEP 685, with the returned value being handled appropriately by
+ `iter_dependencies`.
+ """
+ raise NotImplementedError()
+
+ def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
+ try:
+ text = self.read_text("RECORD")
+ except FileNotFoundError:
+ return None
+ # This extra Path-str cast normalizes entries.
+ return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
+
+ def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
+ try:
+ text = self.read_text("installed-files.txt")
+ except FileNotFoundError:
+ return None
+ paths = (p for p in text.splitlines(keepends=False) if p)
+ root = self.location
+ info = self.info_location
+ if root is None or info is None:
+ return paths
+ try:
+ info_rel = pathlib.Path(info).relative_to(root)
+ except ValueError: # info is not relative to root.
+ return paths
+ if not info_rel.parts: # info *is* root.
+ return paths
+ return (
+ _convert_installed_files_path(pathlib.Path(p).parts, info_rel.parts)
+ for p in paths
+ )
+
+ def iter_declared_entries(self) -> Optional[Iterator[str]]:
+ """Iterate through file entries declared in this distribution.
+
+ For modern .dist-info distributions, this is the files listed in the
+ ``RECORD`` metadata file. For legacy setuptools distributions, this
+ comes from ``installed-files.txt``, with entries normalized to be
+ compatible with the format used by ``RECORD``.
+
+ :return: An iterator for listed entries, or None if the distribution
+ contains neither ``RECORD`` nor ``installed-files.txt``.
+ """
+ return (
+ self._iter_declared_entries_from_record()
+ or self._iter_declared_entries_from_legacy()
+ )
+
+ def _iter_requires_txt_entries(self) -> Iterator[RequiresEntry]:
+ """Parse a ``requires.txt`` in an egg-info directory.
+
+ This is an INI-ish format where an egg-info stores dependencies. A
+ section name describes extra other environment markers, while each entry
+ is an arbitrary string (not a key-value pair) representing a dependency
+ as a requirement string (no markers).
+
+ There is a construct in ``importlib.metadata`` called ``Sectioned`` that
+ does mostly the same, but the format is currently considered private.
+ """
+ try:
+ content = self.read_text("requires.txt")
+ except FileNotFoundError:
+ return
+ extra = marker = "" # Section-less entries don't have markers.
+ for line in content.splitlines():
+ line = line.strip()
+ if not line or line.startswith("#"): # Comment; ignored.
+ continue
+ if line.startswith("[") and line.endswith("]"): # A section header.
+ extra, _, marker = line.strip("[]").partition(":")
+ continue
+ yield RequiresEntry(requirement=line, extra=extra, marker=marker)
+
+ def _iter_egg_info_extras(self) -> Iterable[str]:
+ """Get extras from the egg-info directory."""
+ known_extras = {""}
+ for entry in self._iter_requires_txt_entries():
+ extra = canonicalize_name(entry.extra)
+ if extra in known_extras:
+ continue
+ known_extras.add(extra)
+ yield extra
+
+ def _iter_egg_info_dependencies(self) -> Iterable[str]:
+ """Get distribution dependencies from the egg-info directory.
+
+ To ease parsing, this converts a legacy dependency entry into a PEP 508
+ requirement string. Like ``_iter_requires_txt_entries()``, there is code
+ in ``importlib.metadata`` that does mostly the same, but not do exactly
+ what we need.
+
+ Namely, ``importlib.metadata`` does not normalize the extra name before
+ putting it into the requirement string, which causes marker comparison
+ to fail because the dist-info format do normalize. This is consistent in
+ all currently available PEP 517 backends, although not standardized.
+ """
+ for entry in self._iter_requires_txt_entries():
+ extra = canonicalize_name(entry.extra)
+ if extra and entry.marker:
+ marker = f'({entry.marker}) and extra == "{extra}"'
+ elif extra:
+ marker = f'extra == "{extra}"'
+ elif entry.marker:
+ marker = entry.marker
+ else:
+ marker = ""
+ if marker:
+ yield f"{entry.requirement} ; {marker}"
+ else:
+ yield entry.requirement
+
+ def _add_egg_info_requires(self, metadata: email.message.Message) -> None:
+ """Add egg-info requires.txt information to the metadata."""
+ if not metadata.get_all("Requires-Dist"):
+ for dep in self._iter_egg_info_dependencies():
+ metadata["Requires-Dist"] = dep
+ if not metadata.get_all("Provides-Extra"):
+ for extra in self._iter_egg_info_extras():
+ metadata["Provides-Extra"] = extra
+
+
+class BaseEnvironment:
+ """An environment containing distributions to introspect."""
+
+ @classmethod
+ def default(cls) -> "BaseEnvironment":
+ raise NotImplementedError()
+
+ @classmethod
+ def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment":
+ raise NotImplementedError()
+
+ def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
+ """Given a requirement name, return the installed distributions.
+
+ The name may not be normalized. The implementation must canonicalize
+ it for lookup.
+ """
+ raise NotImplementedError()
+
+ def _iter_distributions(self) -> Iterator["BaseDistribution"]:
+ """Iterate through installed distributions.
+
+ This function should be implemented by subclass, but never called
+ directly. Use the public ``iter_distribution()`` instead, which
+ implements additional logic to make sure the distributions are valid.
+ """
+ raise NotImplementedError()
+
+ def iter_all_distributions(self) -> Iterator[BaseDistribution]:
+ """Iterate through all installed distributions without any filtering."""
+ for dist in self._iter_distributions():
+ # Make sure the distribution actually comes from a valid Python
+ # packaging distribution. Pip's AdjacentTempDirectory leaves folders
+ # e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The
+ # valid project name pattern is taken from PEP 508.
+ project_name_valid = re.match(
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
+ dist.canonical_name,
+ flags=re.IGNORECASE,
+ )
+ if not project_name_valid:
+ logger.warning(
+ "Ignoring invalid distribution %s (%s)",
+ dist.canonical_name,
+ dist.location,
+ )
+ continue
+ yield dist
+
+ def iter_installed_distributions(
+ self,
+ local_only: bool = True,
+ skip: Container[str] = stdlib_pkgs,
+ include_editables: bool = True,
+ editables_only: bool = False,
+ user_only: bool = False,
+ ) -> Iterator[BaseDistribution]:
+ """Return a list of installed distributions.
+
+ This is based on ``iter_all_distributions()`` with additional filtering
+ options. Note that ``iter_installed_distributions()`` without arguments
+ is *not* equal to ``iter_all_distributions()``, since some of the
+ configurations exclude packages by default.
+
+ :param local_only: If True (default), only return installations
+ local to the current virtualenv, if in a virtualenv.
+ :param skip: An iterable of canonicalized project names to ignore;
+ defaults to ``stdlib_pkgs``.
+ :param include_editables: If False, don't report editables.
+ :param editables_only: If True, only report editables.
+ :param user_only: If True, only report installations in the user
+ site directory.
+ """
+ it = self.iter_all_distributions()
+ if local_only:
+ it = (d for d in it if d.local)
+ if not include_editables:
+ it = (d for d in it if not d.editable)
+ if editables_only:
+ it = (d for d in it if d.editable)
+ if user_only:
+ it = (d for d in it if d.in_usersite)
+ return (d for d in it if d.canonical_name not in skip)
+
+
+class Wheel(Protocol):
+ location: str
+
+ def as_zipfile(self) -> zipfile.ZipFile:
+ raise NotImplementedError()
+
+
+class FilesystemWheel(Wheel):
+ def __init__(self, location: str) -> None:
+ self.location = location
+
+ def as_zipfile(self) -> zipfile.ZipFile:
+ return zipfile.ZipFile(self.location, allowZip64=True)
+
+
+class MemoryWheel(Wheel):
+ def __init__(self, location: str, stream: IO[bytes]) -> None:
+ self.location = location
+ self.stream = stream
+
+ def as_zipfile(self) -> zipfile.ZipFile:
+ return zipfile.ZipFile(self.stream, allowZip64=True)
diff --git a/env/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py b/env/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a779138db1040d3903c2bb66ecb2f52a46879dae
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py
@@ -0,0 +1,6 @@
+from ._dists import Distribution
+from ._envs import Environment
+
+__all__ = ["NAME", "Distribution", "Environment"]
+
+NAME = "importlib"
diff --git a/env/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py b/env/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec1e815cdbdf517ec8b3565d7dafb7e67ce1705c
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py
@@ -0,0 +1,85 @@
+import importlib.metadata
+import os
+from typing import Any, Optional, Protocol, Tuple, cast
+
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+
+
+class BadMetadata(ValueError):
+ def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None:
+ self.dist = dist
+ self.reason = reason
+
+ def __str__(self) -> str:
+ return f"Bad metadata in {self.dist} ({self.reason})"
+
+
+class BasePath(Protocol):
+ """A protocol that various path objects conform.
+
+ This exists because importlib.metadata uses both ``pathlib.Path`` and
+ ``zipfile.Path``, and we need a common base for type hints (Union does not
+ work well since ``zipfile.Path`` is too new for our linter setup).
+
+ This does not mean to be exhaustive, but only contains things that present
+ in both classes *that we need*.
+ """
+
+ @property
+ def name(self) -> str:
+ raise NotImplementedError()
+
+ @property
+ def parent(self) -> "BasePath":
+ raise NotImplementedError()
+
+
+def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]:
+ """Find the path to the distribution's metadata directory.
+
+ HACK: This relies on importlib.metadata's private ``_path`` attribute. Not
+ all distributions exist on disk, so importlib.metadata is correct to not
+ expose the attribute as public. But pip's code base is old and not as clean,
+ so we do this to avoid having to rewrite too many things. Hopefully we can
+ eliminate this some day.
+ """
+ return getattr(d, "_path", None)
+
+
+def parse_name_and_version_from_info_directory(
+ dist: importlib.metadata.Distribution,
+) -> Tuple[Optional[str], Optional[str]]:
+ """Get a name and version from the metadata directory name.
+
+ This is much faster than reading distribution metadata.
+ """
+ info_location = get_info_location(dist)
+ if info_location is None:
+ return None, None
+
+ stem, suffix = os.path.splitext(info_location.name)
+ if suffix == ".dist-info":
+ name, sep, version = stem.partition("-")
+ if sep:
+ return name, version
+
+ if suffix == ".egg-info":
+ name = stem.split("-", 1)[0]
+ return name, None
+
+ return None, None
+
+
+def get_dist_canonical_name(dist: importlib.metadata.Distribution) -> NormalizedName:
+ """Get the distribution's normalized name.
+
+ The ``name`` attribute is only available in Python 3.10 or later. We are
+ targeting exactly that, but Mypy does not know this.
+ """
+ if name := parse_name_and_version_from_info_directory(dist)[0]:
+ return canonicalize_name(name)
+
+ name = cast(Any, dist).name
+ if not isinstance(name, str):
+ raise BadMetadata(dist, reason="invalid metadata entry 'name'")
+ return canonicalize_name(name)
diff --git a/env/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py b/env/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py
new file mode 100644
index 0000000000000000000000000000000000000000..36cd326232e932f0dc8b38a733fd3ef38658cf94
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py
@@ -0,0 +1,221 @@
+import email.message
+import importlib.metadata
+import pathlib
+import zipfile
+from typing import (
+ Collection,
+ Dict,
+ Iterable,
+ Iterator,
+ Mapping,
+ Optional,
+ Sequence,
+ cast,
+)
+
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+from pip._vendor.packaging.version import Version
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.exceptions import InvalidWheel, UnsupportedWheel
+from pip._internal.metadata.base import (
+ BaseDistribution,
+ BaseEntryPoint,
+ InfoPath,
+ Wheel,
+)
+from pip._internal.utils.misc import normalize_path
+from pip._internal.utils.packaging import get_requirement
+from pip._internal.utils.temp_dir import TempDirectory
+from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
+
+from ._compat import (
+ BasePath,
+ get_dist_canonical_name,
+ parse_name_and_version_from_info_directory,
+)
+
+
+class WheelDistribution(importlib.metadata.Distribution):
+ """An ``importlib.metadata.Distribution`` read from a wheel.
+
+ Although ``importlib.metadata.PathDistribution`` accepts ``zipfile.Path``,
+ its implementation is too "lazy" for pip's needs (we can't keep the ZipFile
+ handle open for the entire lifetime of the distribution object).
+
+ This implementation eagerly reads the entire metadata directory into the
+ memory instead, and operates from that.
+ """
+
+ def __init__(
+ self,
+ files: Mapping[pathlib.PurePosixPath, bytes],
+ info_location: pathlib.PurePosixPath,
+ ) -> None:
+ self._files = files
+ self.info_location = info_location
+
+ @classmethod
+ def from_zipfile(
+ cls,
+ zf: zipfile.ZipFile,
+ name: str,
+ location: str,
+ ) -> "WheelDistribution":
+ info_dir, _ = parse_wheel(zf, name)
+ paths = (
+ (name, pathlib.PurePosixPath(name.split("/", 1)[-1]))
+ for name in zf.namelist()
+ if name.startswith(f"{info_dir}/")
+ )
+ files = {
+ relpath: read_wheel_metadata_file(zf, fullpath)
+ for fullpath, relpath in paths
+ }
+ info_location = pathlib.PurePosixPath(location, info_dir)
+ return cls(files, info_location)
+
+ def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
+ # Only allow iterating through the metadata directory.
+ if pathlib.PurePosixPath(str(path)) in self._files:
+ return iter(self._files)
+ raise FileNotFoundError(path)
+
+ def read_text(self, filename: str) -> Optional[str]:
+ try:
+ data = self._files[pathlib.PurePosixPath(filename)]
+ except KeyError:
+ return None
+ try:
+ text = data.decode("utf-8")
+ except UnicodeDecodeError as e:
+ wheel = self.info_location.parent
+ error = f"Error decoding metadata for {wheel}: {e} in {filename} file"
+ raise UnsupportedWheel(error)
+ return text
+
+
+class Distribution(BaseDistribution):
+ def __init__(
+ self,
+ dist: importlib.metadata.Distribution,
+ info_location: Optional[BasePath],
+ installed_location: Optional[BasePath],
+ ) -> None:
+ self._dist = dist
+ self._info_location = info_location
+ self._installed_location = installed_location
+
+ @classmethod
+ def from_directory(cls, directory: str) -> BaseDistribution:
+ info_location = pathlib.Path(directory)
+ dist = importlib.metadata.Distribution.at(info_location)
+ return cls(dist, info_location, info_location.parent)
+
+ @classmethod
+ def from_metadata_file_contents(
+ cls,
+ metadata_contents: bytes,
+ filename: str,
+ project_name: str,
+ ) -> BaseDistribution:
+ # Generate temp dir to contain the metadata file, and write the file contents.
+ temp_dir = pathlib.Path(
+ TempDirectory(kind="metadata", globally_managed=True).path
+ )
+ metadata_path = temp_dir / "METADATA"
+ metadata_path.write_bytes(metadata_contents)
+ # Construct dist pointing to the newly created directory.
+ dist = importlib.metadata.Distribution.at(metadata_path.parent)
+ return cls(dist, metadata_path.parent, None)
+
+ @classmethod
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
+ try:
+ with wheel.as_zipfile() as zf:
+ dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
+ except zipfile.BadZipFile as e:
+ raise InvalidWheel(wheel.location, name) from e
+ return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))
+
+ @property
+ def location(self) -> Optional[str]:
+ if self._info_location is None:
+ return None
+ return str(self._info_location.parent)
+
+ @property
+ def info_location(self) -> Optional[str]:
+ if self._info_location is None:
+ return None
+ return str(self._info_location)
+
+ @property
+ def installed_location(self) -> Optional[str]:
+ if self._installed_location is None:
+ return None
+ return normalize_path(str(self._installed_location))
+
+ @property
+ def canonical_name(self) -> NormalizedName:
+ return get_dist_canonical_name(self._dist)
+
+ @property
+ def version(self) -> Version:
+ if version := parse_name_and_version_from_info_directory(self._dist)[1]:
+ return parse_version(version)
+ return parse_version(self._dist.version)
+
+ @property
+ def raw_version(self) -> str:
+ return self._dist.version
+
+ def is_file(self, path: InfoPath) -> bool:
+ return self._dist.read_text(str(path)) is not None
+
+ def iter_distutils_script_names(self) -> Iterator[str]:
+ # A distutils installation is always "flat" (not in e.g. egg form), so
+ # if this distribution's info location is NOT a pathlib.Path (but e.g.
+ # zipfile.Path), it can never contain any distutils scripts.
+ if not isinstance(self._info_location, pathlib.Path):
+ return
+ for child in self._info_location.joinpath("scripts").iterdir():
+ yield child.name
+
+ def read_text(self, path: InfoPath) -> str:
+ content = self._dist.read_text(str(path))
+ if content is None:
+ raise FileNotFoundError(path)
+ return content
+
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
+ # importlib.metadata's EntryPoint structure sasitfies BaseEntryPoint.
+ return self._dist.entry_points
+
+ def _metadata_impl(self) -> email.message.Message:
+ # From Python 3.10+, importlib.metadata declares PackageMetadata as the
+ # return type. This protocol is unfortunately a disaster now and misses
+ # a ton of fields that we need, including get() and get_payload(). We
+ # rely on the implementation that the object is actually a Message now,
+ # until upstream can improve the protocol. (python/cpython#94952)
+ return cast(email.message.Message, self._dist.metadata)
+
+ def iter_provided_extras(self) -> Iterable[NormalizedName]:
+ return [
+ canonicalize_name(extra)
+ for extra in self.metadata.get_all("Provides-Extra", [])
+ ]
+
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
+ contexts: Sequence[Dict[str, str]] = [{"extra": e} for e in extras]
+ for req_string in self.metadata.get_all("Requires-Dist", []):
+ # strip() because email.message.Message.get_all() may return a leading \n
+ # in case a long header was wrapped.
+ req = get_requirement(req_string.strip())
+ if not req.marker:
+ yield req
+ elif not extras and req.marker.evaluate({"extra": ""}):
+ yield req
+ elif any(req.marker.evaluate(context) for context in contexts):
+ yield req
diff --git a/env/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py b/env/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d906fd3149f1242db2efa6ed095d5857d61116c
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py
@@ -0,0 +1,189 @@
+import functools
+import importlib.metadata
+import logging
+import os
+import pathlib
+import sys
+import zipfile
+import zipimport
+from typing import Iterator, List, Optional, Sequence, Set, Tuple
+
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+
+from pip._internal.metadata.base import BaseDistribution, BaseEnvironment
+from pip._internal.models.wheel import Wheel
+from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.filetypes import WHEEL_EXTENSION
+
+from ._compat import BadMetadata, BasePath, get_dist_canonical_name, get_info_location
+from ._dists import Distribution
+
+logger = logging.getLogger(__name__)
+
+
+def _looks_like_wheel(location: str) -> bool:
+ if not location.endswith(WHEEL_EXTENSION):
+ return False
+ if not os.path.isfile(location):
+ return False
+ if not Wheel.wheel_file_re.match(os.path.basename(location)):
+ return False
+ return zipfile.is_zipfile(location)
+
+
+class _DistributionFinder:
+ """Finder to locate distributions.
+
+ The main purpose of this class is to memoize found distributions' names, so
+ only one distribution is returned for each package name. At lot of pip code
+ assumes this (because it is setuptools's behavior), and not doing the same
+ can potentially cause a distribution in lower precedence path to override a
+ higher precedence one if the caller is not careful.
+
+ Eventually we probably want to make it possible to see lower precedence
+ installations as well. It's useful feature, after all.
+ """
+
+ FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]]
+
+ def __init__(self) -> None:
+ self._found_names: Set[NormalizedName] = set()
+
+ def _find_impl(self, location: str) -> Iterator[FoundResult]:
+ """Find distributions in a location."""
+ # Skip looking inside a wheel. Since a package inside a wheel is not
+ # always valid (due to .data directories etc.), its .dist-info entry
+ # should not be considered an installed distribution.
+ if _looks_like_wheel(location):
+ return
+ # To know exactly where we find a distribution, we have to feed in the
+ # paths one by one, instead of dumping the list to importlib.metadata.
+ for dist in importlib.metadata.distributions(path=[location]):
+ info_location = get_info_location(dist)
+ try:
+ name = get_dist_canonical_name(dist)
+ except BadMetadata as e:
+ logger.warning("Skipping %s due to %s", info_location, e.reason)
+ continue
+ if name in self._found_names:
+ continue
+ self._found_names.add(name)
+ yield dist, info_location
+
+ def find(self, location: str) -> Iterator[BaseDistribution]:
+ """Find distributions in a location.
+
+ The path can be either a directory, or a ZIP archive.
+ """
+ for dist, info_location in self._find_impl(location):
+ if info_location is None:
+ installed_location: Optional[BasePath] = None
+ else:
+ installed_location = info_location.parent
+ yield Distribution(dist, info_location, installed_location)
+
+ def find_linked(self, location: str) -> Iterator[BaseDistribution]:
+ """Read location in egg-link files and return distributions in there.
+
+ The path should be a directory; otherwise this returns nothing. This
+ follows how setuptools does this for compatibility. The first non-empty
+ line in the egg-link is read as a path (resolved against the egg-link's
+ containing directory if relative). Distributions found at that linked
+ location are returned.
+ """
+ path = pathlib.Path(location)
+ if not path.is_dir():
+ return
+ for child in path.iterdir():
+ if child.suffix != ".egg-link":
+ continue
+ with child.open() as f:
+ lines = (line.strip() for line in f)
+ target_rel = next((line for line in lines if line), "")
+ if not target_rel:
+ continue
+ target_location = str(path.joinpath(target_rel))
+ for dist, info_location in self._find_impl(target_location):
+ yield Distribution(dist, info_location, path)
+
+ def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]:
+ from pip._vendor.pkg_resources import find_distributions
+
+ from pip._internal.metadata import pkg_resources as legacy
+
+ with os.scandir(location) as it:
+ for entry in it:
+ if not entry.name.endswith(".egg"):
+ continue
+ for dist in find_distributions(entry.path):
+ yield legacy.Distribution(dist)
+
+ def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]:
+ from pip._vendor.pkg_resources import find_eggs_in_zip
+
+ from pip._internal.metadata import pkg_resources as legacy
+
+ try:
+ importer = zipimport.zipimporter(location)
+ except zipimport.ZipImportError:
+ return
+ for dist in find_eggs_in_zip(importer, location):
+ yield legacy.Distribution(dist)
+
+ def find_eggs(self, location: str) -> Iterator[BaseDistribution]:
+ """Find eggs in a location.
+
+ This actually uses the old *pkg_resources* backend. We likely want to
+ deprecate this so we can eventually remove the *pkg_resources*
+ dependency entirely. Before that, this should first emit a deprecation
+ warning for some versions when using the fallback since importing
+ *pkg_resources* is slow for those who don't need it.
+ """
+ if os.path.isdir(location):
+ yield from self._find_eggs_in_dir(location)
+ if zipfile.is_zipfile(location):
+ yield from self._find_eggs_in_zip(location)
+
+
+@functools.lru_cache(maxsize=None) # Warn a distribution exactly once.
+def _emit_egg_deprecation(location: Optional[str]) -> None:
+ deprecated(
+ reason=f"Loading egg at {location} is deprecated.",
+ replacement="to use pip for package installation",
+ gone_in="25.1",
+ issue=12330,
+ )
+
+
+class Environment(BaseEnvironment):
+ def __init__(self, paths: Sequence[str]) -> None:
+ self._paths = paths
+
+ @classmethod
+ def default(cls) -> BaseEnvironment:
+ return cls(sys.path)
+
+ @classmethod
+ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
+ if paths is None:
+ return cls(sys.path)
+ return cls(paths)
+
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
+ finder = _DistributionFinder()
+ for location in self._paths:
+ yield from finder.find(location)
+ for dist in finder.find_eggs(location):
+ _emit_egg_deprecation(dist.location)
+ yield dist
+ # This must go last because that's how pkg_resources tie-breaks.
+ yield from finder.find_linked(location)
+
+ def get_distribution(self, name: str) -> Optional[BaseDistribution]:
+ canonical_name = canonicalize_name(name)
+ matches = (
+ distribution
+ for distribution in self.iter_all_distributions()
+ if distribution.canonical_name == canonical_name
+ )
+ return next(matches, None)
diff --git a/env/Lib/site-packages/pip/_internal/metadata/pkg_resources.py b/env/Lib/site-packages/pip/_internal/metadata/pkg_resources.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ea84f93a6fb8f2d04230d70491eac7809672031
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/metadata/pkg_resources.py
@@ -0,0 +1,301 @@
+import email.message
+import email.parser
+import logging
+import os
+import zipfile
+from typing import (
+ Collection,
+ Iterable,
+ Iterator,
+ List,
+ Mapping,
+ NamedTuple,
+ Optional,
+)
+
+from pip._vendor import pkg_resources
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+from pip._vendor.packaging.version import Version
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
+from pip._internal.utils.egg_link import egg_link_path_from_location
+from pip._internal.utils.misc import display_path, normalize_path
+from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
+
+from .base import (
+ BaseDistribution,
+ BaseEntryPoint,
+ BaseEnvironment,
+ InfoPath,
+ Wheel,
+)
+
+__all__ = ["NAME", "Distribution", "Environment"]
+
+logger = logging.getLogger(__name__)
+
+NAME = "pkg_resources"
+
+
+class EntryPoint(NamedTuple):
+ name: str
+ value: str
+ group: str
+
+
+class InMemoryMetadata:
+ """IMetadataProvider that reads metadata files from a dictionary.
+
+ This also maps metadata decoding exceptions to our internal exception type.
+ """
+
+ def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None:
+ self._metadata = metadata
+ self._wheel_name = wheel_name
+
+ def has_metadata(self, name: str) -> bool:
+ return name in self._metadata
+
+ def get_metadata(self, name: str) -> str:
+ try:
+ return self._metadata[name].decode()
+ except UnicodeDecodeError as e:
+ # Augment the default error with the origin of the file.
+ raise UnsupportedWheel(
+ f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
+ )
+
+ def get_metadata_lines(self, name: str) -> Iterable[str]:
+ return pkg_resources.yield_lines(self.get_metadata(name))
+
+ def metadata_isdir(self, name: str) -> bool:
+ return False
+
+ def metadata_listdir(self, name: str) -> List[str]:
+ return []
+
+ def run_script(self, script_name: str, namespace: str) -> None:
+ pass
+
+
+class Distribution(BaseDistribution):
+ def __init__(self, dist: pkg_resources.Distribution) -> None:
+ self._dist = dist
+ # This is populated lazily, to avoid loading metadata for all possible
+ # distributions eagerly.
+ self.__extra_mapping: Optional[Mapping[NormalizedName, str]] = None
+
+ @property
+ def _extra_mapping(self) -> Mapping[NormalizedName, str]:
+ if self.__extra_mapping is None:
+ self.__extra_mapping = {
+ canonicalize_name(extra): extra for extra in self._dist.extras
+ }
+
+ return self.__extra_mapping
+
+ @classmethod
+ def from_directory(cls, directory: str) -> BaseDistribution:
+ dist_dir = directory.rstrip(os.sep)
+
+ # Build a PathMetadata object, from path to metadata. :wink:
+ base_dir, dist_dir_name = os.path.split(dist_dir)
+ metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
+
+ # Determine the correct Distribution object type.
+ if dist_dir.endswith(".egg-info"):
+ dist_cls = pkg_resources.Distribution
+ dist_name = os.path.splitext(dist_dir_name)[0]
+ else:
+ assert dist_dir.endswith(".dist-info")
+ dist_cls = pkg_resources.DistInfoDistribution
+ dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
+
+ dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
+ return cls(dist)
+
+ @classmethod
+ def from_metadata_file_contents(
+ cls,
+ metadata_contents: bytes,
+ filename: str,
+ project_name: str,
+ ) -> BaseDistribution:
+ metadata_dict = {
+ "METADATA": metadata_contents,
+ }
+ dist = pkg_resources.DistInfoDistribution(
+ location=filename,
+ metadata=InMemoryMetadata(metadata_dict, filename),
+ project_name=project_name,
+ )
+ return cls(dist)
+
+ @classmethod
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
+ try:
+ with wheel.as_zipfile() as zf:
+ info_dir, _ = parse_wheel(zf, name)
+ metadata_dict = {
+ path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
+ for path in zf.namelist()
+ if path.startswith(f"{info_dir}/")
+ }
+ except zipfile.BadZipFile as e:
+ raise InvalidWheel(wheel.location, name) from e
+ except UnsupportedWheel as e:
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
+ dist = pkg_resources.DistInfoDistribution(
+ location=wheel.location,
+ metadata=InMemoryMetadata(metadata_dict, wheel.location),
+ project_name=name,
+ )
+ return cls(dist)
+
+ @property
+ def location(self) -> Optional[str]:
+ return self._dist.location
+
+ @property
+ def installed_location(self) -> Optional[str]:
+ egg_link = egg_link_path_from_location(self.raw_name)
+ if egg_link:
+ location = egg_link
+ elif self.location:
+ location = self.location
+ else:
+ return None
+ return normalize_path(location)
+
+ @property
+ def info_location(self) -> Optional[str]:
+ return self._dist.egg_info
+
+ @property
+ def installed_by_distutils(self) -> bool:
+ # A distutils-installed distribution is provided by FileMetadata. This
+ # provider has a "path" attribute not present anywhere else. Not the
+ # best introspection logic, but pip has been doing this for a long time.
+ try:
+ return bool(self._dist._provider.path)
+ except AttributeError:
+ return False
+
+ @property
+ def canonical_name(self) -> NormalizedName:
+ return canonicalize_name(self._dist.project_name)
+
+ @property
+ def version(self) -> Version:
+ return parse_version(self._dist.version)
+
+ @property
+ def raw_version(self) -> str:
+ return self._dist.version
+
+ def is_file(self, path: InfoPath) -> bool:
+ return self._dist.has_metadata(str(path))
+
+ def iter_distutils_script_names(self) -> Iterator[str]:
+ yield from self._dist.metadata_listdir("scripts")
+
+ def read_text(self, path: InfoPath) -> str:
+ name = str(path)
+ if not self._dist.has_metadata(name):
+ raise FileNotFoundError(name)
+ content = self._dist.get_metadata(name)
+ if content is None:
+ raise NoneMetadataError(self, name)
+ return content
+
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
+ for group, entries in self._dist.get_entry_map().items():
+ for name, entry_point in entries.items():
+ name, _, value = str(entry_point).partition("=")
+ yield EntryPoint(name=name.strip(), value=value.strip(), group=group)
+
+ def _metadata_impl(self) -> email.message.Message:
+ """
+ :raises NoneMetadataError: if the distribution reports `has_metadata()`
+ True but `get_metadata()` returns None.
+ """
+ if isinstance(self._dist, pkg_resources.DistInfoDistribution):
+ metadata_name = "METADATA"
+ else:
+ metadata_name = "PKG-INFO"
+ try:
+ metadata = self.read_text(metadata_name)
+ except FileNotFoundError:
+ if self.location:
+ displaying_path = display_path(self.location)
+ else:
+ displaying_path = repr(self.location)
+ logger.warning("No metadata found in %s", displaying_path)
+ metadata = ""
+ feed_parser = email.parser.FeedParser()
+ feed_parser.feed(metadata)
+ return feed_parser.close()
+
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
+ if extras:
+ relevant_extras = set(self._extra_mapping) & set(
+ map(canonicalize_name, extras)
+ )
+ extras = [self._extra_mapping[extra] for extra in relevant_extras]
+ return self._dist.requires(extras)
+
+ def iter_provided_extras(self) -> Iterable[NormalizedName]:
+ return self._extra_mapping.keys()
+
+
+class Environment(BaseEnvironment):
+ def __init__(self, ws: pkg_resources.WorkingSet) -> None:
+ self._ws = ws
+
+ @classmethod
+ def default(cls) -> BaseEnvironment:
+ return cls(pkg_resources.working_set)
+
+ @classmethod
+ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
+ return cls(pkg_resources.WorkingSet(paths))
+
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
+ for dist in self._ws:
+ yield Distribution(dist)
+
+ def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
+ """Find a distribution matching the ``name`` in the environment.
+
+ This searches from *all* distributions available in the environment, to
+ match the behavior of ``pkg_resources.get_distribution()``.
+ """
+ canonical_name = canonicalize_name(name)
+ for dist in self.iter_all_distributions():
+ if dist.canonical_name == canonical_name:
+ return dist
+ return None
+
+ def get_distribution(self, name: str) -> Optional[BaseDistribution]:
+ # Search the distribution by looking through the working set.
+ dist = self._search_distribution(name)
+ if dist:
+ return dist
+
+ # If distribution could not be found, call working_set.require to
+ # update the working set, and try to find the distribution again.
+ # This might happen for e.g. when you install a package twice, once
+ # using setup.py develop and again using setup.py install. Now when
+ # running pip uninstall twice, the package gets removed from the
+ # working set in the first uninstall, so we have to populate the
+ # working set again so that pip knows about it and the packages gets
+ # picked up and is successfully uninstalled the second time too.
+ try:
+ # We didn't pass in any version specifiers, so this can never
+ # raise pkg_resources.VersionConflict.
+ self._ws.require(name)
+ except pkg_resources.DistributionNotFound:
+ return None
+ return self._search_distribution(name)
diff --git a/env/Lib/site-packages/pip/_internal/models/__init__.py b/env/Lib/site-packages/pip/_internal/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7855226e4b500142deef8fb247cd33a9a991d122
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/__init__.py
@@ -0,0 +1,2 @@
+"""A package that contains models that represent entities.
+"""
diff --git a/env/Lib/site-packages/pip/_internal/models/candidate.py b/env/Lib/site-packages/pip/_internal/models/candidate.py
new file mode 100644
index 0000000000000000000000000000000000000000..f27f283154ac5aa55d52ccac754138b36341ff6b
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/candidate.py
@@ -0,0 +1,25 @@
+from dataclasses import dataclass
+
+from pip._vendor.packaging.version import Version
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.models.link import Link
+
+
+@dataclass(frozen=True)
+class InstallationCandidate:
+ """Represents a potential "candidate" for installation."""
+
+ __slots__ = ["name", "version", "link"]
+
+ name: str
+ version: Version
+ link: Link
+
+ def __init__(self, name: str, version: str, link: Link) -> None:
+ object.__setattr__(self, "name", name)
+ object.__setattr__(self, "version", parse_version(version))
+ object.__setattr__(self, "link", link)
+
+ def __str__(self) -> str:
+ return f"{self.name!r} candidate (version {self.version} at {self.link})"
diff --git a/env/Lib/site-packages/pip/_internal/models/direct_url.py b/env/Lib/site-packages/pip/_internal/models/direct_url.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc5ec8d4aa9b02b7264f7a5a0222e7e1fe215ad0
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/direct_url.py
@@ -0,0 +1,224 @@
+""" PEP 610 """
+
+import json
+import re
+import urllib.parse
+from dataclasses import dataclass
+from typing import Any, ClassVar, Dict, Iterable, Optional, Type, TypeVar, Union
+
+__all__ = [
+ "DirectUrl",
+ "DirectUrlValidationError",
+ "DirInfo",
+ "ArchiveInfo",
+ "VcsInfo",
+]
+
+T = TypeVar("T")
+
+DIRECT_URL_METADATA_NAME = "direct_url.json"
+ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$")
+
+
+class DirectUrlValidationError(Exception):
+ pass
+
+
+def _get(
+ d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
+) -> Optional[T]:
+ """Get value from dictionary and verify expected type."""
+ if key not in d:
+ return default
+ value = d[key]
+ if not isinstance(value, expected_type):
+ raise DirectUrlValidationError(
+ f"{value!r} has unexpected type for {key} (expected {expected_type})"
+ )
+ return value
+
+
+def _get_required(
+ d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
+) -> T:
+ value = _get(d, expected_type, key, default)
+ if value is None:
+ raise DirectUrlValidationError(f"{key} must have a value")
+ return value
+
+
+def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
+ infos = [info for info in infos if info is not None]
+ if not infos:
+ raise DirectUrlValidationError(
+ "missing one of archive_info, dir_info, vcs_info"
+ )
+ if len(infos) > 1:
+ raise DirectUrlValidationError(
+ "more than one of archive_info, dir_info, vcs_info"
+ )
+ assert infos[0] is not None
+ return infos[0]
+
+
+def _filter_none(**kwargs: Any) -> Dict[str, Any]:
+ """Make dict excluding None values."""
+ return {k: v for k, v in kwargs.items() if v is not None}
+
+
+@dataclass
+class VcsInfo:
+ name: ClassVar = "vcs_info"
+
+ vcs: str
+ commit_id: str
+ requested_revision: Optional[str] = None
+
+ @classmethod
+ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
+ if d is None:
+ return None
+ return cls(
+ vcs=_get_required(d, str, "vcs"),
+ commit_id=_get_required(d, str, "commit_id"),
+ requested_revision=_get(d, str, "requested_revision"),
+ )
+
+ def _to_dict(self) -> Dict[str, Any]:
+ return _filter_none(
+ vcs=self.vcs,
+ requested_revision=self.requested_revision,
+ commit_id=self.commit_id,
+ )
+
+
+class ArchiveInfo:
+ name = "archive_info"
+
+ def __init__(
+ self,
+ hash: Optional[str] = None,
+ hashes: Optional[Dict[str, str]] = None,
+ ) -> None:
+ # set hashes before hash, since the hash setter will further populate hashes
+ self.hashes = hashes
+ self.hash = hash
+
+ @property
+ def hash(self) -> Optional[str]:
+ return self._hash
+
+ @hash.setter
+ def hash(self, value: Optional[str]) -> None:
+ if value is not None:
+ # Auto-populate the hashes key to upgrade to the new format automatically.
+ # We don't back-populate the legacy hash key from hashes.
+ try:
+ hash_name, hash_value = value.split("=", 1)
+ except ValueError:
+ raise DirectUrlValidationError(
+ f"invalid archive_info.hash format: {value!r}"
+ )
+ if self.hashes is None:
+ self.hashes = {hash_name: hash_value}
+ elif hash_name not in self.hashes:
+ self.hashes = self.hashes.copy()
+ self.hashes[hash_name] = hash_value
+ self._hash = value
+
+ @classmethod
+ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
+ if d is None:
+ return None
+ return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
+
+ def _to_dict(self) -> Dict[str, Any]:
+ return _filter_none(hash=self.hash, hashes=self.hashes)
+
+
+@dataclass
+class DirInfo:
+ name: ClassVar = "dir_info"
+
+ editable: bool = False
+
+ @classmethod
+ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
+ if d is None:
+ return None
+ return cls(editable=_get_required(d, bool, "editable", default=False))
+
+ def _to_dict(self) -> Dict[str, Any]:
+ return _filter_none(editable=self.editable or None)
+
+
+InfoType = Union[ArchiveInfo, DirInfo, VcsInfo]
+
+
+@dataclass
+class DirectUrl:
+ url: str
+ info: InfoType
+ subdirectory: Optional[str] = None
+
+ def _remove_auth_from_netloc(self, netloc: str) -> str:
+ if "@" not in netloc:
+ return netloc
+ user_pass, netloc_no_user_pass = netloc.split("@", 1)
+ if (
+ isinstance(self.info, VcsInfo)
+ and self.info.vcs == "git"
+ and user_pass == "git"
+ ):
+ return netloc
+ if ENV_VAR_RE.match(user_pass):
+ return netloc
+ return netloc_no_user_pass
+
+ @property
+ def redacted_url(self) -> str:
+ """url with user:password part removed unless it is formed with
+ environment variables as specified in PEP 610, or it is ``git``
+ in the case of a git URL.
+ """
+ purl = urllib.parse.urlsplit(self.url)
+ netloc = self._remove_auth_from_netloc(purl.netloc)
+ surl = urllib.parse.urlunsplit(
+ (purl.scheme, netloc, purl.path, purl.query, purl.fragment)
+ )
+ return surl
+
+ def validate(self) -> None:
+ self.from_dict(self.to_dict())
+
+ @classmethod
+ def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
+ return DirectUrl(
+ url=_get_required(d, str, "url"),
+ subdirectory=_get(d, str, "subdirectory"),
+ info=_exactly_one_of(
+ [
+ ArchiveInfo._from_dict(_get(d, dict, "archive_info")),
+ DirInfo._from_dict(_get(d, dict, "dir_info")),
+ VcsInfo._from_dict(_get(d, dict, "vcs_info")),
+ ]
+ ),
+ )
+
+ def to_dict(self) -> Dict[str, Any]:
+ res = _filter_none(
+ url=self.redacted_url,
+ subdirectory=self.subdirectory,
+ )
+ res[self.info.name] = self.info._to_dict()
+ return res
+
+ @classmethod
+ def from_json(cls, s: str) -> "DirectUrl":
+ return cls.from_dict(json.loads(s))
+
+ def to_json(self) -> str:
+ return json.dumps(self.to_dict(), sort_keys=True)
+
+ def is_local_editable(self) -> bool:
+ return isinstance(self.info, DirInfo) and self.info.editable
diff --git a/env/Lib/site-packages/pip/_internal/models/format_control.py b/env/Lib/site-packages/pip/_internal/models/format_control.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccd11272c030c2d067e1bb6d90fc744c7379a923
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/format_control.py
@@ -0,0 +1,78 @@
+from typing import FrozenSet, Optional, Set
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.exceptions import CommandError
+
+
+class FormatControl:
+ """Helper for managing formats from which a package can be installed."""
+
+ __slots__ = ["no_binary", "only_binary"]
+
+ def __init__(
+ self,
+ no_binary: Optional[Set[str]] = None,
+ only_binary: Optional[Set[str]] = None,
+ ) -> None:
+ if no_binary is None:
+ no_binary = set()
+ if only_binary is None:
+ only_binary = set()
+
+ self.no_binary = no_binary
+ self.only_binary = only_binary
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, self.__class__):
+ return NotImplemented
+
+ if self.__slots__ != other.__slots__:
+ return False
+
+ return all(getattr(self, k) == getattr(other, k) for k in self.__slots__)
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})"
+
+ @staticmethod
+ def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
+ if value.startswith("-"):
+ raise CommandError(
+ "--no-binary / --only-binary option requires 1 argument."
+ )
+ new = value.split(",")
+ while ":all:" in new:
+ other.clear()
+ target.clear()
+ target.add(":all:")
+ del new[: new.index(":all:") + 1]
+ # Without a none, we want to discard everything as :all: covers it
+ if ":none:" not in new:
+ return
+ for name in new:
+ if name == ":none:":
+ target.clear()
+ continue
+ name = canonicalize_name(name)
+ other.discard(name)
+ target.add(name)
+
+ def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
+ result = {"binary", "source"}
+ if canonical_name in self.only_binary:
+ result.discard("source")
+ elif canonical_name in self.no_binary:
+ result.discard("binary")
+ elif ":all:" in self.only_binary:
+ result.discard("source")
+ elif ":all:" in self.no_binary:
+ result.discard("binary")
+ return frozenset(result)
+
+ def disallow_binaries(self) -> None:
+ self.handle_mutual_excludes(
+ ":all:",
+ self.no_binary,
+ self.only_binary,
+ )
diff --git a/env/Lib/site-packages/pip/_internal/models/index.py b/env/Lib/site-packages/pip/_internal/models/index.py
new file mode 100644
index 0000000000000000000000000000000000000000..b94c32511f0cda2363bfc4f29c9c8bfcc7101f9b
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/index.py
@@ -0,0 +1,28 @@
+import urllib.parse
+
+
+class PackageIndex:
+ """Represents a Package Index and provides easier access to endpoints"""
+
+ __slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"]
+
+ def __init__(self, url: str, file_storage_domain: str) -> None:
+ super().__init__()
+ self.url = url
+ self.netloc = urllib.parse.urlsplit(url).netloc
+ self.simple_url = self._url_for_path("simple")
+ self.pypi_url = self._url_for_path("pypi")
+
+ # This is part of a temporary hack used to block installs of PyPI
+ # packages which depend on external urls only necessary until PyPI can
+ # block such packages themselves
+ self.file_storage_domain = file_storage_domain
+
+ def _url_for_path(self, path: str) -> str:
+ return urllib.parse.urljoin(self.url, path)
+
+
+PyPI = PackageIndex("https://pypi.org/", file_storage_domain="files.pythonhosted.org")
+TestPyPI = PackageIndex(
+ "https://test.pypi.org/", file_storage_domain="test-files.pythonhosted.org"
+)
diff --git a/env/Lib/site-packages/pip/_internal/models/installation_report.py b/env/Lib/site-packages/pip/_internal/models/installation_report.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9c6330df32bd2b57c885156cb7f8c0c8c3e3741
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/installation_report.py
@@ -0,0 +1,56 @@
+from typing import Any, Dict, Sequence
+
+from pip._vendor.packaging.markers import default_environment
+
+from pip import __version__
+from pip._internal.req.req_install import InstallRequirement
+
+
+class InstallationReport:
+ def __init__(self, install_requirements: Sequence[InstallRequirement]):
+ self._install_requirements = install_requirements
+
+ @classmethod
+ def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
+ assert ireq.download_info, f"No download_info for {ireq}"
+ res = {
+ # PEP 610 json for the download URL. download_info.archive_info.hashes may
+ # be absent when the requirement was installed from the wheel cache
+ # and the cache entry was populated by an older pip version that did not
+ # record origin.json.
+ "download_info": ireq.download_info.to_dict(),
+ # is_direct is true if the requirement was a direct URL reference (which
+ # includes editable requirements), and false if the requirement was
+ # downloaded from a PEP 503 index or --find-links.
+ "is_direct": ireq.is_direct,
+ # is_yanked is true if the requirement was yanked from the index, but
+ # was still selected by pip to conform to PEP 592.
+ "is_yanked": ireq.link.is_yanked if ireq.link else False,
+ # requested is true if the requirement was specified by the user (aka
+ # top level requirement), and false if it was installed as a dependency of a
+ # requirement. https://peps.python.org/pep-0376/#requested
+ "requested": ireq.user_supplied,
+ # PEP 566 json encoding for metadata
+ # https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
+ "metadata": ireq.get_dist().metadata_dict,
+ }
+ if ireq.user_supplied and ireq.extras:
+ # For top level requirements, the list of requested extras, if any.
+ res["requested_extras"] = sorted(ireq.extras)
+ return res
+
+ def to_dict(self) -> Dict[str, Any]:
+ return {
+ "version": "1",
+ "pip_version": __version__,
+ "install": [
+ self._install_req_to_dict(ireq) for ireq in self._install_requirements
+ ],
+ # https://peps.python.org/pep-0508/#environment-markers
+ # TODO: currently, the resolver uses the default environment to evaluate
+ # environment markers, so that is what we report here. In the future, it
+ # should also take into account options such as --python-version or
+ # --platform, perhaps under the form of an environment_override field?
+ # https://github.com/pypa/pip/issues/11198
+ "environment": default_environment(),
+ }
diff --git a/env/Lib/site-packages/pip/_internal/models/link.py b/env/Lib/site-packages/pip/_internal/models/link.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f41f2f6a09036d321de5a2453eeafe16f152ebf
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/link.py
@@ -0,0 +1,590 @@
+import functools
+import itertools
+import logging
+import os
+import posixpath
+import re
+import urllib.parse
+from dataclasses import dataclass
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ List,
+ Mapping,
+ NamedTuple,
+ Optional,
+ Tuple,
+ Union,
+)
+
+from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.filetypes import WHEEL_EXTENSION
+from pip._internal.utils.hashes import Hashes
+from pip._internal.utils.misc import (
+ pairwise,
+ redact_auth_from_url,
+ split_auth_from_netloc,
+ splitext,
+)
+from pip._internal.utils.urls import path_to_url, url_to_path
+
+if TYPE_CHECKING:
+ from pip._internal.index.collector import IndexContent
+
+logger = logging.getLogger(__name__)
+
+
+# Order matters, earlier hashes have a precedence over later hashes for what
+# we will pick to use.
+_SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5")
+
+
+@dataclass(frozen=True)
+class LinkHash:
+ """Links to content may have embedded hash values. This class parses those.
+
+ `name` must be any member of `_SUPPORTED_HASHES`.
+
+ This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to
+ be JSON-serializable to conform to PEP 610, this class contains the logic for
+ parsing a hash name and value for correctness, and then checking whether that hash
+ conforms to a schema with `.is_hash_allowed()`."""
+
+ name: str
+ value: str
+
+ _hash_url_fragment_re = re.compile(
+ # NB: we do not validate that the second group (.*) is a valid hex
+ # digest. Instead, we simply keep that string in this class, and then check it
+ # against Hashes when hash-checking is needed. This is easier to debug than
+ # proactively discarding an invalid hex digest, as we handle incorrect hashes
+ # and malformed hashes in the same place.
+ r"[#&]({choices})=([^&]*)".format(
+ choices="|".join(re.escape(hash_name) for hash_name in _SUPPORTED_HASHES)
+ ),
+ )
+
+ def __post_init__(self) -> None:
+ assert self.name in _SUPPORTED_HASHES
+
+ @classmethod
+ @functools.lru_cache(maxsize=None)
+ def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
+ """Search a string for a checksum algorithm name and encoded output value."""
+ match = cls._hash_url_fragment_re.search(url)
+ if match is None:
+ return None
+ name, value = match.groups()
+ return cls(name=name, value=value)
+
+ def as_dict(self) -> Dict[str, str]:
+ return {self.name: self.value}
+
+ def as_hashes(self) -> Hashes:
+ """Return a Hashes instance which checks only for the current hash."""
+ return Hashes({self.name: [self.value]})
+
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
+ """
+ Return True if the current hash is allowed by `hashes`.
+ """
+ if hashes is None:
+ return False
+ return hashes.is_hash_allowed(self.name, hex_digest=self.value)
+
+
+@dataclass(frozen=True)
+class MetadataFile:
+ """Information about a core metadata file associated with a distribution."""
+
+ hashes: Optional[Dict[str, str]]
+
+ def __post_init__(self) -> None:
+ if self.hashes is not None:
+ assert all(name in _SUPPORTED_HASHES for name in self.hashes)
+
+
+def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
+ # Remove any unsupported hash types from the mapping. If this leaves no
+ # supported hashes, return None
+ if hashes is None:
+ return None
+ hashes = {n: v for n, v in hashes.items() if n in _SUPPORTED_HASHES}
+ if not hashes:
+ return None
+ return hashes
+
+
+def _clean_url_path_part(part: str) -> str:
+ """
+ Clean a "part" of a URL path (i.e. after splitting on "@" characters).
+ """
+ # We unquote prior to quoting to make sure nothing is double quoted.
+ return urllib.parse.quote(urllib.parse.unquote(part))
+
+
+def _clean_file_url_path(part: str) -> str:
+ """
+ Clean the first part of a URL path that corresponds to a local
+ filesystem path (i.e. the first part after splitting on "@" characters).
+ """
+ # We unquote prior to quoting to make sure nothing is double quoted.
+ # Also, on Windows the path part might contain a drive letter which
+ # should not be quoted. On Linux where drive letters do not
+ # exist, the colon should be quoted. We rely on urllib.request
+ # to do the right thing here.
+ return urllib.request.pathname2url(urllib.request.url2pathname(part))
+
+
+# percent-encoded: /
+_reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
+
+
+def _clean_url_path(path: str, is_local_path: bool) -> str:
+ """
+ Clean the path portion of a URL.
+ """
+ if is_local_path:
+ clean_func = _clean_file_url_path
+ else:
+ clean_func = _clean_url_path_part
+
+ # Split on the reserved characters prior to cleaning so that
+ # revision strings in VCS URLs are properly preserved.
+ parts = _reserved_chars_re.split(path)
+
+ cleaned_parts = []
+ for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
+ cleaned_parts.append(clean_func(to_clean))
+ # Normalize %xx escapes (e.g. %2f -> %2F)
+ cleaned_parts.append(reserved.upper())
+
+ return "".join(cleaned_parts)
+
+
+def _ensure_quoted_url(url: str) -> str:
+ """
+ Make sure a link is fully quoted.
+ For example, if ' ' occurs in the URL, it will be replaced with "%20",
+ and without double-quoting other characters.
+ """
+ # Split the URL into parts according to the general structure
+ # `scheme://netloc/path;parameters?query#fragment`.
+ result = urllib.parse.urlparse(url)
+ # If the netloc is empty, then the URL refers to a local filesystem path.
+ is_local_path = not result.netloc
+ path = _clean_url_path(result.path, is_local_path=is_local_path)
+ return urllib.parse.urlunparse(result._replace(path=path))
+
+
+@functools.total_ordering
+class Link:
+ """Represents a parsed link from a Package Index's simple URL"""
+
+ __slots__ = [
+ "_parsed_url",
+ "_url",
+ "_hashes",
+ "comes_from",
+ "requires_python",
+ "yanked_reason",
+ "metadata_file_data",
+ "cache_link_parsing",
+ "egg_fragment",
+ ]
+
+ def __init__(
+ self,
+ url: str,
+ comes_from: Optional[Union[str, "IndexContent"]] = None,
+ requires_python: Optional[str] = None,
+ yanked_reason: Optional[str] = None,
+ metadata_file_data: Optional[MetadataFile] = None,
+ cache_link_parsing: bool = True,
+ hashes: Optional[Mapping[str, str]] = None,
+ ) -> None:
+ """
+ :param url: url of the resource pointed to (href of the link)
+ :param comes_from: instance of IndexContent where the link was found,
+ or string.
+ :param requires_python: String containing the `Requires-Python`
+ metadata field, specified in PEP 345. This may be specified by
+ a data-requires-python attribute in the HTML link tag, as
+ described in PEP 503.
+ :param yanked_reason: the reason the file has been yanked, if the
+ file has been yanked, or None if the file hasn't been yanked.
+ This is the value of the "data-yanked" attribute, if present, in
+ a simple repository HTML link. If the file has been yanked but
+ no reason was provided, this should be the empty string. See
+ PEP 592 for more information and the specification.
+ :param metadata_file_data: the metadata attached to the file, or None if
+ no such metadata is provided. This argument, if not None, indicates
+ that a separate metadata file exists, and also optionally supplies
+ hashes for that file.
+ :param cache_link_parsing: A flag that is used elsewhere to determine
+ whether resources retrieved from this link should be cached. PyPI
+ URLs should generally have this set to False, for example.
+ :param hashes: A mapping of hash names to digests to allow us to
+ determine the validity of a download.
+ """
+
+ # The comes_from, requires_python, and metadata_file_data arguments are
+ # only used by classmethods of this class, and are not used in client
+ # code directly.
+
+ # url can be a UNC windows share
+ if url.startswith("\\\\"):
+ url = path_to_url(url)
+
+ self._parsed_url = urllib.parse.urlsplit(url)
+ # Store the url as a private attribute to prevent accidentally
+ # trying to set a new value.
+ self._url = url
+
+ link_hash = LinkHash.find_hash_url_fragment(url)
+ hashes_from_link = {} if link_hash is None else link_hash.as_dict()
+ if hashes is None:
+ self._hashes = hashes_from_link
+ else:
+ self._hashes = {**hashes, **hashes_from_link}
+
+ self.comes_from = comes_from
+ self.requires_python = requires_python if requires_python else None
+ self.yanked_reason = yanked_reason
+ self.metadata_file_data = metadata_file_data
+
+ self.cache_link_parsing = cache_link_parsing
+ self.egg_fragment = self._egg_fragment()
+
+ @classmethod
+ def from_json(
+ cls,
+ file_data: Dict[str, Any],
+ page_url: str,
+ ) -> Optional["Link"]:
+ """
+ Convert an pypi json document from a simple repository page into a Link.
+ """
+ file_url = file_data.get("url")
+ if file_url is None:
+ return None
+
+ url = _ensure_quoted_url(urllib.parse.urljoin(page_url, file_url))
+ pyrequire = file_data.get("requires-python")
+ yanked_reason = file_data.get("yanked")
+ hashes = file_data.get("hashes", {})
+
+ # PEP 714: Indexes must use the name core-metadata, but
+ # clients should support the old name as a fallback for compatibility.
+ metadata_info = file_data.get("core-metadata")
+ if metadata_info is None:
+ metadata_info = file_data.get("dist-info-metadata")
+
+ # The metadata info value may be a boolean, or a dict of hashes.
+ if isinstance(metadata_info, dict):
+ # The file exists, and hashes have been supplied
+ metadata_file_data = MetadataFile(supported_hashes(metadata_info))
+ elif metadata_info:
+ # The file exists, but there are no hashes
+ metadata_file_data = MetadataFile(None)
+ else:
+ # False or not present: the file does not exist
+ metadata_file_data = None
+
+ # The Link.yanked_reason expects an empty string instead of a boolean.
+ if yanked_reason and not isinstance(yanked_reason, str):
+ yanked_reason = ""
+ # The Link.yanked_reason expects None instead of False.
+ elif not yanked_reason:
+ yanked_reason = None
+
+ return cls(
+ url,
+ comes_from=page_url,
+ requires_python=pyrequire,
+ yanked_reason=yanked_reason,
+ hashes=hashes,
+ metadata_file_data=metadata_file_data,
+ )
+
+ @classmethod
+ def from_element(
+ cls,
+ anchor_attribs: Dict[str, Optional[str]],
+ page_url: str,
+ base_url: str,
+ ) -> Optional["Link"]:
+ """
+ Convert an anchor element's attributes in a simple repository page to a Link.
+ """
+ href = anchor_attribs.get("href")
+ if not href:
+ return None
+
+ url = _ensure_quoted_url(urllib.parse.urljoin(base_url, href))
+ pyrequire = anchor_attribs.get("data-requires-python")
+ yanked_reason = anchor_attribs.get("data-yanked")
+
+ # PEP 714: Indexes must use the name data-core-metadata, but
+ # clients should support the old name as a fallback for compatibility.
+ metadata_info = anchor_attribs.get("data-core-metadata")
+ if metadata_info is None:
+ metadata_info = anchor_attribs.get("data-dist-info-metadata")
+ # The metadata info value may be the string "true", or a string of
+ # the form "hashname=hashval"
+ if metadata_info == "true":
+ # The file exists, but there are no hashes
+ metadata_file_data = MetadataFile(None)
+ elif metadata_info is None:
+ # The file does not exist
+ metadata_file_data = None
+ else:
+ # The file exists, and hashes have been supplied
+ hashname, sep, hashval = metadata_info.partition("=")
+ if sep == "=":
+ metadata_file_data = MetadataFile(supported_hashes({hashname: hashval}))
+ else:
+ # Error - data is wrong. Treat as no hashes supplied.
+ logger.debug(
+ "Index returned invalid data-dist-info-metadata value: %s",
+ metadata_info,
+ )
+ metadata_file_data = MetadataFile(None)
+
+ return cls(
+ url,
+ comes_from=page_url,
+ requires_python=pyrequire,
+ yanked_reason=yanked_reason,
+ metadata_file_data=metadata_file_data,
+ )
+
+ def __str__(self) -> str:
+ if self.requires_python:
+ rp = f" (requires-python:{self.requires_python})"
+ else:
+ rp = ""
+ if self.comes_from:
+ return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}"
+ else:
+ return redact_auth_from_url(str(self._url))
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __hash__(self) -> int:
+ return hash(self.url)
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, Link):
+ return NotImplemented
+ return self.url == other.url
+
+ def __lt__(self, other: Any) -> bool:
+ if not isinstance(other, Link):
+ return NotImplemented
+ return self.url < other.url
+
+ @property
+ def url(self) -> str:
+ return self._url
+
+ @property
+ def filename(self) -> str:
+ path = self.path.rstrip("/")
+ name = posixpath.basename(path)
+ if not name:
+ # Make sure we don't leak auth information if the netloc
+ # includes a username and password.
+ netloc, user_pass = split_auth_from_netloc(self.netloc)
+ return netloc
+
+ name = urllib.parse.unquote(name)
+ assert name, f"URL {self._url!r} produced no filename"
+ return name
+
+ @property
+ def file_path(self) -> str:
+ return url_to_path(self.url)
+
+ @property
+ def scheme(self) -> str:
+ return self._parsed_url.scheme
+
+ @property
+ def netloc(self) -> str:
+ """
+ This can contain auth information.
+ """
+ return self._parsed_url.netloc
+
+ @property
+ def path(self) -> str:
+ return urllib.parse.unquote(self._parsed_url.path)
+
+ def splitext(self) -> Tuple[str, str]:
+ return splitext(posixpath.basename(self.path.rstrip("/")))
+
+ @property
+ def ext(self) -> str:
+ return self.splitext()[1]
+
+ @property
+ def url_without_fragment(self) -> str:
+ scheme, netloc, path, query, fragment = self._parsed_url
+ return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
+
+ _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
+
+ # Per PEP 508.
+ _project_name_re = re.compile(
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
+ )
+
+ def _egg_fragment(self) -> Optional[str]:
+ match = self._egg_fragment_re.search(self._url)
+ if not match:
+ return None
+
+ # An egg fragment looks like a PEP 508 project name, along with
+ # an optional extras specifier. Anything else is invalid.
+ project_name = match.group(1)
+ if not self._project_name_re.match(project_name):
+ deprecated(
+ reason=f"{self} contains an egg fragment with a non-PEP 508 name",
+ replacement="to use the req @ url syntax, and remove the egg fragment",
+ gone_in="25.0",
+ issue=11617,
+ )
+
+ return project_name
+
+ _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
+
+ @property
+ def subdirectory_fragment(self) -> Optional[str]:
+ match = self._subdirectory_fragment_re.search(self._url)
+ if not match:
+ return None
+ return match.group(1)
+
+ def metadata_link(self) -> Optional["Link"]:
+ """Return a link to the associated core metadata file (if any)."""
+ if self.metadata_file_data is None:
+ return None
+ metadata_url = f"{self.url_without_fragment}.metadata"
+ if self.metadata_file_data.hashes is None:
+ return Link(metadata_url)
+ return Link(metadata_url, hashes=self.metadata_file_data.hashes)
+
+ def as_hashes(self) -> Hashes:
+ return Hashes({k: [v] for k, v in self._hashes.items()})
+
+ @property
+ def hash(self) -> Optional[str]:
+ return next(iter(self._hashes.values()), None)
+
+ @property
+ def hash_name(self) -> Optional[str]:
+ return next(iter(self._hashes), None)
+
+ @property
+ def show_url(self) -> str:
+ return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0])
+
+ @property
+ def is_file(self) -> bool:
+ return self.scheme == "file"
+
+ def is_existing_dir(self) -> bool:
+ return self.is_file and os.path.isdir(self.file_path)
+
+ @property
+ def is_wheel(self) -> bool:
+ return self.ext == WHEEL_EXTENSION
+
+ @property
+ def is_vcs(self) -> bool:
+ from pip._internal.vcs import vcs
+
+ return self.scheme in vcs.all_schemes
+
+ @property
+ def is_yanked(self) -> bool:
+ return self.yanked_reason is not None
+
+ @property
+ def has_hash(self) -> bool:
+ return bool(self._hashes)
+
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
+ """
+ Return True if the link has a hash and it is allowed by `hashes`.
+ """
+ if hashes is None:
+ return False
+ return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
+
+
+class _CleanResult(NamedTuple):
+ """Convert link for equivalency check.
+
+ This is used in the resolver to check whether two URL-specified requirements
+ likely point to the same distribution and can be considered equivalent. This
+ equivalency logic avoids comparing URLs literally, which can be too strict
+ (e.g. "a=1&b=2" vs "b=2&a=1") and produce conflicts unexpecting to users.
+
+ Currently this does three things:
+
+ 1. Drop the basic auth part. This is technically wrong since a server can
+ serve different content based on auth, but if it does that, it is even
+ impossible to guarantee two URLs without auth are equivalent, since
+ the user can input different auth information when prompted. So the
+ practical solution is to assume the auth doesn't affect the response.
+ 2. Parse the query to avoid the ordering issue. Note that ordering under the
+ same key in the query are NOT cleaned; i.e. "a=1&a=2" and "a=2&a=1" are
+ still considered different.
+ 3. Explicitly drop most of the fragment part, except ``subdirectory=`` and
+ hash values, since it should have no impact the downloaded content. Note
+ that this drops the "egg=" part historically used to denote the requested
+ project (and extras), which is wrong in the strictest sense, but too many
+ people are supplying it inconsistently to cause superfluous resolution
+ conflicts, so we choose to also ignore them.
+ """
+
+ parsed: urllib.parse.SplitResult
+ query: Dict[str, List[str]]
+ subdirectory: str
+ hashes: Dict[str, str]
+
+
+def _clean_link(link: Link) -> _CleanResult:
+ parsed = link._parsed_url
+ netloc = parsed.netloc.rsplit("@", 1)[-1]
+ # According to RFC 8089, an empty host in file: means localhost.
+ if parsed.scheme == "file" and not netloc:
+ netloc = "localhost"
+ fragment = urllib.parse.parse_qs(parsed.fragment)
+ if "egg" in fragment:
+ logger.debug("Ignoring egg= fragment in %s", link)
+ try:
+ # If there are multiple subdirectory values, use the first one.
+ # This matches the behavior of Link.subdirectory_fragment.
+ subdirectory = fragment["subdirectory"][0]
+ except (IndexError, KeyError):
+ subdirectory = ""
+ # If there are multiple hash values under the same algorithm, use the
+ # first one. This matches the behavior of Link.hash_value.
+ hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
+ return _CleanResult(
+ parsed=parsed._replace(netloc=netloc, query="", fragment=""),
+ query=urllib.parse.parse_qs(parsed.query),
+ subdirectory=subdirectory,
+ hashes=hashes,
+ )
+
+
+@functools.lru_cache(maxsize=None)
+def links_equivalent(link1: Link, link2: Link) -> bool:
+ return _clean_link(link1) == _clean_link(link2)
diff --git a/env/Lib/site-packages/pip/_internal/models/scheme.py b/env/Lib/site-packages/pip/_internal/models/scheme.py
new file mode 100644
index 0000000000000000000000000000000000000000..06a9a550e34389c27ad3ee0bcef73d581cd4b448
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/scheme.py
@@ -0,0 +1,25 @@
+"""
+For types associated with installation schemes.
+
+For a general overview of available schemes and their context, see
+https://docs.python.org/3/install/index.html#alternate-installation.
+"""
+
+from dataclasses import dataclass
+
+SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]
+
+
+@dataclass(frozen=True)
+class Scheme:
+ """A Scheme holds paths which are used as the base directories for
+ artifacts associated with a Python package.
+ """
+
+ __slots__ = SCHEME_KEYS
+
+ platlib: str
+ purelib: str
+ headers: str
+ scripts: str
+ data: str
diff --git a/env/Lib/site-packages/pip/_internal/models/search_scope.py b/env/Lib/site-packages/pip/_internal/models/search_scope.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee7bc86229acda0378707431e5b4e9f054305d85
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/search_scope.py
@@ -0,0 +1,127 @@
+import itertools
+import logging
+import os
+import posixpath
+import urllib.parse
+from dataclasses import dataclass
+from typing import List
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.models.index import PyPI
+from pip._internal.utils.compat import has_tls
+from pip._internal.utils.misc import normalize_path, redact_auth_from_url
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass(frozen=True)
+class SearchScope:
+ """
+ Encapsulates the locations that pip is configured to search.
+ """
+
+ __slots__ = ["find_links", "index_urls", "no_index"]
+
+ find_links: List[str]
+ index_urls: List[str]
+ no_index: bool
+
+ @classmethod
+ def create(
+ cls,
+ find_links: List[str],
+ index_urls: List[str],
+ no_index: bool,
+ ) -> "SearchScope":
+ """
+ Create a SearchScope object after normalizing the `find_links`.
+ """
+ # Build find_links. If an argument starts with ~, it may be
+ # a local file relative to a home directory. So try normalizing
+ # it and if it exists, use the normalized version.
+ # This is deliberately conservative - it might be fine just to
+ # blindly normalize anything starting with a ~...
+ built_find_links: List[str] = []
+ for link in find_links:
+ if link.startswith("~"):
+ new_link = normalize_path(link)
+ if os.path.exists(new_link):
+ link = new_link
+ built_find_links.append(link)
+
+ # If we don't have TLS enabled, then WARN if anyplace we're looking
+ # relies on TLS.
+ if not has_tls():
+ for link in itertools.chain(index_urls, built_find_links):
+ parsed = urllib.parse.urlparse(link)
+ if parsed.scheme == "https":
+ logger.warning(
+ "pip is configured with locations that require "
+ "TLS/SSL, however the ssl module in Python is not "
+ "available."
+ )
+ break
+
+ return cls(
+ find_links=built_find_links,
+ index_urls=index_urls,
+ no_index=no_index,
+ )
+
+ def get_formatted_locations(self) -> str:
+ lines = []
+ redacted_index_urls = []
+ if self.index_urls and self.index_urls != [PyPI.simple_url]:
+ for url in self.index_urls:
+ redacted_index_url = redact_auth_from_url(url)
+
+ # Parse the URL
+ purl = urllib.parse.urlsplit(redacted_index_url)
+
+ # URL is generally invalid if scheme and netloc is missing
+ # there are issues with Python and URL parsing, so this test
+ # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
+ # always parse invalid URLs correctly - it should raise
+ # exceptions for malformed URLs
+ if not purl.scheme and not purl.netloc:
+ logger.warning(
+ 'The index url "%s" seems invalid, please provide a scheme.',
+ redacted_index_url,
+ )
+
+ redacted_index_urls.append(redacted_index_url)
+
+ lines.append(
+ "Looking in indexes: {}".format(", ".join(redacted_index_urls))
+ )
+
+ if self.find_links:
+ lines.append(
+ "Looking in links: {}".format(
+ ", ".join(redact_auth_from_url(url) for url in self.find_links)
+ )
+ )
+ return "\n".join(lines)
+
+ def get_index_urls_locations(self, project_name: str) -> List[str]:
+ """Returns the locations found via self.index_urls
+
+ Checks the url_name on the main (first in the list) index and
+ use this url_name to produce all locations
+ """
+
+ def mkurl_pypi_url(url: str) -> str:
+ loc = posixpath.join(
+ url, urllib.parse.quote(canonicalize_name(project_name))
+ )
+ # For maximum compatibility with easy_install, ensure the path
+ # ends in a trailing slash. Although this isn't in the spec
+ # (and PyPI can handle it without the slash) some other index
+ # implementations might break if they relied on easy_install's
+ # behavior.
+ if not loc.endswith("/"):
+ loc = loc + "/"
+ return loc
+
+ return [mkurl_pypi_url(url) for url in self.index_urls]
diff --git a/env/Lib/site-packages/pip/_internal/models/selection_prefs.py b/env/Lib/site-packages/pip/_internal/models/selection_prefs.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9b50aa51756719d751ed0338aa7ca0a33d45f5a
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/selection_prefs.py
@@ -0,0 +1,53 @@
+from typing import Optional
+
+from pip._internal.models.format_control import FormatControl
+
+
+# TODO: This needs Python 3.10's improved slots support for dataclasses
+# to be converted into a dataclass.
+class SelectionPreferences:
+ """
+ Encapsulates the candidate selection preferences for downloading
+ and installing files.
+ """
+
+ __slots__ = [
+ "allow_yanked",
+ "allow_all_prereleases",
+ "format_control",
+ "prefer_binary",
+ "ignore_requires_python",
+ ]
+
+ # Don't include an allow_yanked default value to make sure each call
+ # site considers whether yanked releases are allowed. This also causes
+ # that decision to be made explicit in the calling code, which helps
+ # people when reading the code.
+ def __init__(
+ self,
+ allow_yanked: bool,
+ allow_all_prereleases: bool = False,
+ format_control: Optional[FormatControl] = None,
+ prefer_binary: bool = False,
+ ignore_requires_python: Optional[bool] = None,
+ ) -> None:
+ """Create a SelectionPreferences object.
+
+ :param allow_yanked: Whether files marked as yanked (in the sense
+ of PEP 592) are permitted to be candidates for install.
+ :param format_control: A FormatControl object or None. Used to control
+ the selection of source packages / binary packages when consulting
+ the index and links.
+ :param prefer_binary: Whether to prefer an old, but valid, binary
+ dist over a new source dist.
+ :param ignore_requires_python: Whether to ignore incompatible
+ "Requires-Python" values in links. Defaults to False.
+ """
+ if ignore_requires_python is None:
+ ignore_requires_python = False
+
+ self.allow_yanked = allow_yanked
+ self.allow_all_prereleases = allow_all_prereleases
+ self.format_control = format_control
+ self.prefer_binary = prefer_binary
+ self.ignore_requires_python = ignore_requires_python
diff --git a/env/Lib/site-packages/pip/_internal/models/target_python.py b/env/Lib/site-packages/pip/_internal/models/target_python.py
new file mode 100644
index 0000000000000000000000000000000000000000..88925a9fd01a440e6de970bc234c3503b7f09cc1
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/target_python.py
@@ -0,0 +1,121 @@
+import sys
+from typing import List, Optional, Set, Tuple
+
+from pip._vendor.packaging.tags import Tag
+
+from pip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot
+from pip._internal.utils.misc import normalize_version_info
+
+
+class TargetPython:
+ """
+ Encapsulates the properties of a Python interpreter one is targeting
+ for a package install, download, etc.
+ """
+
+ __slots__ = [
+ "_given_py_version_info",
+ "abis",
+ "implementation",
+ "platforms",
+ "py_version",
+ "py_version_info",
+ "_valid_tags",
+ "_valid_tags_set",
+ ]
+
+ def __init__(
+ self,
+ platforms: Optional[List[str]] = None,
+ py_version_info: Optional[Tuple[int, ...]] = None,
+ abis: Optional[List[str]] = None,
+ implementation: Optional[str] = None,
+ ) -> None:
+ """
+ :param platforms: A list of strings or None. If None, searches for
+ packages that are supported by the current system. Otherwise, will
+ find packages that can be built on the platforms passed in. These
+ packages will only be downloaded for distribution: they will
+ not be built locally.
+ :param py_version_info: An optional tuple of ints representing the
+ Python version information to use (e.g. `sys.version_info[:3]`).
+ This can have length 1, 2, or 3 when provided.
+ :param abis: A list of strings or None. This is passed to
+ compatibility_tags.py's get_supported() function as is.
+ :param implementation: A string or None. This is passed to
+ compatibility_tags.py's get_supported() function as is.
+ """
+ # Store the given py_version_info for when we call get_supported().
+ self._given_py_version_info = py_version_info
+
+ if py_version_info is None:
+ py_version_info = sys.version_info[:3]
+ else:
+ py_version_info = normalize_version_info(py_version_info)
+
+ py_version = ".".join(map(str, py_version_info[:2]))
+
+ self.abis = abis
+ self.implementation = implementation
+ self.platforms = platforms
+ self.py_version = py_version
+ self.py_version_info = py_version_info
+
+ # This is used to cache the return value of get_(un)sorted_tags.
+ self._valid_tags: Optional[List[Tag]] = None
+ self._valid_tags_set: Optional[Set[Tag]] = None
+
+ def format_given(self) -> str:
+ """
+ Format the given, non-None attributes for display.
+ """
+ display_version = None
+ if self._given_py_version_info is not None:
+ display_version = ".".join(
+ str(part) for part in self._given_py_version_info
+ )
+
+ key_values = [
+ ("platforms", self.platforms),
+ ("version_info", display_version),
+ ("abis", self.abis),
+ ("implementation", self.implementation),
+ ]
+ return " ".join(
+ f"{key}={value!r}" for key, value in key_values if value is not None
+ )
+
+ def get_sorted_tags(self) -> List[Tag]:
+ """
+ Return the supported PEP 425 tags to check wheel candidates against.
+
+ The tags are returned in order of preference (most preferred first).
+ """
+ if self._valid_tags is None:
+ # Pass versions=None if no py_version_info was given since
+ # versions=None uses special default logic.
+ py_version_info = self._given_py_version_info
+ if py_version_info is None:
+ version = None
+ else:
+ version = version_info_to_nodot(py_version_info)
+
+ tags = get_supported(
+ version=version,
+ platforms=self.platforms,
+ abis=self.abis,
+ impl=self.implementation,
+ )
+ self._valid_tags = tags
+
+ return self._valid_tags
+
+ def get_unsorted_tags(self) -> Set[Tag]:
+ """Exactly the same as get_sorted_tags, but returns a set.
+
+ This is important for performance.
+ """
+ if self._valid_tags_set is None:
+ self._valid_tags_set = set(self.get_sorted_tags())
+
+ return self._valid_tags_set
diff --git a/env/Lib/site-packages/pip/_internal/models/wheel.py b/env/Lib/site-packages/pip/_internal/models/wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea8560089d3df41689f41fe2639aa9f61dd1eace
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/models/wheel.py
@@ -0,0 +1,118 @@
+"""Represents a wheel file and provides access to the various parts of the
+name that have meaning.
+"""
+
+import re
+from typing import Dict, Iterable, List
+
+from pip._vendor.packaging.tags import Tag
+from pip._vendor.packaging.utils import (
+ InvalidWheelFilename as PackagingInvalidWheelName,
+)
+from pip._vendor.packaging.utils import parse_wheel_filename
+
+from pip._internal.exceptions import InvalidWheelFilename
+from pip._internal.utils.deprecation import deprecated
+
+
+class Wheel:
+ """A wheel file"""
+
+ wheel_file_re = re.compile(
+ r"""^(?P(?P[^\s-]+?)-(?P[^\s-]*?))
+ ((-(?P\d[^-]*?))?-(?P[^\s-]+?)-(?P[^\s-]+?)-(?P[^\s-]+?)
+ \.whl|\.dist-info)$""",
+ re.VERBOSE,
+ )
+
+ def __init__(self, filename: str) -> None:
+ """
+ :raises InvalidWheelFilename: when the filename is invalid for a wheel
+ """
+ wheel_info = self.wheel_file_re.match(filename)
+ if not wheel_info:
+ raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.")
+ self.filename = filename
+ self.name = wheel_info.group("name").replace("_", "-")
+ _version = wheel_info.group("ver")
+ if "_" in _version:
+ try:
+ parse_wheel_filename(filename)
+ except PackagingInvalidWheelName as e:
+ deprecated(
+ reason=(
+ f"Wheel filename {filename!r} is not correctly normalised. "
+ "Future versions of pip will raise the following error:\n"
+ f"{e.args[0]}\n\n"
+ ),
+ replacement=(
+ "to rename the wheel to use a correctly normalised "
+ "name (this may require updating the version in "
+ "the project metadata)"
+ ),
+ gone_in="25.1",
+ issue=12938,
+ )
+
+ _version = _version.replace("_", "-")
+
+ self.version = _version
+ self.build_tag = wheel_info.group("build")
+ self.pyversions = wheel_info.group("pyver").split(".")
+ self.abis = wheel_info.group("abi").split(".")
+ self.plats = wheel_info.group("plat").split(".")
+
+ # All the tag combinations from this file
+ self.file_tags = {
+ Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
+ }
+
+ def get_formatted_file_tags(self) -> List[str]:
+ """Return the wheel's tags as a sorted list of strings."""
+ return sorted(str(tag) for tag in self.file_tags)
+
+ def support_index_min(self, tags: List[Tag]) -> int:
+ """Return the lowest index that one of the wheel's file_tag combinations
+ achieves in the given list of supported tags.
+
+ For example, if there are 8 supported tags and one of the file tags
+ is first in the list, then return 0.
+
+ :param tags: the PEP 425 tags to check the wheel against, in order
+ with most preferred first.
+
+ :raises ValueError: If none of the wheel's file tags match one of
+ the supported tags.
+ """
+ try:
+ return next(i for i, t in enumerate(tags) if t in self.file_tags)
+ except StopIteration:
+ raise ValueError()
+
+ def find_most_preferred_tag(
+ self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
+ ) -> int:
+ """Return the priority of the most preferred tag that one of the wheel's file
+ tag combinations achieves in the given list of supported tags using the given
+ tag_to_priority mapping, where lower priorities are more-preferred.
+
+ This is used in place of support_index_min in some cases in order to avoid
+ an expensive linear scan of a large list of tags.
+
+ :param tags: the PEP 425 tags to check the wheel against.
+ :param tag_to_priority: a mapping from tag to priority of that tag, where
+ lower is more preferred.
+
+ :raises ValueError: If none of the wheel's file tags match one of
+ the supported tags.
+ """
+ return min(
+ tag_to_priority[tag] for tag in self.file_tags if tag in tag_to_priority
+ )
+
+ def supported(self, tags: Iterable[Tag]) -> bool:
+ """Return whether the wheel is compatible with one of the given tags.
+
+ :param tags: the PEP 425 tags to check the wheel against.
+ """
+ return not self.file_tags.isdisjoint(tags)
diff --git a/env/Lib/site-packages/pip/_internal/network/__init__.py b/env/Lib/site-packages/pip/_internal/network/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b51bde91b2e5b4e557ed9b70fc113843cc3d49ae
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/__init__.py
@@ -0,0 +1,2 @@
+"""Contains purely network-related utilities.
+"""
diff --git a/env/Lib/site-packages/pip/_internal/network/auth.py b/env/Lib/site-packages/pip/_internal/network/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a2606ed080463dc36d34d5a4ff33b8937839823
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/auth.py
@@ -0,0 +1,566 @@
+"""Network Authentication Helpers
+
+Contains interface (MultiDomainBasicAuth) and associated glue code for
+providing credentials in the context of network requests.
+"""
+
+import logging
+import os
+import shutil
+import subprocess
+import sysconfig
+import typing
+import urllib.parse
+from abc import ABC, abstractmethod
+from functools import lru_cache
+from os.path import commonprefix
+from pathlib import Path
+from typing import Any, Dict, List, NamedTuple, Optional, Tuple
+
+from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
+from pip._vendor.requests.models import Request, Response
+from pip._vendor.requests.utils import get_netrc_auth
+
+from pip._internal.utils.logging import getLogger
+from pip._internal.utils.misc import (
+ ask,
+ ask_input,
+ ask_password,
+ remove_auth_from_url,
+ split_auth_netloc_from_url,
+)
+from pip._internal.vcs.versioncontrol import AuthInfo
+
+logger = getLogger(__name__)
+
+KEYRING_DISABLED = False
+
+
+class Credentials(NamedTuple):
+ url: str
+ username: str
+ password: str
+
+
+class KeyRingBaseProvider(ABC):
+ """Keyring base provider interface"""
+
+ has_keyring: bool
+
+ @abstractmethod
+ def get_auth_info(
+ self, url: str, username: Optional[str]
+ ) -> Optional[AuthInfo]: ...
+
+ @abstractmethod
+ def save_auth_info(self, url: str, username: str, password: str) -> None: ...
+
+
+class KeyRingNullProvider(KeyRingBaseProvider):
+ """Keyring null provider"""
+
+ has_keyring = False
+
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ return None
+
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ return None
+
+
+class KeyRingPythonProvider(KeyRingBaseProvider):
+ """Keyring interface which uses locally imported `keyring`"""
+
+ has_keyring = True
+
+ def __init__(self) -> None:
+ import keyring
+
+ self.keyring = keyring
+
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ # Support keyring's get_credential interface which supports getting
+ # credentials without a username. This is only available for
+ # keyring>=15.2.0.
+ if hasattr(self.keyring, "get_credential"):
+ logger.debug("Getting credentials from keyring for %s", url)
+ cred = self.keyring.get_credential(url, username)
+ if cred is not None:
+ return cred.username, cred.password
+ return None
+
+ if username is not None:
+ logger.debug("Getting password from keyring for %s", url)
+ password = self.keyring.get_password(url, username)
+ if password:
+ return username, password
+ return None
+
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ self.keyring.set_password(url, username, password)
+
+
+class KeyRingCliProvider(KeyRingBaseProvider):
+ """Provider which uses `keyring` cli
+
+ Instead of calling the keyring package installed alongside pip
+ we call keyring on the command line which will enable pip to
+ use which ever installation of keyring is available first in
+ PATH.
+ """
+
+ has_keyring = True
+
+ def __init__(self, cmd: str) -> None:
+ self.keyring = cmd
+
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ # This is the default implementation of keyring.get_credential
+ # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
+ if username is not None:
+ password = self._get_password(url, username)
+ if password is not None:
+ return username, password
+ return None
+
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ return self._set_password(url, username, password)
+
+ def _get_password(self, service_name: str, username: str) -> Optional[str]:
+ """Mirror the implementation of keyring.get_password using cli"""
+ if self.keyring is None:
+ return None
+
+ cmd = [self.keyring, "get", service_name, username]
+ env = os.environ.copy()
+ env["PYTHONIOENCODING"] = "utf-8"
+ res = subprocess.run(
+ cmd,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ env=env,
+ )
+ if res.returncode:
+ return None
+ return res.stdout.decode("utf-8").strip(os.linesep)
+
+ def _set_password(self, service_name: str, username: str, password: str) -> None:
+ """Mirror the implementation of keyring.set_password using cli"""
+ if self.keyring is None:
+ return None
+ env = os.environ.copy()
+ env["PYTHONIOENCODING"] = "utf-8"
+ subprocess.run(
+ [self.keyring, "set", service_name, username],
+ input=f"{password}{os.linesep}".encode(),
+ env=env,
+ check=True,
+ )
+ return None
+
+
+@lru_cache(maxsize=None)
+def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
+ logger.verbose("Keyring provider requested: %s", provider)
+
+ # keyring has previously failed and been disabled
+ if KEYRING_DISABLED:
+ provider = "disabled"
+ if provider in ["import", "auto"]:
+ try:
+ impl = KeyRingPythonProvider()
+ logger.verbose("Keyring provider set: import")
+ return impl
+ except ImportError:
+ pass
+ except Exception as exc:
+ # In the event of an unexpected exception
+ # we should warn the user
+ msg = "Installed copy of keyring fails with exception %s"
+ if provider == "auto":
+ msg = msg + ", trying to find a keyring executable as a fallback"
+ logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
+ if provider in ["subprocess", "auto"]:
+ cli = shutil.which("keyring")
+ if cli and cli.startswith(sysconfig.get_path("scripts")):
+ # all code within this function is stolen from shutil.which implementation
+ @typing.no_type_check
+ def PATH_as_shutil_which_determines_it() -> str:
+ path = os.environ.get("PATH", None)
+ if path is None:
+ try:
+ path = os.confstr("CS_PATH")
+ except (AttributeError, ValueError):
+ # os.confstr() or CS_PATH is not available
+ path = os.defpath
+ # bpo-35755: Don't use os.defpath if the PATH environment variable is
+ # set to an empty string
+
+ return path
+
+ scripts = Path(sysconfig.get_path("scripts"))
+
+ paths = []
+ for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
+ p = Path(path)
+ try:
+ if not p.samefile(scripts):
+ paths.append(path)
+ except FileNotFoundError:
+ pass
+
+ path = os.pathsep.join(paths)
+
+ cli = shutil.which("keyring", path=path)
+
+ if cli:
+ logger.verbose("Keyring provider set: subprocess with executable %s", cli)
+ return KeyRingCliProvider(cli)
+
+ logger.verbose("Keyring provider set: disabled")
+ return KeyRingNullProvider()
+
+
+class MultiDomainBasicAuth(AuthBase):
+ def __init__(
+ self,
+ prompting: bool = True,
+ index_urls: Optional[List[str]] = None,
+ keyring_provider: str = "auto",
+ ) -> None:
+ self.prompting = prompting
+ self.index_urls = index_urls
+ self.keyring_provider = keyring_provider # type: ignore[assignment]
+ self.passwords: Dict[str, AuthInfo] = {}
+ # When the user is prompted to enter credentials and keyring is
+ # available, we will offer to save them. If the user accepts,
+ # this value is set to the credentials they entered. After the
+ # request authenticates, the caller should call
+ # ``save_credentials`` to save these.
+ self._credentials_to_save: Optional[Credentials] = None
+
+ @property
+ def keyring_provider(self) -> KeyRingBaseProvider:
+ return get_keyring_provider(self._keyring_provider)
+
+ @keyring_provider.setter
+ def keyring_provider(self, provider: str) -> None:
+ # The free function get_keyring_provider has been decorated with
+ # functools.cache. If an exception occurs in get_keyring_auth that
+ # cache will be cleared and keyring disabled, take that into account
+ # if you want to remove this indirection.
+ self._keyring_provider = provider
+
+ @property
+ def use_keyring(self) -> bool:
+ # We won't use keyring when --no-input is passed unless
+ # a specific provider is requested because it might require
+ # user interaction
+ return self.prompting or self._keyring_provider not in ["auto", "disabled"]
+
+ def _get_keyring_auth(
+ self,
+ url: Optional[str],
+ username: Optional[str],
+ ) -> Optional[AuthInfo]:
+ """Return the tuple auth for a given url from keyring."""
+ # Do nothing if no url was provided
+ if not url:
+ return None
+
+ try:
+ return self.keyring_provider.get_auth_info(url, username)
+ except Exception as exc:
+ # Log the full exception (with stacktrace) at debug, so it'll only
+ # show up when running in verbose mode.
+ logger.debug("Keyring is skipped due to an exception", exc_info=True)
+ # Always log a shortened version of the exception.
+ logger.warning(
+ "Keyring is skipped due to an exception: %s",
+ str(exc),
+ )
+ global KEYRING_DISABLED
+ KEYRING_DISABLED = True
+ get_keyring_provider.cache_clear()
+ return None
+
+ def _get_index_url(self, url: str) -> Optional[str]:
+ """Return the original index URL matching the requested URL.
+
+ Cached or dynamically generated credentials may work against
+ the original index URL rather than just the netloc.
+
+ The provided url should have had its username and password
+ removed already. If the original index url had credentials then
+ they will be included in the return value.
+
+ Returns None if no matching index was found, or if --no-index
+ was specified by the user.
+ """
+ if not url or not self.index_urls:
+ return None
+
+ url = remove_auth_from_url(url).rstrip("/") + "/"
+ parsed_url = urllib.parse.urlsplit(url)
+
+ candidates = []
+
+ for index in self.index_urls:
+ index = index.rstrip("/") + "/"
+ parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
+ if parsed_url == parsed_index:
+ return index
+
+ if parsed_url.netloc != parsed_index.netloc:
+ continue
+
+ candidate = urllib.parse.urlsplit(index)
+ candidates.append(candidate)
+
+ if not candidates:
+ return None
+
+ candidates.sort(
+ reverse=True,
+ key=lambda candidate: commonprefix(
+ [
+ parsed_url.path,
+ candidate.path,
+ ]
+ ).rfind("/"),
+ )
+
+ return urllib.parse.urlunsplit(candidates[0])
+
+ def _get_new_credentials(
+ self,
+ original_url: str,
+ *,
+ allow_netrc: bool = True,
+ allow_keyring: bool = False,
+ ) -> AuthInfo:
+ """Find and return credentials for the specified URL."""
+ # Split the credentials and netloc from the url.
+ url, netloc, url_user_password = split_auth_netloc_from_url(
+ original_url,
+ )
+
+ # Start with the credentials embedded in the url
+ username, password = url_user_password
+ if username is not None and password is not None:
+ logger.debug("Found credentials in url for %s", netloc)
+ return url_user_password
+
+ # Find a matching index url for this request
+ index_url = self._get_index_url(url)
+ if index_url:
+ # Split the credentials from the url.
+ index_info = split_auth_netloc_from_url(index_url)
+ if index_info:
+ index_url, _, index_url_user_password = index_info
+ logger.debug("Found index url %s", index_url)
+
+ # If an index URL was found, try its embedded credentials
+ if index_url and index_url_user_password[0] is not None:
+ username, password = index_url_user_password
+ if username is not None and password is not None:
+ logger.debug("Found credentials in index url for %s", netloc)
+ return index_url_user_password
+
+ # Get creds from netrc if we still don't have them
+ if allow_netrc:
+ netrc_auth = get_netrc_auth(original_url)
+ if netrc_auth:
+ logger.debug("Found credentials in netrc for %s", netloc)
+ return netrc_auth
+
+ # If we don't have a password and keyring is available, use it.
+ if allow_keyring:
+ # The index url is more specific than the netloc, so try it first
+ # fmt: off
+ kr_auth = (
+ self._get_keyring_auth(index_url, username) or
+ self._get_keyring_auth(netloc, username)
+ )
+ # fmt: on
+ if kr_auth:
+ logger.debug("Found credentials in keyring for %s", netloc)
+ return kr_auth
+
+ return username, password
+
+ def _get_url_and_credentials(
+ self, original_url: str
+ ) -> Tuple[str, Optional[str], Optional[str]]:
+ """Return the credentials to use for the provided URL.
+
+ If allowed, netrc and keyring may be used to obtain the
+ correct credentials.
+
+ Returns (url_without_credentials, username, password). Note
+ that even if the original URL contains credentials, this
+ function may return a different username and password.
+ """
+ url, netloc, _ = split_auth_netloc_from_url(original_url)
+
+ # Try to get credentials from original url
+ username, password = self._get_new_credentials(original_url)
+
+ # If credentials not found, use any stored credentials for this netloc.
+ # Do this if either the username or the password is missing.
+ # This accounts for the situation in which the user has specified
+ # the username in the index url, but the password comes from keyring.
+ if (username is None or password is None) and netloc in self.passwords:
+ un, pw = self.passwords[netloc]
+ # It is possible that the cached credentials are for a different username,
+ # in which case the cache should be ignored.
+ if username is None or username == un:
+ username, password = un, pw
+
+ if username is not None or password is not None:
+ # Convert the username and password if they're None, so that
+ # this netloc will show up as "cached" in the conditional above.
+ # Further, HTTPBasicAuth doesn't accept None, so it makes sense to
+ # cache the value that is going to be used.
+ username = username or ""
+ password = password or ""
+
+ # Store any acquired credentials.
+ self.passwords[netloc] = (username, password)
+
+ assert (
+ # Credentials were found
+ (username is not None and password is not None)
+ # Credentials were not found
+ or (username is None and password is None)
+ ), f"Could not load credentials from url: {original_url}"
+
+ return url, username, password
+
+ def __call__(self, req: Request) -> Request:
+ # Get credentials for this request
+ url, username, password = self._get_url_and_credentials(req.url)
+
+ # Set the url of the request to the url without any credentials
+ req.url = url
+
+ if username is not None and password is not None:
+ # Send the basic auth with this request
+ req = HTTPBasicAuth(username, password)(req)
+
+ # Attach a hook to handle 401 responses
+ req.register_hook("response", self.handle_401)
+
+ return req
+
+ # Factored out to allow for easy patching in tests
+ def _prompt_for_password(
+ self, netloc: str
+ ) -> Tuple[Optional[str], Optional[str], bool]:
+ username = ask_input(f"User for {netloc}: ") if self.prompting else None
+ if not username:
+ return None, None, False
+ if self.use_keyring:
+ auth = self._get_keyring_auth(netloc, username)
+ if auth and auth[0] is not None and auth[1] is not None:
+ return auth[0], auth[1], False
+ password = ask_password("Password: ")
+ return username, password, True
+
+ # Factored out to allow for easy patching in tests
+ def _should_save_password_to_keyring(self) -> bool:
+ if (
+ not self.prompting
+ or not self.use_keyring
+ or not self.keyring_provider.has_keyring
+ ):
+ return False
+ return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
+
+ def handle_401(self, resp: Response, **kwargs: Any) -> Response:
+ # We only care about 401 responses, anything else we want to just
+ # pass through the actual response
+ if resp.status_code != 401:
+ return resp
+
+ username, password = None, None
+
+ # Query the keyring for credentials:
+ if self.use_keyring:
+ username, password = self._get_new_credentials(
+ resp.url,
+ allow_netrc=False,
+ allow_keyring=True,
+ )
+
+ # We are not able to prompt the user so simply return the response
+ if not self.prompting and not username and not password:
+ return resp
+
+ parsed = urllib.parse.urlparse(resp.url)
+
+ # Prompt the user for a new username and password
+ save = False
+ if not username and not password:
+ username, password, save = self._prompt_for_password(parsed.netloc)
+
+ # Store the new username and password to use for future requests
+ self._credentials_to_save = None
+ if username is not None and password is not None:
+ self.passwords[parsed.netloc] = (username, password)
+
+ # Prompt to save the password to keyring
+ if save and self._should_save_password_to_keyring():
+ self._credentials_to_save = Credentials(
+ url=parsed.netloc,
+ username=username,
+ password=password,
+ )
+
+ # Consume content and release the original connection to allow our new
+ # request to reuse the same one.
+ # The result of the assignment isn't used, it's just needed to consume
+ # the content.
+ _ = resp.content
+ resp.raw.release_conn()
+
+ # Add our new username and password to the request
+ req = HTTPBasicAuth(username or "", password or "")(resp.request)
+ req.register_hook("response", self.warn_on_401)
+
+ # On successful request, save the credentials that were used to
+ # keyring. (Note that if the user responded "no" above, this member
+ # is not set and nothing will be saved.)
+ if self._credentials_to_save:
+ req.register_hook("response", self.save_credentials)
+
+ # Send our new request
+ new_resp = resp.connection.send(req, **kwargs)
+ new_resp.history.append(resp)
+
+ return new_resp
+
+ def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
+ """Response callback to warn about incorrect credentials."""
+ if resp.status_code == 401:
+ logger.warning(
+ "401 Error, Credentials not correct for %s",
+ resp.request.url,
+ )
+
+ def save_credentials(self, resp: Response, **kwargs: Any) -> None:
+ """Response callback to save credentials on success."""
+ assert (
+ self.keyring_provider.has_keyring
+ ), "should never reach here without keyring"
+
+ creds = self._credentials_to_save
+ self._credentials_to_save = None
+ if creds and resp.status_code < 400:
+ try:
+ logger.info("Saving credentials to keyring")
+ self.keyring_provider.save_auth_info(
+ creds.url, creds.username, creds.password
+ )
+ except Exception:
+ logger.exception("Failed to save credentials")
diff --git a/env/Lib/site-packages/pip/_internal/network/cache.py b/env/Lib/site-packages/pip/_internal/network/cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d0fb545dc2b3c8865a6b57b12f0de92bd22dccf
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/cache.py
@@ -0,0 +1,106 @@
+"""HTTP cache implementation.
+"""
+
+import os
+from contextlib import contextmanager
+from datetime import datetime
+from typing import BinaryIO, Generator, Optional, Union
+
+from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
+from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
+from pip._vendor.requests.models import Response
+
+from pip._internal.utils.filesystem import adjacent_tmp_file, replace
+from pip._internal.utils.misc import ensure_dir
+
+
+def is_from_cache(response: Response) -> bool:
+ return getattr(response, "from_cache", False)
+
+
+@contextmanager
+def suppressed_cache_errors() -> Generator[None, None, None]:
+ """If we can't access the cache then we can just skip caching and process
+ requests as if caching wasn't enabled.
+ """
+ try:
+ yield
+ except OSError:
+ pass
+
+
+class SafeFileCache(SeparateBodyBaseCache):
+ """
+ A file based cache which is safe to use even when the target directory may
+ not be accessible or writable.
+
+ There is a race condition when two processes try to write and/or read the
+ same entry at the same time, since each entry consists of two separate
+ files (https://github.com/psf/cachecontrol/issues/324). We therefore have
+ additional logic that makes sure that both files to be present before
+ returning an entry; this fixes the read side of the race condition.
+
+ For the write side, we assume that the server will only ever return the
+ same data for the same URL, which ought to be the case for files pip is
+ downloading. PyPI does not have a mechanism to swap out a wheel for
+ another wheel, for example. If this assumption is not true, the
+ CacheControl issue will need to be fixed.
+ """
+
+ def __init__(self, directory: str) -> None:
+ assert directory is not None, "Cache directory must not be None."
+ super().__init__()
+ self.directory = directory
+
+ def _get_cache_path(self, name: str) -> str:
+ # From cachecontrol.caches.file_cache.FileCache._fn, brought into our
+ # class for backwards-compatibility and to avoid using a non-public
+ # method.
+ hashed = SeparateBodyFileCache.encode(name)
+ parts = list(hashed[:5]) + [hashed]
+ return os.path.join(self.directory, *parts)
+
+ def get(self, key: str) -> Optional[bytes]:
+ # The cache entry is only valid if both metadata and body exist.
+ metadata_path = self._get_cache_path(key)
+ body_path = metadata_path + ".body"
+ if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
+ return None
+ with suppressed_cache_errors():
+ with open(metadata_path, "rb") as f:
+ return f.read()
+
+ def _write(self, path: str, data: bytes) -> None:
+ with suppressed_cache_errors():
+ ensure_dir(os.path.dirname(path))
+
+ with adjacent_tmp_file(path) as f:
+ f.write(data)
+
+ replace(f.name, path)
+
+ def set(
+ self, key: str, value: bytes, expires: Union[int, datetime, None] = None
+ ) -> None:
+ path = self._get_cache_path(key)
+ self._write(path, value)
+
+ def delete(self, key: str) -> None:
+ path = self._get_cache_path(key)
+ with suppressed_cache_errors():
+ os.remove(path)
+ with suppressed_cache_errors():
+ os.remove(path + ".body")
+
+ def get_body(self, key: str) -> Optional[BinaryIO]:
+ # The cache entry is only valid if both metadata and body exist.
+ metadata_path = self._get_cache_path(key)
+ body_path = metadata_path + ".body"
+ if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
+ return None
+ with suppressed_cache_errors():
+ return open(body_path, "rb")
+
+ def set_body(self, key: str, body: bytes) -> None:
+ path = self._get_cache_path(key) + ".body"
+ self._write(path, body)
diff --git a/env/Lib/site-packages/pip/_internal/network/download.py b/env/Lib/site-packages/pip/_internal/network/download.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c3bce3d2fd0cd0678ab4af066dea803b7064067
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/download.py
@@ -0,0 +1,187 @@
+"""Download files with progress indicators.
+"""
+
+import email.message
+import logging
+import mimetypes
+import os
+from typing import Iterable, Optional, Tuple
+
+from pip._vendor.requests.models import Response
+
+from pip._internal.cli.progress_bars import get_download_progress_renderer
+from pip._internal.exceptions import NetworkConnectionError
+from pip._internal.models.index import PyPI
+from pip._internal.models.link import Link
+from pip._internal.network.cache import is_from_cache
+from pip._internal.network.session import PipSession
+from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
+from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext
+
+logger = logging.getLogger(__name__)
+
+
+def _get_http_response_size(resp: Response) -> Optional[int]:
+ try:
+ return int(resp.headers["content-length"])
+ except (ValueError, KeyError, TypeError):
+ return None
+
+
+def _prepare_download(
+ resp: Response,
+ link: Link,
+ progress_bar: str,
+) -> Iterable[bytes]:
+ total_length = _get_http_response_size(resp)
+
+ if link.netloc == PyPI.file_storage_domain:
+ url = link.show_url
+ else:
+ url = link.url_without_fragment
+
+ logged_url = redact_auth_from_url(url)
+
+ if total_length:
+ logged_url = f"{logged_url} ({format_size(total_length)})"
+
+ if is_from_cache(resp):
+ logger.info("Using cached %s", logged_url)
+ else:
+ logger.info("Downloading %s", logged_url)
+
+ if logger.getEffectiveLevel() > logging.INFO:
+ show_progress = False
+ elif is_from_cache(resp):
+ show_progress = False
+ elif not total_length:
+ show_progress = True
+ elif total_length > (512 * 1024):
+ show_progress = True
+ else:
+ show_progress = False
+
+ chunks = response_chunks(resp)
+
+ if not show_progress:
+ return chunks
+
+ renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
+ return renderer(chunks)
+
+
+def sanitize_content_filename(filename: str) -> str:
+ """
+ Sanitize the "filename" value from a Content-Disposition header.
+ """
+ return os.path.basename(filename)
+
+
+def parse_content_disposition(content_disposition: str, default_filename: str) -> str:
+ """
+ Parse the "filename" value from a Content-Disposition header, and
+ return the default filename if the result is empty.
+ """
+ m = email.message.Message()
+ m["content-type"] = content_disposition
+ filename = m.get_param("filename")
+ if filename:
+ # We need to sanitize the filename to prevent directory traversal
+ # in case the filename contains ".." path parts.
+ filename = sanitize_content_filename(str(filename))
+ return filename or default_filename
+
+
+def _get_http_response_filename(resp: Response, link: Link) -> str:
+ """Get an ideal filename from the given HTTP response, falling back to
+ the link filename if not provided.
+ """
+ filename = link.filename # fallback
+ # Have a look at the Content-Disposition header for a better guess
+ content_disposition = resp.headers.get("content-disposition")
+ if content_disposition:
+ filename = parse_content_disposition(content_disposition, filename)
+ ext: Optional[str] = splitext(filename)[1]
+ if not ext:
+ ext = mimetypes.guess_extension(resp.headers.get("content-type", ""))
+ if ext:
+ filename += ext
+ if not ext and link.url != resp.url:
+ ext = os.path.splitext(resp.url)[1]
+ if ext:
+ filename += ext
+ return filename
+
+
+def _http_get_download(session: PipSession, link: Link) -> Response:
+ target_url = link.url.split("#", 1)[0]
+ resp = session.get(target_url, headers=HEADERS, stream=True)
+ raise_for_status(resp)
+ return resp
+
+
+class Downloader:
+ def __init__(
+ self,
+ session: PipSession,
+ progress_bar: str,
+ ) -> None:
+ self._session = session
+ self._progress_bar = progress_bar
+
+ def __call__(self, link: Link, location: str) -> Tuple[str, str]:
+ """Download the file given by link into location."""
+ try:
+ resp = _http_get_download(self._session, link)
+ except NetworkConnectionError as e:
+ assert e.response is not None
+ logger.critical(
+ "HTTP error %s while getting %s", e.response.status_code, link
+ )
+ raise
+
+ filename = _get_http_response_filename(resp, link)
+ filepath = os.path.join(location, filename)
+
+ chunks = _prepare_download(resp, link, self._progress_bar)
+ with open(filepath, "wb") as content_file:
+ for chunk in chunks:
+ content_file.write(chunk)
+ content_type = resp.headers.get("Content-Type", "")
+ return filepath, content_type
+
+
+class BatchDownloader:
+ def __init__(
+ self,
+ session: PipSession,
+ progress_bar: str,
+ ) -> None:
+ self._session = session
+ self._progress_bar = progress_bar
+
+ def __call__(
+ self, links: Iterable[Link], location: str
+ ) -> Iterable[Tuple[Link, Tuple[str, str]]]:
+ """Download the files given by links into location."""
+ for link in links:
+ try:
+ resp = _http_get_download(self._session, link)
+ except NetworkConnectionError as e:
+ assert e.response is not None
+ logger.critical(
+ "HTTP error %s while getting %s",
+ e.response.status_code,
+ link,
+ )
+ raise
+
+ filename = _get_http_response_filename(resp, link)
+ filepath = os.path.join(location, filename)
+
+ chunks = _prepare_download(resp, link, self._progress_bar)
+ with open(filepath, "wb") as content_file:
+ for chunk in chunks:
+ content_file.write(chunk)
+ content_type = resp.headers.get("Content-Type", "")
+ yield link, (filepath, content_type)
diff --git a/env/Lib/site-packages/pip/_internal/network/lazy_wheel.py b/env/Lib/site-packages/pip/_internal/network/lazy_wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..03f883c1fc46d374e2b4556c361e4d173aacf027
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/lazy_wheel.py
@@ -0,0 +1,210 @@
+"""Lazy ZIP over HTTP"""
+
+__all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
+
+from bisect import bisect_left, bisect_right
+from contextlib import contextmanager
+from tempfile import NamedTemporaryFile
+from typing import Any, Dict, Generator, List, Optional, Tuple
+from zipfile import BadZipFile, ZipFile
+
+from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
+
+from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
+from pip._internal.network.session import PipSession
+from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
+
+
+class HTTPRangeRequestUnsupported(Exception):
+ pass
+
+
+def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
+ """Return a distribution object from the given wheel URL.
+
+ This uses HTTP range requests to only fetch the portion of the wheel
+ containing metadata, just enough for the object to be constructed.
+ If such requests are not supported, HTTPRangeRequestUnsupported
+ is raised.
+ """
+ with LazyZipOverHTTP(url, session) as zf:
+ # For read-only ZIP files, ZipFile only needs methods read,
+ # seek, seekable and tell, not the whole IO protocol.
+ wheel = MemoryWheel(zf.name, zf) # type: ignore
+ # After context manager exit, wheel.name
+ # is an invalid file by intention.
+ return get_wheel_distribution(wheel, canonicalize_name(name))
+
+
+class LazyZipOverHTTP:
+ """File-like object mapped to a ZIP file over HTTP.
+
+ This uses HTTP range requests to lazily fetch the file's content,
+ which is supposed to be fed to ZipFile. If such requests are not
+ supported by the server, raise HTTPRangeRequestUnsupported
+ during initialization.
+ """
+
+ def __init__(
+ self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE
+ ) -> None:
+ head = session.head(url, headers=HEADERS)
+ raise_for_status(head)
+ assert head.status_code == 200
+ self._session, self._url, self._chunk_size = session, url, chunk_size
+ self._length = int(head.headers["Content-Length"])
+ self._file = NamedTemporaryFile()
+ self.truncate(self._length)
+ self._left: List[int] = []
+ self._right: List[int] = []
+ if "bytes" not in head.headers.get("Accept-Ranges", "none"):
+ raise HTTPRangeRequestUnsupported("range request is not supported")
+ self._check_zip()
+
+ @property
+ def mode(self) -> str:
+ """Opening mode, which is always rb."""
+ return "rb"
+
+ @property
+ def name(self) -> str:
+ """Path to the underlying file."""
+ return self._file.name
+
+ def seekable(self) -> bool:
+ """Return whether random access is supported, which is True."""
+ return True
+
+ def close(self) -> None:
+ """Close the file."""
+ self._file.close()
+
+ @property
+ def closed(self) -> bool:
+ """Whether the file is closed."""
+ return self._file.closed
+
+ def read(self, size: int = -1) -> bytes:
+ """Read up to size bytes from the object and return them.
+
+ As a convenience, if size is unspecified or -1,
+ all bytes until EOF are returned. Fewer than
+ size bytes may be returned if EOF is reached.
+ """
+ download_size = max(size, self._chunk_size)
+ start, length = self.tell(), self._length
+ stop = length if size < 0 else min(start + download_size, length)
+ start = max(0, stop - download_size)
+ self._download(start, stop - 1)
+ return self._file.read(size)
+
+ def readable(self) -> bool:
+ """Return whether the file is readable, which is True."""
+ return True
+
+ def seek(self, offset: int, whence: int = 0) -> int:
+ """Change stream position and return the new absolute position.
+
+ Seek to offset relative position indicated by whence:
+ * 0: Start of stream (the default). pos should be >= 0;
+ * 1: Current position - pos may be negative;
+ * 2: End of stream - pos usually negative.
+ """
+ return self._file.seek(offset, whence)
+
+ def tell(self) -> int:
+ """Return the current position."""
+ return self._file.tell()
+
+ def truncate(self, size: Optional[int] = None) -> int:
+ """Resize the stream to the given size in bytes.
+
+ If size is unspecified resize to the current position.
+ The current stream position isn't changed.
+
+ Return the new file size.
+ """
+ return self._file.truncate(size)
+
+ def writable(self) -> bool:
+ """Return False."""
+ return False
+
+ def __enter__(self) -> "LazyZipOverHTTP":
+ self._file.__enter__()
+ return self
+
+ def __exit__(self, *exc: Any) -> None:
+ self._file.__exit__(*exc)
+
+ @contextmanager
+ def _stay(self) -> Generator[None, None, None]:
+ """Return a context manager keeping the position.
+
+ At the end of the block, seek back to original position.
+ """
+ pos = self.tell()
+ try:
+ yield
+ finally:
+ self.seek(pos)
+
+ def _check_zip(self) -> None:
+ """Check and download until the file is a valid ZIP."""
+ end = self._length - 1
+ for start in reversed(range(0, end, self._chunk_size)):
+ self._download(start, end)
+ with self._stay():
+ try:
+ # For read-only ZIP files, ZipFile only needs
+ # methods read, seek, seekable and tell.
+ ZipFile(self)
+ except BadZipFile:
+ pass
+ else:
+ break
+
+ def _stream_response(
+ self, start: int, end: int, base_headers: Dict[str, str] = HEADERS
+ ) -> Response:
+ """Return HTTP response to a range request from start to end."""
+ headers = base_headers.copy()
+ headers["Range"] = f"bytes={start}-{end}"
+ # TODO: Get range requests to be correctly cached
+ headers["Cache-Control"] = "no-cache"
+ return self._session.get(self._url, headers=headers, stream=True)
+
+ def _merge(
+ self, start: int, end: int, left: int, right: int
+ ) -> Generator[Tuple[int, int], None, None]:
+ """Return a generator of intervals to be fetched.
+
+ Args:
+ start (int): Start of needed interval
+ end (int): End of needed interval
+ left (int): Index of first overlapping downloaded data
+ right (int): Index after last overlapping downloaded data
+ """
+ lslice, rslice = self._left[left:right], self._right[left:right]
+ i = start = min([start] + lslice[:1])
+ end = max([end] + rslice[-1:])
+ for j, k in zip(lslice, rslice):
+ if j > i:
+ yield i, j - 1
+ i = k + 1
+ if i <= end:
+ yield i, end
+ self._left[left:right], self._right[left:right] = [start], [end]
+
+ def _download(self, start: int, end: int) -> None:
+ """Download bytes from start to end inclusively."""
+ with self._stay():
+ left = bisect_left(self._right, start)
+ right = bisect_right(self._left, end)
+ for start, end in self._merge(start, end, left, right):
+ response = self._stream_response(start, end)
+ response.raise_for_status()
+ self.seek(start)
+ for chunk in response_chunks(response, self._chunk_size):
+ self._file.write(chunk)
diff --git a/env/Lib/site-packages/pip/_internal/network/session.py b/env/Lib/site-packages/pip/_internal/network/session.py
new file mode 100644
index 0000000000000000000000000000000000000000..1765b4f6bd74cb10ca7af93f819c6a6f063f3a84
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/session.py
@@ -0,0 +1,522 @@
+"""PipSession and supporting code, containing all pip-specific
+network request configuration and behavior.
+"""
+
+import email.utils
+import functools
+import io
+import ipaddress
+import json
+import logging
+import mimetypes
+import os
+import platform
+import shutil
+import subprocess
+import sys
+import urllib.parse
+import warnings
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ Generator,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+)
+
+from pip._vendor import requests, urllib3
+from pip._vendor.cachecontrol import CacheControlAdapter as _BaseCacheControlAdapter
+from pip._vendor.requests.adapters import DEFAULT_POOLBLOCK, BaseAdapter
+from pip._vendor.requests.adapters import HTTPAdapter as _BaseHTTPAdapter
+from pip._vendor.requests.models import PreparedRequest, Response
+from pip._vendor.requests.structures import CaseInsensitiveDict
+from pip._vendor.urllib3.connectionpool import ConnectionPool
+from pip._vendor.urllib3.exceptions import InsecureRequestWarning
+
+from pip import __version__
+from pip._internal.metadata import get_default_environment
+from pip._internal.models.link import Link
+from pip._internal.network.auth import MultiDomainBasicAuth
+from pip._internal.network.cache import SafeFileCache
+
+# Import ssl from compat so the initial import occurs in only one place.
+from pip._internal.utils.compat import has_tls
+from pip._internal.utils.glibc import libc_ver
+from pip._internal.utils.misc import build_url_from_netloc, parse_netloc
+from pip._internal.utils.urls import url_to_path
+
+if TYPE_CHECKING:
+ from ssl import SSLContext
+
+ from pip._vendor.urllib3.poolmanager import PoolManager
+
+
+logger = logging.getLogger(__name__)
+
+SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
+
+
+# Ignore warning raised when using --trusted-host.
+warnings.filterwarnings("ignore", category=InsecureRequestWarning)
+
+
+SECURE_ORIGINS: List[SecureOrigin] = [
+ # protocol, hostname, port
+ # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
+ ("https", "*", "*"),
+ ("*", "localhost", "*"),
+ ("*", "127.0.0.0/8", "*"),
+ ("*", "::1/128", "*"),
+ ("file", "*", None),
+ # ssh is always secure.
+ ("ssh", "*", "*"),
+]
+
+
+# These are environment variables present when running under various
+# CI systems. For each variable, some CI systems that use the variable
+# are indicated. The collection was chosen so that for each of a number
+# of popular systems, at least one of the environment variables is used.
+# This list is used to provide some indication of and lower bound for
+# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive.
+# For more background, see: https://github.com/pypa/pip/issues/5499
+CI_ENVIRONMENT_VARIABLES = (
+ # Azure Pipelines
+ "BUILD_BUILDID",
+ # Jenkins
+ "BUILD_ID",
+ # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
+ "CI",
+ # Explicit environment variable.
+ "PIP_IS_CI",
+)
+
+
+def looks_like_ci() -> bool:
+ """
+ Return whether it looks like pip is running under CI.
+ """
+ # We don't use the method of checking for a tty (e.g. using isatty())
+ # because some CI systems mimic a tty (e.g. Travis CI). Thus that
+ # method doesn't provide definitive information in either direction.
+ return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)
+
+
+@functools.lru_cache(maxsize=1)
+def user_agent() -> str:
+ """
+ Return a string representing the user agent.
+ """
+ data: Dict[str, Any] = {
+ "installer": {"name": "pip", "version": __version__},
+ "python": platform.python_version(),
+ "implementation": {
+ "name": platform.python_implementation(),
+ },
+ }
+
+ if data["implementation"]["name"] == "CPython":
+ data["implementation"]["version"] = platform.python_version()
+ elif data["implementation"]["name"] == "PyPy":
+ pypy_version_info = sys.pypy_version_info # type: ignore
+ if pypy_version_info.releaselevel == "final":
+ pypy_version_info = pypy_version_info[:3]
+ data["implementation"]["version"] = ".".join(
+ [str(x) for x in pypy_version_info]
+ )
+ elif data["implementation"]["name"] == "Jython":
+ # Complete Guess
+ data["implementation"]["version"] = platform.python_version()
+ elif data["implementation"]["name"] == "IronPython":
+ # Complete Guess
+ data["implementation"]["version"] = platform.python_version()
+
+ if sys.platform.startswith("linux"):
+ from pip._vendor import distro
+
+ linux_distribution = distro.name(), distro.version(), distro.codename()
+ distro_infos: Dict[str, Any] = dict(
+ filter(
+ lambda x: x[1],
+ zip(["name", "version", "id"], linux_distribution),
+ )
+ )
+ libc = dict(
+ filter(
+ lambda x: x[1],
+ zip(["lib", "version"], libc_ver()),
+ )
+ )
+ if libc:
+ distro_infos["libc"] = libc
+ if distro_infos:
+ data["distro"] = distro_infos
+
+ if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
+ data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
+
+ if platform.system():
+ data.setdefault("system", {})["name"] = platform.system()
+
+ if platform.release():
+ data.setdefault("system", {})["release"] = platform.release()
+
+ if platform.machine():
+ data["cpu"] = platform.machine()
+
+ if has_tls():
+ import _ssl as ssl
+
+ data["openssl_version"] = ssl.OPENSSL_VERSION
+
+ setuptools_dist = get_default_environment().get_distribution("setuptools")
+ if setuptools_dist is not None:
+ data["setuptools_version"] = str(setuptools_dist.version)
+
+ if shutil.which("rustc") is not None:
+ # If for any reason `rustc --version` fails, silently ignore it
+ try:
+ rustc_output = subprocess.check_output(
+ ["rustc", "--version"], stderr=subprocess.STDOUT, timeout=0.5
+ )
+ except Exception:
+ pass
+ else:
+ if rustc_output.startswith(b"rustc "):
+ # The format of `rustc --version` is:
+ # `b'rustc 1.52.1 (9bc8c42bb 2021-05-09)\n'`
+ # We extract just the middle (1.52.1) part
+ data["rustc_version"] = rustc_output.split(b" ")[1].decode()
+
+ # Use None rather than False so as not to give the impression that
+ # pip knows it is not being run under CI. Rather, it is a null or
+ # inconclusive result. Also, we include some value rather than no
+ # value to make it easier to know that the check has been run.
+ data["ci"] = True if looks_like_ci() else None
+
+ user_data = os.environ.get("PIP_USER_AGENT_USER_DATA")
+ if user_data is not None:
+ data["user_data"] = user_data
+
+ return "{data[installer][name]}/{data[installer][version]} {json}".format(
+ data=data,
+ json=json.dumps(data, separators=(",", ":"), sort_keys=True),
+ )
+
+
+class LocalFSAdapter(BaseAdapter):
+ def send(
+ self,
+ request: PreparedRequest,
+ stream: bool = False,
+ timeout: Optional[Union[float, Tuple[float, float]]] = None,
+ verify: Union[bool, str] = True,
+ cert: Optional[Union[str, Tuple[str, str]]] = None,
+ proxies: Optional[Mapping[str, str]] = None,
+ ) -> Response:
+ pathname = url_to_path(request.url)
+
+ resp = Response()
+ resp.status_code = 200
+ resp.url = request.url
+
+ try:
+ stats = os.stat(pathname)
+ except OSError as exc:
+ # format the exception raised as a io.BytesIO object,
+ # to return a better error message:
+ resp.status_code = 404
+ resp.reason = type(exc).__name__
+ resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode())
+ else:
+ modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
+ content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
+ resp.headers = CaseInsensitiveDict(
+ {
+ "Content-Type": content_type,
+ "Content-Length": stats.st_size,
+ "Last-Modified": modified,
+ }
+ )
+
+ resp.raw = open(pathname, "rb")
+ resp.close = resp.raw.close
+
+ return resp
+
+ def close(self) -> None:
+ pass
+
+
+class _SSLContextAdapterMixin:
+ """Mixin to add the ``ssl_context`` constructor argument to HTTP adapters.
+
+ The additional argument is forwarded directly to the pool manager. This allows us
+ to dynamically decide what SSL store to use at runtime, which is used to implement
+ the optional ``truststore`` backend.
+ """
+
+ def __init__(
+ self,
+ *,
+ ssl_context: Optional["SSLContext"] = None,
+ **kwargs: Any,
+ ) -> None:
+ self._ssl_context = ssl_context
+ super().__init__(**kwargs)
+
+ def init_poolmanager(
+ self,
+ connections: int,
+ maxsize: int,
+ block: bool = DEFAULT_POOLBLOCK,
+ **pool_kwargs: Any,
+ ) -> "PoolManager":
+ if self._ssl_context is not None:
+ pool_kwargs.setdefault("ssl_context", self._ssl_context)
+ return super().init_poolmanager( # type: ignore[misc]
+ connections=connections,
+ maxsize=maxsize,
+ block=block,
+ **pool_kwargs,
+ )
+
+
+class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
+ pass
+
+
+class CacheControlAdapter(_SSLContextAdapterMixin, _BaseCacheControlAdapter):
+ pass
+
+
+class InsecureHTTPAdapter(HTTPAdapter):
+ def cert_verify(
+ self,
+ conn: ConnectionPool,
+ url: str,
+ verify: Union[bool, str],
+ cert: Optional[Union[str, Tuple[str, str]]],
+ ) -> None:
+ super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
+
+
+class InsecureCacheControlAdapter(CacheControlAdapter):
+ def cert_verify(
+ self,
+ conn: ConnectionPool,
+ url: str,
+ verify: Union[bool, str],
+ cert: Optional[Union[str, Tuple[str, str]]],
+ ) -> None:
+ super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
+
+
+class PipSession(requests.Session):
+ timeout: Optional[int] = None
+
+ def __init__(
+ self,
+ *args: Any,
+ retries: int = 0,
+ cache: Optional[str] = None,
+ trusted_hosts: Sequence[str] = (),
+ index_urls: Optional[List[str]] = None,
+ ssl_context: Optional["SSLContext"] = None,
+ **kwargs: Any,
+ ) -> None:
+ """
+ :param trusted_hosts: Domains not to emit warnings for when not using
+ HTTPS.
+ """
+ super().__init__(*args, **kwargs)
+
+ # Namespace the attribute with "pip_" just in case to prevent
+ # possible conflicts with the base class.
+ self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
+
+ # Attach our User Agent to the request
+ self.headers["User-Agent"] = user_agent()
+
+ # Attach our Authentication handler to the session
+ self.auth = MultiDomainBasicAuth(index_urls=index_urls)
+
+ # Create our urllib3.Retry instance which will allow us to customize
+ # how we handle retries.
+ retries = urllib3.Retry(
+ # Set the total number of retries that a particular request can
+ # have.
+ total=retries,
+ # A 503 error from PyPI typically means that the Fastly -> Origin
+ # connection got interrupted in some way. A 503 error in general
+ # is typically considered a transient error so we'll go ahead and
+ # retry it.
+ # A 500 may indicate transient error in Amazon S3
+ # A 502 may be a transient error from a CDN like CloudFlare or CloudFront
+ # A 520 or 527 - may indicate transient error in CloudFlare
+ status_forcelist=[500, 502, 503, 520, 527],
+ # Add a small amount of back off between failed requests in
+ # order to prevent hammering the service.
+ backoff_factor=0.25,
+ ) # type: ignore
+
+ # Our Insecure HTTPAdapter disables HTTPS validation. It does not
+ # support caching so we'll use it for all http:// URLs.
+ # If caching is disabled, we will also use it for
+ # https:// hosts that we've marked as ignoring
+ # TLS errors for (trusted-hosts).
+ insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
+
+ # We want to _only_ cache responses on securely fetched origins or when
+ # the host is specified as trusted. We do this because
+ # we can't validate the response of an insecurely/untrusted fetched
+ # origin, and we don't want someone to be able to poison the cache and
+ # require manual eviction from the cache to fix it.
+ if cache:
+ secure_adapter = CacheControlAdapter(
+ cache=SafeFileCache(cache),
+ max_retries=retries,
+ ssl_context=ssl_context,
+ )
+ self._trusted_host_adapter = InsecureCacheControlAdapter(
+ cache=SafeFileCache(cache),
+ max_retries=retries,
+ )
+ else:
+ secure_adapter = HTTPAdapter(max_retries=retries, ssl_context=ssl_context)
+ self._trusted_host_adapter = insecure_adapter
+
+ self.mount("https://", secure_adapter)
+ self.mount("http://", insecure_adapter)
+
+ # Enable file:// urls
+ self.mount("file://", LocalFSAdapter())
+
+ for host in trusted_hosts:
+ self.add_trusted_host(host, suppress_logging=True)
+
+ def update_index_urls(self, new_index_urls: List[str]) -> None:
+ """
+ :param new_index_urls: New index urls to update the authentication
+ handler with.
+ """
+ self.auth.index_urls = new_index_urls
+
+ def add_trusted_host(
+ self, host: str, source: Optional[str] = None, suppress_logging: bool = False
+ ) -> None:
+ """
+ :param host: It is okay to provide a host that has previously been
+ added.
+ :param source: An optional source string, for logging where the host
+ string came from.
+ """
+ if not suppress_logging:
+ msg = f"adding trusted host: {host!r}"
+ if source is not None:
+ msg += f" (from {source})"
+ logger.info(msg)
+
+ parsed_host, parsed_port = parse_netloc(host)
+ if parsed_host is None:
+ raise ValueError(f"Trusted host URL must include a host part: {host!r}")
+ if (parsed_host, parsed_port) not in self.pip_trusted_origins:
+ self.pip_trusted_origins.append((parsed_host, parsed_port))
+
+ self.mount(
+ build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter
+ )
+ self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter)
+ if not parsed_port:
+ self.mount(
+ build_url_from_netloc(host, scheme="http") + ":",
+ self._trusted_host_adapter,
+ )
+ # Mount wildcard ports for the same host.
+ self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
+
+ def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]:
+ yield from SECURE_ORIGINS
+ for host, port in self.pip_trusted_origins:
+ yield ("*", host, "*" if port is None else port)
+
+ def is_secure_origin(self, location: Link) -> bool:
+ # Determine if this url used a secure transport mechanism
+ parsed = urllib.parse.urlparse(str(location))
+ origin_protocol, origin_host, origin_port = (
+ parsed.scheme,
+ parsed.hostname,
+ parsed.port,
+ )
+
+ # The protocol to use to see if the protocol matches.
+ # Don't count the repository type as part of the protocol: in
+ # cases such as "git+ssh", only use "ssh". (I.e., Only verify against
+ # the last scheme.)
+ origin_protocol = origin_protocol.rsplit("+", 1)[-1]
+
+ # Determine if our origin is a secure origin by looking through our
+ # hardcoded list of secure origins, as well as any additional ones
+ # configured on this PackageFinder instance.
+ for secure_origin in self.iter_secure_origins():
+ secure_protocol, secure_host, secure_port = secure_origin
+ if origin_protocol != secure_protocol and secure_protocol != "*":
+ continue
+
+ try:
+ addr = ipaddress.ip_address(origin_host or "")
+ network = ipaddress.ip_network(secure_host)
+ except ValueError:
+ # We don't have both a valid address or a valid network, so
+ # we'll check this origin against hostnames.
+ if (
+ origin_host
+ and origin_host.lower() != secure_host.lower()
+ and secure_host != "*"
+ ):
+ continue
+ else:
+ # We have a valid address and network, so see if the address
+ # is contained within the network.
+ if addr not in network:
+ continue
+
+ # Check to see if the port matches.
+ if (
+ origin_port != secure_port
+ and secure_port != "*"
+ and secure_port is not None
+ ):
+ continue
+
+ # If we've gotten here, then this origin matches the current
+ # secure origin and we should return True
+ return True
+
+ # If we've gotten to this point, then the origin isn't secure and we
+ # will not accept it as a valid location to search. We will however
+ # log a warning that we are ignoring it.
+ logger.warning(
+ "The repository located at %s is not a trusted or secure host and "
+ "is being ignored. If this repository is available via HTTPS we "
+ "recommend you use HTTPS instead, otherwise you may silence "
+ "this warning and allow it anyway with '--trusted-host %s'.",
+ origin_host,
+ origin_host,
+ )
+
+ return False
+
+ def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
+ # Allow setting a default timeout on a session
+ kwargs.setdefault("timeout", self.timeout)
+ # Allow setting a default proxies on a session
+ kwargs.setdefault("proxies", self.proxies)
+
+ # Dispatch the actual request
+ return super().request(method, url, *args, **kwargs)
diff --git a/env/Lib/site-packages/pip/_internal/network/utils.py b/env/Lib/site-packages/pip/_internal/network/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba4c265e89b1ce570ceeeebf1a321eebec919b4
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/utils.py
@@ -0,0 +1,98 @@
+from typing import Dict, Generator
+
+from pip._vendor.requests.models import Response
+
+from pip._internal.exceptions import NetworkConnectionError
+
+# The following comments and HTTP headers were originally added by
+# Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03.
+#
+# We use Accept-Encoding: identity here because requests defaults to
+# accepting compressed responses. This breaks in a variety of ways
+# depending on how the server is configured.
+# - Some servers will notice that the file isn't a compressible file
+# and will leave the file alone and with an empty Content-Encoding
+# - Some servers will notice that the file is already compressed and
+# will leave the file alone, adding a Content-Encoding: gzip header
+# - Some servers won't notice anything at all and will take a file
+# that's already been compressed and compress it again, and set
+# the Content-Encoding: gzip header
+# By setting this to request only the identity encoding we're hoping
+# to eliminate the third case. Hopefully there does not exist a server
+# which when given a file will notice it is already compressed and that
+# you're not asking for a compressed file and will then decompress it
+# before sending because if that's the case I don't think it'll ever be
+# possible to make this work.
+HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"}
+
+DOWNLOAD_CHUNK_SIZE = 256 * 1024
+
+
+def raise_for_status(resp: Response) -> None:
+ http_error_msg = ""
+ if isinstance(resp.reason, bytes):
+ # We attempt to decode utf-8 first because some servers
+ # choose to localize their reason strings. If the string
+ # isn't utf-8, we fall back to iso-8859-1 for all other
+ # encodings.
+ try:
+ reason = resp.reason.decode("utf-8")
+ except UnicodeDecodeError:
+ reason = resp.reason.decode("iso-8859-1")
+ else:
+ reason = resp.reason
+
+ if 400 <= resp.status_code < 500:
+ http_error_msg = (
+ f"{resp.status_code} Client Error: {reason} for url: {resp.url}"
+ )
+
+ elif 500 <= resp.status_code < 600:
+ http_error_msg = (
+ f"{resp.status_code} Server Error: {reason} for url: {resp.url}"
+ )
+
+ if http_error_msg:
+ raise NetworkConnectionError(http_error_msg, response=resp)
+
+
+def response_chunks(
+ response: Response, chunk_size: int = DOWNLOAD_CHUNK_SIZE
+) -> Generator[bytes, None, None]:
+ """Given a requests Response, provide the data chunks."""
+ try:
+ # Special case for urllib3.
+ for chunk in response.raw.stream(
+ chunk_size,
+ # We use decode_content=False here because we don't
+ # want urllib3 to mess with the raw bytes we get
+ # from the server. If we decompress inside of
+ # urllib3 then we cannot verify the checksum
+ # because the checksum will be of the compressed
+ # file. This breakage will only occur if the
+ # server adds a Content-Encoding header, which
+ # depends on how the server was configured:
+ # - Some servers will notice that the file isn't a
+ # compressible file and will leave the file alone
+ # and with an empty Content-Encoding
+ # - Some servers will notice that the file is
+ # already compressed and will leave the file
+ # alone and will add a Content-Encoding: gzip
+ # header
+ # - Some servers won't notice anything at all and
+ # will take a file that's already been compressed
+ # and compress it again and set the
+ # Content-Encoding: gzip header
+ #
+ # By setting this not to decode automatically we
+ # hope to eliminate problems with the second case.
+ decode_content=False,
+ ):
+ yield chunk
+ except AttributeError:
+ # Standard file-like object.
+ while True:
+ chunk = response.raw.read(chunk_size)
+ if not chunk:
+ break
+ yield chunk
diff --git a/env/Lib/site-packages/pip/_internal/network/xmlrpc.py b/env/Lib/site-packages/pip/_internal/network/xmlrpc.py
new file mode 100644
index 0000000000000000000000000000000000000000..22ec8d2f4a6b276aedf2249d50ff10a21922513c
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/network/xmlrpc.py
@@ -0,0 +1,62 @@
+"""xmlrpclib.Transport implementation
+"""
+
+import logging
+import urllib.parse
+import xmlrpc.client
+from typing import TYPE_CHECKING, Tuple
+
+from pip._internal.exceptions import NetworkConnectionError
+from pip._internal.network.session import PipSession
+from pip._internal.network.utils import raise_for_status
+
+if TYPE_CHECKING:
+ from xmlrpc.client import _HostType, _Marshallable
+
+ from _typeshed import SizedBuffer
+
+logger = logging.getLogger(__name__)
+
+
+class PipXmlrpcTransport(xmlrpc.client.Transport):
+ """Provide a `xmlrpclib.Transport` implementation via a `PipSession`
+ object.
+ """
+
+ def __init__(
+ self, index_url: str, session: PipSession, use_datetime: bool = False
+ ) -> None:
+ super().__init__(use_datetime)
+ index_parts = urllib.parse.urlparse(index_url)
+ self._scheme = index_parts.scheme
+ self._session = session
+
+ def request(
+ self,
+ host: "_HostType",
+ handler: str,
+ request_body: "SizedBuffer",
+ verbose: bool = False,
+ ) -> Tuple["_Marshallable", ...]:
+ assert isinstance(host, str)
+ parts = (self._scheme, host, handler, None, None, None)
+ url = urllib.parse.urlunparse(parts)
+ try:
+ headers = {"Content-Type": "text/xml"}
+ response = self._session.post(
+ url,
+ data=request_body,
+ headers=headers,
+ stream=True,
+ )
+ raise_for_status(response)
+ self.verbose = verbose
+ return self.parse_response(response.raw)
+ except NetworkConnectionError as exc:
+ assert exc.response
+ logger.critical(
+ "HTTP error %s while getting %s",
+ exc.response.status_code,
+ url,
+ )
+ raise
diff --git a/env/Lib/site-packages/pip/_internal/operations/__init__.py b/env/Lib/site-packages/pip/_internal/operations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/__init__.py b/env/Lib/site-packages/pip/_internal/operations/build/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/build_tracker.py b/env/Lib/site-packages/pip/_internal/operations/build/build_tracker.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ed8dd235960bf406e26d5c0069b12c7f2d463e9
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/build_tracker.py
@@ -0,0 +1,138 @@
+import contextlib
+import hashlib
+import logging
+import os
+from types import TracebackType
+from typing import Dict, Generator, Optional, Type, Union
+
+from pip._internal.req.req_install import InstallRequirement
+from pip._internal.utils.temp_dir import TempDirectory
+
+logger = logging.getLogger(__name__)
+
+
+@contextlib.contextmanager
+def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
+ target = os.environ
+
+ # Save values from the target and change them.
+ non_existent_marker = object()
+ saved_values: Dict[str, Union[object, str]] = {}
+ for name, new_value in changes.items():
+ try:
+ saved_values[name] = target[name]
+ except KeyError:
+ saved_values[name] = non_existent_marker
+ target[name] = new_value
+
+ try:
+ yield
+ finally:
+ # Restore original values in the target.
+ for name, original_value in saved_values.items():
+ if original_value is non_existent_marker:
+ del target[name]
+ else:
+ assert isinstance(original_value, str) # for mypy
+ target[name] = original_value
+
+
+@contextlib.contextmanager
+def get_build_tracker() -> Generator["BuildTracker", None, None]:
+ root = os.environ.get("PIP_BUILD_TRACKER")
+ with contextlib.ExitStack() as ctx:
+ if root is None:
+ root = ctx.enter_context(TempDirectory(kind="build-tracker")).path
+ ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root))
+ logger.debug("Initialized build tracking at %s", root)
+
+ with BuildTracker(root) as tracker:
+ yield tracker
+
+
+class TrackerId(str):
+ """Uniquely identifying string provided to the build tracker."""
+
+
+class BuildTracker:
+ """Ensure that an sdist cannot request itself as a setup requirement.
+
+ When an sdist is prepared, it identifies its setup requirements in the
+ context of ``BuildTracker.track()``. If a requirement shows up recursively, this
+ raises an exception.
+
+ This stops fork bombs embedded in malicious packages."""
+
+ def __init__(self, root: str) -> None:
+ self._root = root
+ self._entries: Dict[TrackerId, InstallRequirement] = {}
+ logger.debug("Created build tracker: %s", self._root)
+
+ def __enter__(self) -> "BuildTracker":
+ logger.debug("Entered build tracker: %s", self._root)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.cleanup()
+
+ def _entry_path(self, key: TrackerId) -> str:
+ hashed = hashlib.sha224(key.encode()).hexdigest()
+ return os.path.join(self._root, hashed)
+
+ def add(self, req: InstallRequirement, key: TrackerId) -> None:
+ """Add an InstallRequirement to build tracking."""
+
+ # Get the file to write information about this requirement.
+ entry_path = self._entry_path(key)
+
+ # Try reading from the file. If it exists and can be read from, a build
+ # is already in progress, so a LookupError is raised.
+ try:
+ with open(entry_path) as fp:
+ contents = fp.read()
+ except FileNotFoundError:
+ pass
+ else:
+ message = f"{req.link} is already being built: {contents}"
+ raise LookupError(message)
+
+ # If we're here, req should really not be building already.
+ assert key not in self._entries
+
+ # Start tracking this requirement.
+ with open(entry_path, "w", encoding="utf-8") as fp:
+ fp.write(str(req))
+ self._entries[key] = req
+
+ logger.debug("Added %s to build tracker %r", req, self._root)
+
+ def remove(self, req: InstallRequirement, key: TrackerId) -> None:
+ """Remove an InstallRequirement from build tracking."""
+
+ # Delete the created file and the corresponding entry.
+ os.unlink(self._entry_path(key))
+ del self._entries[key]
+
+ logger.debug("Removed %s from build tracker %r", req, self._root)
+
+ def cleanup(self) -> None:
+ for key, req in list(self._entries.items()):
+ self.remove(req, key)
+
+ logger.debug("Removed build tracker: %r", self._root)
+
+ @contextlib.contextmanager
+ def track(self, req: InstallRequirement, key: str) -> Generator[None, None, None]:
+ """Ensure that `key` cannot install itself as a setup requirement.
+
+ :raises LookupError: If `key` was already provided in a parent invocation of
+ the context introduced by this method."""
+ tracker_id = TrackerId(key)
+ self.add(req, tracker_id)
+ yield
+ self.remove(req, tracker_id)
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/metadata.py b/env/Lib/site-packages/pip/_internal/operations/build/metadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..c66ac354deb035405fe0e4040dac539d28570257
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/metadata.py
@@ -0,0 +1,39 @@
+"""Metadata generation logic for source distributions.
+"""
+
+import os
+
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
+
+from pip._internal.build_env import BuildEnvironment
+from pip._internal.exceptions import (
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
+from pip._internal.utils.subprocess import runner_with_spinner_message
+from pip._internal.utils.temp_dir import TempDirectory
+
+
+def generate_metadata(
+ build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
+) -> str:
+ """Generate metadata using mechanisms described in PEP 517.
+
+ Returns the generated metadata directory.
+ """
+ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
+
+ metadata_dir = metadata_tmpdir.path
+
+ with build_env:
+ # Note that BuildBackendHookCaller implements a fallback for
+ # prepare_metadata_for_build_wheel, so we don't have to
+ # consider the possibility that this hook doesn't exist.
+ runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
+ with backend.subprocess_runner(runner):
+ try:
+ distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
+
+ return os.path.join(metadata_dir, distinfo_dir)
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/metadata_editable.py b/env/Lib/site-packages/pip/_internal/operations/build/metadata_editable.py
new file mode 100644
index 0000000000000000000000000000000000000000..27c69f0d1eaf3e223d599e91f969d52a821426fe
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/metadata_editable.py
@@ -0,0 +1,41 @@
+"""Metadata generation logic for source distributions.
+"""
+
+import os
+
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
+
+from pip._internal.build_env import BuildEnvironment
+from pip._internal.exceptions import (
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
+from pip._internal.utils.subprocess import runner_with_spinner_message
+from pip._internal.utils.temp_dir import TempDirectory
+
+
+def generate_editable_metadata(
+ build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
+) -> str:
+ """Generate metadata using mechanisms described in PEP 660.
+
+ Returns the generated metadata directory.
+ """
+ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
+
+ metadata_dir = metadata_tmpdir.path
+
+ with build_env:
+ # Note that BuildBackendHookCaller implements a fallback for
+ # prepare_metadata_for_build_wheel/editable, so we don't have to
+ # consider the possibility that this hook doesn't exist.
+ runner = runner_with_spinner_message(
+ "Preparing editable metadata (pyproject.toml)"
+ )
+ with backend.subprocess_runner(runner):
+ try:
+ distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir)
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
+
+ return os.path.join(metadata_dir, distinfo_dir)
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/metadata_legacy.py b/env/Lib/site-packages/pip/_internal/operations/build/metadata_legacy.py
new file mode 100644
index 0000000000000000000000000000000000000000..c01dd1c678a083c17190fc9b7e214eca5c91f4f1
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/metadata_legacy.py
@@ -0,0 +1,74 @@
+"""Metadata generation logic for legacy source distributions.
+"""
+
+import logging
+import os
+
+from pip._internal.build_env import BuildEnvironment
+from pip._internal.cli.spinners import open_spinner
+from pip._internal.exceptions import (
+ InstallationError,
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
+from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args
+from pip._internal.utils.subprocess import call_subprocess
+from pip._internal.utils.temp_dir import TempDirectory
+
+logger = logging.getLogger(__name__)
+
+
+def _find_egg_info(directory: str) -> str:
+ """Find an .egg-info subdirectory in `directory`."""
+ filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")]
+
+ if not filenames:
+ raise InstallationError(f"No .egg-info directory found in {directory}")
+
+ if len(filenames) > 1:
+ raise InstallationError(
+ f"More than one .egg-info directory found in {directory}"
+ )
+
+ return os.path.join(directory, filenames[0])
+
+
+def generate_metadata(
+ build_env: BuildEnvironment,
+ setup_py_path: str,
+ source_dir: str,
+ isolated: bool,
+ details: str,
+) -> str:
+ """Generate metadata using setup.py-based defacto mechanisms.
+
+ Returns the generated metadata directory.
+ """
+ logger.debug(
+ "Running setup.py (path:%s) egg_info for package %s",
+ setup_py_path,
+ details,
+ )
+
+ egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path
+
+ args = make_setuptools_egg_info_args(
+ setup_py_path,
+ egg_info_dir=egg_info_dir,
+ no_user_config=isolated,
+ )
+
+ with build_env:
+ with open_spinner("Preparing metadata (setup.py)") as spinner:
+ try:
+ call_subprocess(
+ args,
+ cwd=source_dir,
+ command_desc="python setup.py egg_info",
+ spinner=spinner,
+ )
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
+
+ # Return the .egg-info directory.
+ return _find_egg_info(egg_info_dir)
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/wheel.py b/env/Lib/site-packages/pip/_internal/operations/build/wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..064811ad11bb07b2b7bc8e30ec6c03f21997d6b2
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/wheel.py
@@ -0,0 +1,37 @@
+import logging
+import os
+from typing import Optional
+
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
+
+from pip._internal.utils.subprocess import runner_with_spinner_message
+
+logger = logging.getLogger(__name__)
+
+
+def build_wheel_pep517(
+ name: str,
+ backend: BuildBackendHookCaller,
+ metadata_directory: str,
+ tempd: str,
+) -> Optional[str]:
+ """Build one InstallRequirement using the PEP 517 build process.
+
+ Returns path to wheel if successfully built. Otherwise, returns None.
+ """
+ assert metadata_directory is not None
+ try:
+ logger.debug("Destination directory: %s", tempd)
+
+ runner = runner_with_spinner_message(
+ f"Building wheel for {name} (pyproject.toml)"
+ )
+ with backend.subprocess_runner(runner):
+ wheel_name = backend.build_wheel(
+ tempd,
+ metadata_directory=metadata_directory,
+ )
+ except Exception:
+ logger.error("Failed building wheel for %s", name)
+ return None
+ return os.path.join(tempd, wheel_name)
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/wheel_editable.py b/env/Lib/site-packages/pip/_internal/operations/build/wheel_editable.py
new file mode 100644
index 0000000000000000000000000000000000000000..719d69dd801b78b360c6c2234080eee638b8de82
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/wheel_editable.py
@@ -0,0 +1,46 @@
+import logging
+import os
+from typing import Optional
+
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
+
+from pip._internal.utils.subprocess import runner_with_spinner_message
+
+logger = logging.getLogger(__name__)
+
+
+def build_wheel_editable(
+ name: str,
+ backend: BuildBackendHookCaller,
+ metadata_directory: str,
+ tempd: str,
+) -> Optional[str]:
+ """Build one InstallRequirement using the PEP 660 build process.
+
+ Returns path to wheel if successfully built. Otherwise, returns None.
+ """
+ assert metadata_directory is not None
+ try:
+ logger.debug("Destination directory: %s", tempd)
+
+ runner = runner_with_spinner_message(
+ f"Building editable for {name} (pyproject.toml)"
+ )
+ with backend.subprocess_runner(runner):
+ try:
+ wheel_name = backend.build_editable(
+ tempd,
+ metadata_directory=metadata_directory,
+ )
+ except HookMissing as e:
+ logger.error(
+ "Cannot build editable %s because the build "
+ "backend does not have the %s hook",
+ name,
+ e,
+ )
+ return None
+ except Exception:
+ logger.error("Failed building editable for %s", name)
+ return None
+ return os.path.join(tempd, wheel_name)
diff --git a/env/Lib/site-packages/pip/_internal/operations/build/wheel_legacy.py b/env/Lib/site-packages/pip/_internal/operations/build/wheel_legacy.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ee2a7058d323e41f3c930f14685f68e6a599fa5
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/build/wheel_legacy.py
@@ -0,0 +1,102 @@
+import logging
+import os.path
+from typing import List, Optional
+
+from pip._internal.cli.spinners import open_spinner
+from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args
+from pip._internal.utils.subprocess import call_subprocess, format_command_args
+
+logger = logging.getLogger(__name__)
+
+
+def format_command_result(
+ command_args: List[str],
+ command_output: str,
+) -> str:
+ """Format command information for logging."""
+ command_desc = format_command_args(command_args)
+ text = f"Command arguments: {command_desc}\n"
+
+ if not command_output:
+ text += "Command output: None"
+ elif logger.getEffectiveLevel() > logging.DEBUG:
+ text += "Command output: [use --verbose to show]"
+ else:
+ if not command_output.endswith("\n"):
+ command_output += "\n"
+ text += f"Command output:\n{command_output}"
+
+ return text
+
+
+def get_legacy_build_wheel_path(
+ names: List[str],
+ temp_dir: str,
+ name: str,
+ command_args: List[str],
+ command_output: str,
+) -> Optional[str]:
+ """Return the path to the wheel in the temporary build directory."""
+ # Sort for determinism.
+ names = sorted(names)
+ if not names:
+ msg = f"Legacy build of wheel for {name!r} created no files.\n"
+ msg += format_command_result(command_args, command_output)
+ logger.warning(msg)
+ return None
+
+ if len(names) > 1:
+ msg = (
+ f"Legacy build of wheel for {name!r} created more than one file.\n"
+ f"Filenames (choosing first): {names}\n"
+ )
+ msg += format_command_result(command_args, command_output)
+ logger.warning(msg)
+
+ return os.path.join(temp_dir, names[0])
+
+
+def build_wheel_legacy(
+ name: str,
+ setup_py_path: str,
+ source_dir: str,
+ global_options: List[str],
+ build_options: List[str],
+ tempd: str,
+) -> Optional[str]:
+ """Build one unpacked package using the "legacy" build process.
+
+ Returns path to wheel if successfully built. Otherwise, returns None.
+ """
+ wheel_args = make_setuptools_bdist_wheel_args(
+ setup_py_path,
+ global_options=global_options,
+ build_options=build_options,
+ destination_dir=tempd,
+ )
+
+ spin_message = f"Building wheel for {name} (setup.py)"
+ with open_spinner(spin_message) as spinner:
+ logger.debug("Destination directory: %s", tempd)
+
+ try:
+ output = call_subprocess(
+ wheel_args,
+ command_desc="python setup.py bdist_wheel",
+ cwd=source_dir,
+ spinner=spinner,
+ )
+ except Exception:
+ spinner.finish("error")
+ logger.error("Failed building wheel for %s", name)
+ return None
+
+ names = os.listdir(tempd)
+ wheel_path = get_legacy_build_wheel_path(
+ names=names,
+ temp_dir=tempd,
+ name=name,
+ command_args=wheel_args,
+ command_output=output,
+ )
+ return wheel_path
diff --git a/env/Lib/site-packages/pip/_internal/operations/check.py b/env/Lib/site-packages/pip/_internal/operations/check.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b6fbc4c37599588ad69da3dc8a4d9628dc89b96
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/check.py
@@ -0,0 +1,181 @@
+"""Validation of dependencies of packages
+"""
+
+import logging
+from contextlib import suppress
+from email.parser import Parser
+from functools import reduce
+from typing import (
+ Callable,
+ Dict,
+ FrozenSet,
+ Generator,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Set,
+ Tuple,
+)
+
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.tags import Tag, parse_tag
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+from pip._vendor.packaging.version import Version
+
+from pip._internal.distributions import make_distribution_for_install_requirement
+from pip._internal.metadata import get_default_environment
+from pip._internal.metadata.base import BaseDistribution
+from pip._internal.req.req_install import InstallRequirement
+
+logger = logging.getLogger(__name__)
+
+
+class PackageDetails(NamedTuple):
+ version: Version
+ dependencies: List[Requirement]
+
+
+# Shorthands
+PackageSet = Dict[NormalizedName, PackageDetails]
+Missing = Tuple[NormalizedName, Requirement]
+Conflicting = Tuple[NormalizedName, Version, Requirement]
+
+MissingDict = Dict[NormalizedName, List[Missing]]
+ConflictingDict = Dict[NormalizedName, List[Conflicting]]
+CheckResult = Tuple[MissingDict, ConflictingDict]
+ConflictDetails = Tuple[PackageSet, CheckResult]
+
+
+def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
+ """Converts a list of distributions into a PackageSet."""
+ package_set = {}
+ problems = False
+ env = get_default_environment()
+ for dist in env.iter_installed_distributions(local_only=False, skip=()):
+ name = dist.canonical_name
+ try:
+ dependencies = list(dist.iter_dependencies())
+ package_set[name] = PackageDetails(dist.version, dependencies)
+ except (OSError, ValueError) as e:
+ # Don't crash on unreadable or broken metadata.
+ logger.warning("Error parsing dependencies of %s: %s", name, e)
+ problems = True
+ return package_set, problems
+
+
+def check_package_set(
+ package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
+) -> CheckResult:
+ """Check if a package set is consistent
+
+ If should_ignore is passed, it should be a callable that takes a
+ package name and returns a boolean.
+ """
+
+ missing = {}
+ conflicting = {}
+
+ for package_name, package_detail in package_set.items():
+ # Info about dependencies of package_name
+ missing_deps: Set[Missing] = set()
+ conflicting_deps: Set[Conflicting] = set()
+
+ if should_ignore and should_ignore(package_name):
+ continue
+
+ for req in package_detail.dependencies:
+ name = canonicalize_name(req.name)
+
+ # Check if it's missing
+ if name not in package_set:
+ missed = True
+ if req.marker is not None:
+ missed = req.marker.evaluate({"extra": ""})
+ if missed:
+ missing_deps.add((name, req))
+ continue
+
+ # Check if there's a conflict
+ version = package_set[name].version
+ if not req.specifier.contains(version, prereleases=True):
+ conflicting_deps.add((name, version, req))
+
+ if missing_deps:
+ missing[package_name] = sorted(missing_deps, key=str)
+ if conflicting_deps:
+ conflicting[package_name] = sorted(conflicting_deps, key=str)
+
+ return missing, conflicting
+
+
+def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
+ """For checking if the dependency graph would be consistent after \
+ installing given requirements
+ """
+ # Start from the current state
+ package_set, _ = create_package_set_from_installed()
+ # Install packages
+ would_be_installed = _simulate_installation_of(to_install, package_set)
+
+ # Only warn about directly-dependent packages; create a whitelist of them
+ whitelist = _create_whitelist(would_be_installed, package_set)
+
+ return (
+ package_set,
+ check_package_set(
+ package_set, should_ignore=lambda name: name not in whitelist
+ ),
+ )
+
+
+def check_unsupported(
+ packages: Iterable[BaseDistribution],
+ supported_tags: Iterable[Tag],
+) -> Generator[BaseDistribution, None, None]:
+ for p in packages:
+ with suppress(FileNotFoundError):
+ wheel_file = p.read_text("WHEEL")
+ wheel_tags: FrozenSet[Tag] = reduce(
+ frozenset.union,
+ map(parse_tag, Parser().parsestr(wheel_file).get_all("Tag", [])),
+ frozenset(),
+ )
+ if wheel_tags.isdisjoint(supported_tags):
+ yield p
+
+
+def _simulate_installation_of(
+ to_install: List[InstallRequirement], package_set: PackageSet
+) -> Set[NormalizedName]:
+ """Computes the version of packages after installing to_install."""
+ # Keep track of packages that were installed
+ installed = set()
+
+ # Modify it as installing requirement_set would (assuming no errors)
+ for inst_req in to_install:
+ abstract_dist = make_distribution_for_install_requirement(inst_req)
+ dist = abstract_dist.get_metadata_distribution()
+ name = dist.canonical_name
+ package_set[name] = PackageDetails(dist.version, list(dist.iter_dependencies()))
+
+ installed.add(name)
+
+ return installed
+
+
+def _create_whitelist(
+ would_be_installed: Set[NormalizedName], package_set: PackageSet
+) -> Set[NormalizedName]:
+ packages_affected = set(would_be_installed)
+
+ for package_name in package_set:
+ if package_name in packages_affected:
+ continue
+
+ for req in package_set[package_name].dependencies:
+ if canonicalize_name(req.name) in packages_affected:
+ packages_affected.add(package_name)
+ break
+
+ return packages_affected
diff --git a/env/Lib/site-packages/pip/_internal/operations/freeze.py b/env/Lib/site-packages/pip/_internal/operations/freeze.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb1039fb7766795059df227f13e37c54ce436618
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/freeze.py
@@ -0,0 +1,258 @@
+import collections
+import logging
+import os
+from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
+
+from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.packaging.version import InvalidVersion
+
+from pip._internal.exceptions import BadCommand, InstallationError
+from pip._internal.metadata import BaseDistribution, get_environment
+from pip._internal.req.constructors import (
+ install_req_from_editable,
+ install_req_from_line,
+)
+from pip._internal.req.req_file import COMMENT_RE
+from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference
+
+logger = logging.getLogger(__name__)
+
+
+class _EditableInfo(NamedTuple):
+ requirement: str
+ comments: List[str]
+
+
+def freeze(
+ requirement: Optional[List[str]] = None,
+ local_only: bool = False,
+ user_only: bool = False,
+ paths: Optional[List[str]] = None,
+ isolated: bool = False,
+ exclude_editable: bool = False,
+ skip: Container[str] = (),
+) -> Generator[str, None, None]:
+ installations: Dict[str, FrozenRequirement] = {}
+
+ dists = get_environment(paths).iter_installed_distributions(
+ local_only=local_only,
+ skip=(),
+ user_only=user_only,
+ )
+ for dist in dists:
+ req = FrozenRequirement.from_dist(dist)
+ if exclude_editable and req.editable:
+ continue
+ installations[req.canonical_name] = req
+
+ if requirement:
+ # the options that don't get turned into an InstallRequirement
+ # should only be emitted once, even if the same option is in multiple
+ # requirements files, so we need to keep track of what has been emitted
+ # so that we don't emit it again if it's seen again
+ emitted_options: Set[str] = set()
+ # keep track of which files a requirement is in so that we can
+ # give an accurate warning if a requirement appears multiple times.
+ req_files: Dict[str, List[str]] = collections.defaultdict(list)
+ for req_file_path in requirement:
+ with open(req_file_path) as req_file:
+ for line in req_file:
+ if (
+ not line.strip()
+ or line.strip().startswith("#")
+ or line.startswith(
+ (
+ "-r",
+ "--requirement",
+ "-f",
+ "--find-links",
+ "-i",
+ "--index-url",
+ "--pre",
+ "--trusted-host",
+ "--process-dependency-links",
+ "--extra-index-url",
+ "--use-feature",
+ )
+ )
+ ):
+ line = line.rstrip()
+ if line not in emitted_options:
+ emitted_options.add(line)
+ yield line
+ continue
+
+ if line.startswith("-e") or line.startswith("--editable"):
+ if line.startswith("-e"):
+ line = line[2:].strip()
+ else:
+ line = line[len("--editable") :].strip().lstrip("=")
+ line_req = install_req_from_editable(
+ line,
+ isolated=isolated,
+ )
+ else:
+ line_req = install_req_from_line(
+ COMMENT_RE.sub("", line).strip(),
+ isolated=isolated,
+ )
+
+ if not line_req.name:
+ logger.info(
+ "Skipping line in requirement file [%s] because "
+ "it's not clear what it would install: %s",
+ req_file_path,
+ line.strip(),
+ )
+ logger.info(
+ " (add #egg=PackageName to the URL to avoid"
+ " this warning)"
+ )
+ else:
+ line_req_canonical_name = canonicalize_name(line_req.name)
+ if line_req_canonical_name not in installations:
+ # either it's not installed, or it is installed
+ # but has been processed already
+ if not req_files[line_req.name]:
+ logger.warning(
+ "Requirement file [%s] contains %s, but "
+ "package %r is not installed",
+ req_file_path,
+ COMMENT_RE.sub("", line).strip(),
+ line_req.name,
+ )
+ else:
+ req_files[line_req.name].append(req_file_path)
+ else:
+ yield str(installations[line_req_canonical_name]).rstrip()
+ del installations[line_req_canonical_name]
+ req_files[line_req.name].append(req_file_path)
+
+ # Warn about requirements that were included multiple times (in a
+ # single requirements file or in different requirements files).
+ for name, files in req_files.items():
+ if len(files) > 1:
+ logger.warning(
+ "Requirement %s included multiple times [%s]",
+ name,
+ ", ".join(sorted(set(files))),
+ )
+
+ yield ("## The following requirements were added by pip freeze:")
+ for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
+ if installation.canonical_name not in skip:
+ yield str(installation).rstrip()
+
+
+def _format_as_name_version(dist: BaseDistribution) -> str:
+ try:
+ dist_version = dist.version
+ except InvalidVersion:
+ # legacy version
+ return f"{dist.raw_name}==={dist.raw_version}"
+ else:
+ return f"{dist.raw_name}=={dist_version}"
+
+
+def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
+ """
+ Compute and return values (req, comments) for use in
+ FrozenRequirement.from_dist().
+ """
+ editable_project_location = dist.editable_project_location
+ assert editable_project_location
+ location = os.path.normcase(os.path.abspath(editable_project_location))
+
+ from pip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs
+
+ vcs_backend = vcs.get_backend_for_dir(location)
+
+ if vcs_backend is None:
+ display = _format_as_name_version(dist)
+ logger.debug(
+ 'No VCS found for editable requirement "%s" in: %r',
+ display,
+ location,
+ )
+ return _EditableInfo(
+ requirement=location,
+ comments=[f"# Editable install with no version control ({display})"],
+ )
+
+ vcs_name = type(vcs_backend).__name__
+
+ try:
+ req = vcs_backend.get_src_requirement(location, dist.raw_name)
+ except RemoteNotFoundError:
+ display = _format_as_name_version(dist)
+ return _EditableInfo(
+ requirement=location,
+ comments=[f"# Editable {vcs_name} install with no remote ({display})"],
+ )
+ except RemoteNotValidError as ex:
+ display = _format_as_name_version(dist)
+ return _EditableInfo(
+ requirement=location,
+ comments=[
+ f"# Editable {vcs_name} install ({display}) with either a deleted "
+ f"local remote or invalid URI:",
+ f"# '{ex.url}'",
+ ],
+ )
+ except BadCommand:
+ logger.warning(
+ "cannot determine version of editable source in %s "
+ "(%s command not found in path)",
+ location,
+ vcs_backend.name,
+ )
+ return _EditableInfo(requirement=location, comments=[])
+ except InstallationError as exc:
+ logger.warning("Error when trying to get requirement for VCS system %s", exc)
+ else:
+ return _EditableInfo(requirement=req, comments=[])
+
+ logger.warning("Could not determine repository location of %s", location)
+
+ return _EditableInfo(
+ requirement=location,
+ comments=["## !! Could not determine repository location"],
+ )
+
+
+class FrozenRequirement:
+ def __init__(
+ self,
+ name: str,
+ req: str,
+ editable: bool,
+ comments: Iterable[str] = (),
+ ) -> None:
+ self.name = name
+ self.canonical_name = canonicalize_name(name)
+ self.req = req
+ self.editable = editable
+ self.comments = comments
+
+ @classmethod
+ def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
+ editable = dist.editable
+ if editable:
+ req, comments = _get_editable_info(dist)
+ else:
+ comments = []
+ direct_url = dist.direct_url
+ if direct_url:
+ # if PEP 610 metadata is present, use it
+ req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name)
+ else:
+ # name==version requirement
+ req = _format_as_name_version(dist)
+
+ return cls(dist.raw_name, req, editable, comments=comments)
+
+ def __str__(self) -> str:
+ req = self.req
+ if self.editable:
+ req = f"-e {req}"
+ return "\n".join(list(self.comments) + [str(req)]) + "\n"
diff --git a/env/Lib/site-packages/pip/_internal/operations/install/__init__.py b/env/Lib/site-packages/pip/_internal/operations/install/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..24d6a5dd31fe33b03f90ed0f9ee465253686900c
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/install/__init__.py
@@ -0,0 +1,2 @@
+"""For modules related to installing packages.
+"""
diff --git a/env/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py b/env/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py
new file mode 100644
index 0000000000000000000000000000000000000000..9aaa699a645e03d5971dbb5e43ff73d2f412a550
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py
@@ -0,0 +1,47 @@
+"""Legacy editable installation process, i.e. `setup.py develop`.
+"""
+
+import logging
+from typing import Optional, Sequence
+
+from pip._internal.build_env import BuildEnvironment
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.setuptools_build import make_setuptools_develop_args
+from pip._internal.utils.subprocess import call_subprocess
+
+logger = logging.getLogger(__name__)
+
+
+def install_editable(
+ *,
+ global_options: Sequence[str],
+ prefix: Optional[str],
+ home: Optional[str],
+ use_user_site: bool,
+ name: str,
+ setup_py_path: str,
+ isolated: bool,
+ build_env: BuildEnvironment,
+ unpacked_source_directory: str,
+) -> None:
+ """Install a package in editable mode. Most arguments are pass-through
+ to setuptools.
+ """
+ logger.info("Running setup.py develop for %s", name)
+
+ args = make_setuptools_develop_args(
+ setup_py_path,
+ global_options=global_options,
+ no_user_config=isolated,
+ prefix=prefix,
+ home=home,
+ use_user_site=use_user_site,
+ )
+
+ with indent_log():
+ with build_env:
+ call_subprocess(
+ args,
+ command_desc="python setup.py develop",
+ cwd=unpacked_source_directory,
+ )
diff --git a/env/Lib/site-packages/pip/_internal/operations/install/wheel.py b/env/Lib/site-packages/pip/_internal/operations/install/wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..aef42aa9eefae7cae4a3d877b6dab1c53d85ad5f
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/install/wheel.py
@@ -0,0 +1,741 @@
+"""Support for installing and building the "wheel" binary package format.
+"""
+
+import collections
+import compileall
+import contextlib
+import csv
+import importlib
+import logging
+import os.path
+import re
+import shutil
+import sys
+import warnings
+from base64 import urlsafe_b64encode
+from email.message import Message
+from itertools import chain, filterfalse, starmap
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ BinaryIO,
+ Callable,
+ Dict,
+ Generator,
+ Iterable,
+ Iterator,
+ List,
+ NewType,
+ Optional,
+ Protocol,
+ Sequence,
+ Set,
+ Tuple,
+ Union,
+ cast,
+)
+from zipfile import ZipFile, ZipInfo
+
+from pip._vendor.distlib.scripts import ScriptMaker
+from pip._vendor.distlib.util import get_export_entry
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.exceptions import InstallationError
+from pip._internal.locations import get_major_minor_version
+from pip._internal.metadata import (
+ BaseDistribution,
+ FilesystemWheel,
+ get_wheel_distribution,
+)
+from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl
+from pip._internal.models.scheme import SCHEME_KEYS, Scheme
+from pip._internal.utils.filesystem import adjacent_tmp_file, replace
+from pip._internal.utils.misc import StreamWrapper, ensure_dir, hash_file, partition
+from pip._internal.utils.unpacking import (
+ current_umask,
+ is_within_directory,
+ set_extracted_file_to_default_mode_plus_executable,
+ zip_item_is_executable,
+)
+from pip._internal.utils.wheel import parse_wheel
+
+if TYPE_CHECKING:
+
+ class File(Protocol):
+ src_record_path: "RecordPath"
+ dest_path: str
+ changed: bool
+
+ def save(self) -> None:
+ pass
+
+
+logger = logging.getLogger(__name__)
+
+RecordPath = NewType("RecordPath", str)
+InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
+
+
+def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]:
+ """Return (encoded_digest, length) for path using hashlib.sha256()"""
+ h, length = hash_file(path, blocksize)
+ digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=")
+ return (digest, str(length))
+
+
+def csv_io_kwargs(mode: str) -> Dict[str, Any]:
+ """Return keyword arguments to properly open a CSV file
+ in the given mode.
+ """
+ return {"mode": mode, "newline": "", "encoding": "utf-8"}
+
+
+def fix_script(path: str) -> bool:
+ """Replace #!python with #!/path/to/python
+ Return True if file was changed.
+ """
+ # XXX RECORD hashes will need to be updated
+ assert os.path.isfile(path)
+
+ with open(path, "rb") as script:
+ firstline = script.readline()
+ if not firstline.startswith(b"#!python"):
+ return False
+ exename = sys.executable.encode(sys.getfilesystemencoding())
+ firstline = b"#!" + exename + os.linesep.encode("ascii")
+ rest = script.read()
+ with open(path, "wb") as script:
+ script.write(firstline)
+ script.write(rest)
+ return True
+
+
+def wheel_root_is_purelib(metadata: Message) -> bool:
+ return metadata.get("Root-Is-Purelib", "").lower() == "true"
+
+
+def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]:
+ console_scripts = {}
+ gui_scripts = {}
+ for entry_point in dist.iter_entry_points():
+ if entry_point.group == "console_scripts":
+ console_scripts[entry_point.name] = entry_point.value
+ elif entry_point.group == "gui_scripts":
+ gui_scripts[entry_point.name] = entry_point.value
+ return console_scripts, gui_scripts
+
+
+def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
+ """Determine if any scripts are not on PATH and format a warning.
+ Returns a warning message if one or more scripts are not on PATH,
+ otherwise None.
+ """
+ if not scripts:
+ return None
+
+ # Group scripts by the path they were installed in
+ grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
+ for destfile in scripts:
+ parent_dir = os.path.dirname(destfile)
+ script_name = os.path.basename(destfile)
+ grouped_by_dir[parent_dir].add(script_name)
+
+ # We don't want to warn for directories that are on PATH.
+ not_warn_dirs = [
+ os.path.normcase(os.path.normpath(i)).rstrip(os.sep)
+ for i in os.environ.get("PATH", "").split(os.pathsep)
+ ]
+ # If an executable sits with sys.executable, we don't warn for it.
+ # This covers the case of venv invocations without activating the venv.
+ not_warn_dirs.append(
+ os.path.normcase(os.path.normpath(os.path.dirname(sys.executable)))
+ )
+ warn_for: Dict[str, Set[str]] = {
+ parent_dir: scripts
+ for parent_dir, scripts in grouped_by_dir.items()
+ if os.path.normcase(os.path.normpath(parent_dir)) not in not_warn_dirs
+ }
+ if not warn_for:
+ return None
+
+ # Format a message
+ msg_lines = []
+ for parent_dir, dir_scripts in warn_for.items():
+ sorted_scripts: List[str] = sorted(dir_scripts)
+ if len(sorted_scripts) == 1:
+ start_text = f"script {sorted_scripts[0]} is"
+ else:
+ start_text = "scripts {} are".format(
+ ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1]
+ )
+
+ msg_lines.append(
+ f"The {start_text} installed in '{parent_dir}' which is not on PATH."
+ )
+
+ last_line_fmt = (
+ "Consider adding {} to PATH or, if you prefer "
+ "to suppress this warning, use --no-warn-script-location."
+ )
+ if len(msg_lines) == 1:
+ msg_lines.append(last_line_fmt.format("this directory"))
+ else:
+ msg_lines.append(last_line_fmt.format("these directories"))
+
+ # Add a note if any directory starts with ~
+ warn_for_tilde = any(
+ i[0] == "~" for i in os.environ.get("PATH", "").split(os.pathsep) if i
+ )
+ if warn_for_tilde:
+ tilde_warning_msg = (
+ "NOTE: The current PATH contains path(s) starting with `~`, "
+ "which may not be expanded by all applications."
+ )
+ msg_lines.append(tilde_warning_msg)
+
+ # Returns the formatted multiline message
+ return "\n".join(msg_lines)
+
+
+def _normalized_outrows(
+ outrows: Iterable[InstalledCSVRow],
+) -> List[Tuple[str, str, str]]:
+ """Normalize the given rows of a RECORD file.
+
+ Items in each row are converted into str. Rows are then sorted to make
+ the value more predictable for tests.
+
+ Each row is a 3-tuple (path, hash, size) and corresponds to a record of
+ a RECORD file (see PEP 376 and PEP 427 for details). For the rows
+ passed to this function, the size can be an integer as an int or string,
+ or the empty string.
+ """
+ # Normally, there should only be one row per path, in which case the
+ # second and third elements don't come into play when sorting.
+ # However, in cases in the wild where a path might happen to occur twice,
+ # we don't want the sort operation to trigger an error (but still want
+ # determinism). Since the third element can be an int or string, we
+ # coerce each element to a string to avoid a TypeError in this case.
+ # For additional background, see--
+ # https://github.com/pypa/pip/issues/5868
+ return sorted(
+ (record_path, hash_, str(size)) for record_path, hash_, size in outrows
+ )
+
+
+def _record_to_fs_path(record_path: RecordPath, lib_dir: str) -> str:
+ return os.path.join(lib_dir, record_path)
+
+
+def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath:
+ # On Windows, do not handle relative paths if they belong to different
+ # logical disks
+ if os.path.splitdrive(path)[0].lower() == os.path.splitdrive(lib_dir)[0].lower():
+ path = os.path.relpath(path, lib_dir)
+
+ path = path.replace(os.path.sep, "/")
+ return cast("RecordPath", path)
+
+
+def get_csv_rows_for_installed(
+ old_csv_rows: List[List[str]],
+ installed: Dict[RecordPath, RecordPath],
+ changed: Set[RecordPath],
+ generated: List[str],
+ lib_dir: str,
+) -> List[InstalledCSVRow]:
+ """
+ :param installed: A map from archive RECORD path to installation RECORD
+ path.
+ """
+ installed_rows: List[InstalledCSVRow] = []
+ for row in old_csv_rows:
+ if len(row) > 3:
+ logger.warning("RECORD line has more than three elements: %s", row)
+ old_record_path = cast("RecordPath", row[0])
+ new_record_path = installed.pop(old_record_path, old_record_path)
+ if new_record_path in changed:
+ digest, length = rehash(_record_to_fs_path(new_record_path, lib_dir))
+ else:
+ digest = row[1] if len(row) > 1 else ""
+ length = row[2] if len(row) > 2 else ""
+ installed_rows.append((new_record_path, digest, length))
+ for f in generated:
+ path = _fs_to_record_path(f, lib_dir)
+ digest, length = rehash(f)
+ installed_rows.append((path, digest, length))
+ return installed_rows + [
+ (installed_record_path, "", "") for installed_record_path in installed.values()
+ ]
+
+
+def get_console_script_specs(console: Dict[str, str]) -> List[str]:
+ """
+ Given the mapping from entrypoint name to callable, return the relevant
+ console script specs.
+ """
+ # Don't mutate caller's version
+ console = console.copy()
+
+ scripts_to_generate = []
+
+ # Special case pip and setuptools to generate versioned wrappers
+ #
+ # The issue is that some projects (specifically, pip and setuptools) use
+ # code in setup.py to create "versioned" entry points - pip2.7 on Python
+ # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into
+ # the wheel metadata at build time, and so if the wheel is installed with
+ # a *different* version of Python the entry points will be wrong. The
+ # correct fix for this is to enhance the metadata to be able to describe
+ # such versioned entry points.
+ # Currently, projects using versioned entry points will either have
+ # incorrect versioned entry points, or they will not be able to distribute
+ # "universal" wheels (i.e., they will need a wheel per Python version).
+ #
+ # Because setuptools and pip are bundled with _ensurepip and virtualenv,
+ # we need to use universal wheels. As a workaround, we
+ # override the versioned entry points in the wheel and generate the
+ # correct ones.
+ #
+ # To add the level of hack in this section of code, in order to support
+ # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment
+ # variable which will control which version scripts get installed.
+ #
+ # ENSUREPIP_OPTIONS=altinstall
+ # - Only pipX.Y and easy_install-X.Y will be generated and installed
+ # ENSUREPIP_OPTIONS=install
+ # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note
+ # that this option is technically if ENSUREPIP_OPTIONS is set and is
+ # not altinstall
+ # DEFAULT
+ # - The default behavior is to install pip, pipX, pipX.Y, easy_install
+ # and easy_install-X.Y.
+ pip_script = console.pop("pip", None)
+ if pip_script:
+ if "ENSUREPIP_OPTIONS" not in os.environ:
+ scripts_to_generate.append("pip = " + pip_script)
+
+ if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
+ scripts_to_generate.append(f"pip{sys.version_info[0]} = {pip_script}")
+
+ scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
+ # Delete any other versioned pip entry points
+ pip_ep = [k for k in console if re.match(r"pip(\d+(\.\d+)?)?$", k)]
+ for k in pip_ep:
+ del console[k]
+ easy_install_script = console.pop("easy_install", None)
+ if easy_install_script:
+ if "ENSUREPIP_OPTIONS" not in os.environ:
+ scripts_to_generate.append("easy_install = " + easy_install_script)
+
+ scripts_to_generate.append(
+ f"easy_install-{get_major_minor_version()} = {easy_install_script}"
+ )
+ # Delete any other versioned easy_install entry points
+ easy_install_ep = [
+ k for k in console if re.match(r"easy_install(-\d+\.\d+)?$", k)
+ ]
+ for k in easy_install_ep:
+ del console[k]
+
+ # Generate the console entry points specified in the wheel
+ scripts_to_generate.extend(starmap("{} = {}".format, console.items()))
+
+ return scripts_to_generate
+
+
+class ZipBackedFile:
+ def __init__(
+ self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile
+ ) -> None:
+ self.src_record_path = src_record_path
+ self.dest_path = dest_path
+ self._zip_file = zip_file
+ self.changed = False
+
+ def _getinfo(self) -> ZipInfo:
+ return self._zip_file.getinfo(self.src_record_path)
+
+ def save(self) -> None:
+ # When we open the output file below, any existing file is truncated
+ # before we start writing the new contents. This is fine in most
+ # cases, but can cause a segfault if pip has loaded a shared
+ # object (e.g. from pyopenssl through its vendored urllib3)
+ # Since the shared object is mmap'd an attempt to call a
+ # symbol in it will then cause a segfault. Unlinking the file
+ # allows writing of new contents while allowing the process to
+ # continue to use the old copy.
+ if os.path.exists(self.dest_path):
+ os.unlink(self.dest_path)
+
+ zipinfo = self._getinfo()
+
+ # optimization: the file is created by open(),
+ # skip the decompression when there is 0 bytes to decompress.
+ with open(self.dest_path, "wb") as dest:
+ if zipinfo.file_size > 0:
+ with self._zip_file.open(zipinfo) as f:
+ blocksize = min(zipinfo.file_size, 1024 * 1024)
+ shutil.copyfileobj(f, dest, blocksize)
+
+ if zip_item_is_executable(zipinfo):
+ set_extracted_file_to_default_mode_plus_executable(self.dest_path)
+
+
+class ScriptFile:
+ def __init__(self, file: "File") -> None:
+ self._file = file
+ self.src_record_path = self._file.src_record_path
+ self.dest_path = self._file.dest_path
+ self.changed = False
+
+ def save(self) -> None:
+ self._file.save()
+ self.changed = fix_script(self.dest_path)
+
+
+class MissingCallableSuffix(InstallationError):
+ def __init__(self, entry_point: str) -> None:
+ super().__init__(
+ f"Invalid script entry point: {entry_point} - A callable "
+ "suffix is required. Cf https://packaging.python.org/"
+ "specifications/entry-points/#use-for-scripts for more "
+ "information."
+ )
+
+
+def _raise_for_invalid_entrypoint(specification: str) -> None:
+ entry = get_export_entry(specification)
+ if entry is not None and entry.suffix is None:
+ raise MissingCallableSuffix(str(entry))
+
+
+class PipScriptMaker(ScriptMaker):
+ def make(
+ self, specification: str, options: Optional[Dict[str, Any]] = None
+ ) -> List[str]:
+ _raise_for_invalid_entrypoint(specification)
+ return super().make(specification, options)
+
+
+def _install_wheel( # noqa: C901, PLR0915 function is too long
+ name: str,
+ wheel_zip: ZipFile,
+ wheel_path: str,
+ scheme: Scheme,
+ pycompile: bool = True,
+ warn_script_location: bool = True,
+ direct_url: Optional[DirectUrl] = None,
+ requested: bool = False,
+) -> None:
+ """Install a wheel.
+
+ :param name: Name of the project to install
+ :param wheel_zip: open ZipFile for wheel being installed
+ :param scheme: Distutils scheme dictating the install directories
+ :param req_description: String used in place of the requirement, for
+ logging
+ :param pycompile: Whether to byte-compile installed Python files
+ :param warn_script_location: Whether to check that scripts are installed
+ into a directory on PATH
+ :raises UnsupportedWheel:
+ * when the directory holds an unpacked wheel with incompatible
+ Wheel-Version
+ * when the .dist-info dir does not match the wheel
+ """
+ info_dir, metadata = parse_wheel(wheel_zip, name)
+
+ if wheel_root_is_purelib(metadata):
+ lib_dir = scheme.purelib
+ else:
+ lib_dir = scheme.platlib
+
+ # Record details of the files moved
+ # installed = files copied from the wheel to the destination
+ # changed = files changed while installing (scripts #! line typically)
+ # generated = files newly generated during the install (script wrappers)
+ installed: Dict[RecordPath, RecordPath] = {}
+ changed: Set[RecordPath] = set()
+ generated: List[str] = []
+
+ def record_installed(
+ srcfile: RecordPath, destfile: str, modified: bool = False
+ ) -> None:
+ """Map archive RECORD paths to installation RECORD paths."""
+ newpath = _fs_to_record_path(destfile, lib_dir)
+ installed[srcfile] = newpath
+ if modified:
+ changed.add(newpath)
+
+ def is_dir_path(path: RecordPath) -> bool:
+ return path.endswith("/")
+
+ def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:
+ if not is_within_directory(dest_dir_path, target_path):
+ message = (
+ "The wheel {!r} has a file {!r} trying to install"
+ " outside the target directory {!r}"
+ )
+ raise InstallationError(
+ message.format(wheel_path, target_path, dest_dir_path)
+ )
+
+ def root_scheme_file_maker(
+ zip_file: ZipFile, dest: str
+ ) -> Callable[[RecordPath], "File"]:
+ def make_root_scheme_file(record_path: RecordPath) -> "File":
+ normed_path = os.path.normpath(record_path)
+ dest_path = os.path.join(dest, normed_path)
+ assert_no_path_traversal(dest, dest_path)
+ return ZipBackedFile(record_path, dest_path, zip_file)
+
+ return make_root_scheme_file
+
+ def data_scheme_file_maker(
+ zip_file: ZipFile, scheme: Scheme
+ ) -> Callable[[RecordPath], "File"]:
+ scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}
+
+ def make_data_scheme_file(record_path: RecordPath) -> "File":
+ normed_path = os.path.normpath(record_path)
+ try:
+ _, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
+ except ValueError:
+ message = (
+ f"Unexpected file in {wheel_path}: {record_path!r}. .data directory"
+ " contents should be named like: '/'."
+ )
+ raise InstallationError(message)
+
+ try:
+ scheme_path = scheme_paths[scheme_key]
+ except KeyError:
+ valid_scheme_keys = ", ".join(sorted(scheme_paths))
+ message = (
+ f"Unknown scheme key used in {wheel_path}: {scheme_key} "
+ f"(for file {record_path!r}). .data directory contents "
+ f"should be in subdirectories named with a valid scheme "
+ f"key ({valid_scheme_keys})"
+ )
+ raise InstallationError(message)
+
+ dest_path = os.path.join(scheme_path, dest_subpath)
+ assert_no_path_traversal(scheme_path, dest_path)
+ return ZipBackedFile(record_path, dest_path, zip_file)
+
+ return make_data_scheme_file
+
+ def is_data_scheme_path(path: RecordPath) -> bool:
+ return path.split("/", 1)[0].endswith(".data")
+
+ paths = cast(List[RecordPath], wheel_zip.namelist())
+ file_paths = filterfalse(is_dir_path, paths)
+ root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths)
+
+ make_root_scheme_file = root_scheme_file_maker(wheel_zip, lib_dir)
+ files: Iterator[File] = map(make_root_scheme_file, root_scheme_paths)
+
+ def is_script_scheme_path(path: RecordPath) -> bool:
+ parts = path.split("/", 2)
+ return len(parts) > 2 and parts[0].endswith(".data") and parts[1] == "scripts"
+
+ other_scheme_paths, script_scheme_paths = partition(
+ is_script_scheme_path, data_scheme_paths
+ )
+
+ make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme)
+ other_scheme_files = map(make_data_scheme_file, other_scheme_paths)
+ files = chain(files, other_scheme_files)
+
+ # Get the defined entry points
+ distribution = get_wheel_distribution(
+ FilesystemWheel(wheel_path),
+ canonicalize_name(name),
+ )
+ console, gui = get_entrypoints(distribution)
+
+ def is_entrypoint_wrapper(file: "File") -> bool:
+ # EP, EP.exe and EP-script.py are scripts generated for
+ # entry point EP by setuptools
+ path = file.dest_path
+ name = os.path.basename(path)
+ if name.lower().endswith(".exe"):
+ matchname = name[:-4]
+ elif name.lower().endswith("-script.py"):
+ matchname = name[:-10]
+ elif name.lower().endswith(".pya"):
+ matchname = name[:-4]
+ else:
+ matchname = name
+ # Ignore setuptools-generated scripts
+ return matchname in console or matchname in gui
+
+ script_scheme_files: Iterator[File] = map(
+ make_data_scheme_file, script_scheme_paths
+ )
+ script_scheme_files = filterfalse(is_entrypoint_wrapper, script_scheme_files)
+ script_scheme_files = map(ScriptFile, script_scheme_files)
+ files = chain(files, script_scheme_files)
+
+ existing_parents = set()
+ for file in files:
+ # directory creation is lazy and after file filtering
+ # to ensure we don't install empty dirs; empty dirs can't be
+ # uninstalled.
+ parent_dir = os.path.dirname(file.dest_path)
+ if parent_dir not in existing_parents:
+ ensure_dir(parent_dir)
+ existing_parents.add(parent_dir)
+ file.save()
+ record_installed(file.src_record_path, file.dest_path, file.changed)
+
+ def pyc_source_file_paths() -> Generator[str, None, None]:
+ # We de-duplicate installation paths, since there can be overlap (e.g.
+ # file in .data maps to same location as file in wheel root).
+ # Sorting installation paths makes it easier to reproduce and debug
+ # issues related to permissions on existing files.
+ for installed_path in sorted(set(installed.values())):
+ full_installed_path = os.path.join(lib_dir, installed_path)
+ if not os.path.isfile(full_installed_path):
+ continue
+ if not full_installed_path.endswith(".py"):
+ continue
+ yield full_installed_path
+
+ def pyc_output_path(path: str) -> str:
+ """Return the path the pyc file would have been written to."""
+ return importlib.util.cache_from_source(path)
+
+ # Compile all of the pyc files for the installed files
+ if pycompile:
+ with contextlib.redirect_stdout(
+ StreamWrapper.from_stream(sys.stdout)
+ ) as stdout:
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ for path in pyc_source_file_paths():
+ success = compileall.compile_file(path, force=True, quiet=True)
+ if success:
+ pyc_path = pyc_output_path(path)
+ assert os.path.exists(pyc_path)
+ pyc_record_path = cast(
+ "RecordPath", pyc_path.replace(os.path.sep, "/")
+ )
+ record_installed(pyc_record_path, pyc_path)
+ logger.debug(stdout.getvalue())
+
+ maker = PipScriptMaker(None, scheme.scripts)
+
+ # Ensure old scripts are overwritten.
+ # See https://github.com/pypa/pip/issues/1800
+ maker.clobber = True
+
+ # Ensure we don't generate any variants for scripts because this is almost
+ # never what somebody wants.
+ # See https://bitbucket.org/pypa/distlib/issue/35/
+ maker.variants = {""}
+
+ # This is required because otherwise distlib creates scripts that are not
+ # executable.
+ # See https://bitbucket.org/pypa/distlib/issue/32/
+ maker.set_mode = True
+
+ # Generate the console and GUI entry points specified in the wheel
+ scripts_to_generate = get_console_script_specs(console)
+
+ gui_scripts_to_generate = list(starmap("{} = {}".format, gui.items()))
+
+ generated_console_scripts = maker.make_multiple(scripts_to_generate)
+ generated.extend(generated_console_scripts)
+
+ generated.extend(maker.make_multiple(gui_scripts_to_generate, {"gui": True}))
+
+ if warn_script_location:
+ msg = message_about_scripts_not_on_PATH(generated_console_scripts)
+ if msg is not None:
+ logger.warning(msg)
+
+ generated_file_mode = 0o666 & ~current_umask()
+
+ @contextlib.contextmanager
+ def _generate_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]:
+ with adjacent_tmp_file(path, **kwargs) as f:
+ yield f
+ os.chmod(f.name, generated_file_mode)
+ replace(f.name, path)
+
+ dest_info_dir = os.path.join(lib_dir, info_dir)
+
+ # Record pip as the installer
+ installer_path = os.path.join(dest_info_dir, "INSTALLER")
+ with _generate_file(installer_path) as installer_file:
+ installer_file.write(b"pip\n")
+ generated.append(installer_path)
+
+ # Record the PEP 610 direct URL reference
+ if direct_url is not None:
+ direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME)
+ with _generate_file(direct_url_path) as direct_url_file:
+ direct_url_file.write(direct_url.to_json().encode("utf-8"))
+ generated.append(direct_url_path)
+
+ # Record the REQUESTED file
+ if requested:
+ requested_path = os.path.join(dest_info_dir, "REQUESTED")
+ with open(requested_path, "wb"):
+ pass
+ generated.append(requested_path)
+
+ record_text = distribution.read_text("RECORD")
+ record_rows = list(csv.reader(record_text.splitlines()))
+
+ rows = get_csv_rows_for_installed(
+ record_rows,
+ installed=installed,
+ changed=changed,
+ generated=generated,
+ lib_dir=lib_dir,
+ )
+
+ # Record details of all files installed
+ record_path = os.path.join(dest_info_dir, "RECORD")
+
+ with _generate_file(record_path, **csv_io_kwargs("w")) as record_file:
+ # Explicitly cast to typing.IO[str] as a workaround for the mypy error:
+ # "writer" has incompatible type "BinaryIO"; expected "_Writer"
+ writer = csv.writer(cast("IO[str]", record_file))
+ writer.writerows(_normalized_outrows(rows))
+
+
+@contextlib.contextmanager
+def req_error_context(req_description: str) -> Generator[None, None, None]:
+ try:
+ yield
+ except InstallationError as e:
+ message = f"For req: {req_description}. {e.args[0]}"
+ raise InstallationError(message) from e
+
+
+def install_wheel(
+ name: str,
+ wheel_path: str,
+ scheme: Scheme,
+ req_description: str,
+ pycompile: bool = True,
+ warn_script_location: bool = True,
+ direct_url: Optional[DirectUrl] = None,
+ requested: bool = False,
+) -> None:
+ with ZipFile(wheel_path, allowZip64=True) as z:
+ with req_error_context(req_description):
+ _install_wheel(
+ name=name,
+ wheel_zip=z,
+ wheel_path=wheel_path,
+ scheme=scheme,
+ pycompile=pycompile,
+ warn_script_location=warn_script_location,
+ direct_url=direct_url,
+ requested=requested,
+ )
diff --git a/env/Lib/site-packages/pip/_internal/operations/prepare.py b/env/Lib/site-packages/pip/_internal/operations/prepare.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6aa344720028f422840a720d26f5cfab358062c
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/operations/prepare.py
@@ -0,0 +1,732 @@
+"""Prepares a distribution for installation
+"""
+
+# The following comment should be removed at some point in the future.
+# mypy: strict-optional=False
+
+import mimetypes
+import os
+import shutil
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Dict, Iterable, List, Optional
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.distributions import make_distribution_for_install_requirement
+from pip._internal.distributions.installed import InstalledDistribution
+from pip._internal.exceptions import (
+ DirectoryUrlHashUnsupported,
+ HashMismatch,
+ HashUnpinned,
+ InstallationError,
+ MetadataInconsistent,
+ NetworkConnectionError,
+ VcsHashUnsupported,
+)
+from pip._internal.index.package_finder import PackageFinder
+from pip._internal.metadata import BaseDistribution, get_metadata_distribution
+from pip._internal.models.direct_url import ArchiveInfo
+from pip._internal.models.link import Link
+from pip._internal.models.wheel import Wheel
+from pip._internal.network.download import BatchDownloader, Downloader
+from pip._internal.network.lazy_wheel import (
+ HTTPRangeRequestUnsupported,
+ dist_from_wheel_url,
+)
+from pip._internal.network.session import PipSession
+from pip._internal.operations.build.build_tracker import BuildTracker
+from pip._internal.req.req_install import InstallRequirement
+from pip._internal.utils._log import getLogger
+from pip._internal.utils.direct_url_helpers import (
+ direct_url_for_editable,
+ direct_url_from_link,
+)
+from pip._internal.utils.hashes import Hashes, MissingHashes
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.misc import (
+ display_path,
+ hash_file,
+ hide_url,
+ redact_auth_from_requirement,
+)
+from pip._internal.utils.temp_dir import TempDirectory
+from pip._internal.utils.unpacking import unpack_file
+from pip._internal.vcs import vcs
+
+logger = getLogger(__name__)
+
+
+def _get_prepared_distribution(
+ req: InstallRequirement,
+ build_tracker: BuildTracker,
+ finder: PackageFinder,
+ build_isolation: bool,
+ check_build_deps: bool,
+) -> BaseDistribution:
+ """Prepare a distribution for installation."""
+ abstract_dist = make_distribution_for_install_requirement(req)
+ tracker_id = abstract_dist.build_tracker_id
+ if tracker_id is not None:
+ with build_tracker.track(req, tracker_id):
+ abstract_dist.prepare_distribution_metadata(
+ finder, build_isolation, check_build_deps
+ )
+ return abstract_dist.get_metadata_distribution()
+
+
+def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
+ vcs_backend = vcs.get_backend_for_scheme(link.scheme)
+ assert vcs_backend is not None
+ vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity)
+
+
+@dataclass
+class File:
+ path: str
+ content_type: Optional[str] = None
+
+ def __post_init__(self) -> None:
+ if self.content_type is None:
+ self.content_type = mimetypes.guess_type(self.path)[0]
+
+
+def get_http_url(
+ link: Link,
+ download: Downloader,
+ download_dir: Optional[str] = None,
+ hashes: Optional[Hashes] = None,
+) -> File:
+ temp_dir = TempDirectory(kind="unpack", globally_managed=True)
+ # If a download dir is specified, is the file already downloaded there?
+ already_downloaded_path = None
+ if download_dir:
+ already_downloaded_path = _check_download_dir(link, download_dir, hashes)
+
+ if already_downloaded_path:
+ from_path = already_downloaded_path
+ content_type = None
+ else:
+ # let's download to a tmp dir
+ from_path, content_type = download(link, temp_dir.path)
+ if hashes:
+ hashes.check_against_path(from_path)
+
+ return File(from_path, content_type)
+
+
+def get_file_url(
+ link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
+) -> File:
+ """Get file and optionally check its hash."""
+ # If a download dir is specified, is the file already there and valid?
+ already_downloaded_path = None
+ if download_dir:
+ already_downloaded_path = _check_download_dir(link, download_dir, hashes)
+
+ if already_downloaded_path:
+ from_path = already_downloaded_path
+ else:
+ from_path = link.file_path
+
+ # If --require-hashes is off, `hashes` is either empty, the
+ # link's embedded hash, or MissingHashes; it is required to
+ # match. If --require-hashes is on, we are satisfied by any
+ # hash in `hashes` matching: a URL-based or an option-based
+ # one; no internet-sourced hash will be in `hashes`.
+ if hashes:
+ hashes.check_against_path(from_path)
+ return File(from_path, None)
+
+
+def unpack_url(
+ link: Link,
+ location: str,
+ download: Downloader,
+ verbosity: int,
+ download_dir: Optional[str] = None,
+ hashes: Optional[Hashes] = None,
+) -> Optional[File]:
+ """Unpack link into location, downloading if required.
+
+ :param hashes: A Hashes object, one of whose embedded hashes must match,
+ or HashMismatch will be raised. If the Hashes is empty, no matches are
+ required, and unhashable types of requirements (like VCS ones, which
+ would ordinarily raise HashUnsupported) are allowed.
+ """
+ # non-editable vcs urls
+ if link.is_vcs:
+ unpack_vcs_link(link, location, verbosity=verbosity)
+ return None
+
+ assert not link.is_existing_dir()
+
+ # file urls
+ if link.is_file:
+ file = get_file_url(link, download_dir, hashes=hashes)
+
+ # http urls
+ else:
+ file = get_http_url(
+ link,
+ download,
+ download_dir,
+ hashes=hashes,
+ )
+
+ # unpack the archive to the build dir location. even when only downloading
+ # archives, they have to be unpacked to parse dependencies, except wheels
+ if not link.is_wheel:
+ unpack_file(file.path, location, file.content_type)
+
+ return file
+
+
+def _check_download_dir(
+ link: Link,
+ download_dir: str,
+ hashes: Optional[Hashes],
+ warn_on_hash_mismatch: bool = True,
+) -> Optional[str]:
+ """Check download_dir for previously downloaded file with correct hash
+ If a correct file is found return its path else None
+ """
+ download_path = os.path.join(download_dir, link.filename)
+
+ if not os.path.exists(download_path):
+ return None
+
+ # If already downloaded, does its hash match?
+ logger.info("File was already downloaded %s", download_path)
+ if hashes:
+ try:
+ hashes.check_against_path(download_path)
+ except HashMismatch:
+ if warn_on_hash_mismatch:
+ logger.warning(
+ "Previously-downloaded file %s has bad hash. Re-downloading.",
+ download_path,
+ )
+ os.unlink(download_path)
+ return None
+ return download_path
+
+
+class RequirementPreparer:
+ """Prepares a Requirement"""
+
+ def __init__(
+ self,
+ build_dir: str,
+ download_dir: Optional[str],
+ src_dir: str,
+ build_isolation: bool,
+ check_build_deps: bool,
+ build_tracker: BuildTracker,
+ session: PipSession,
+ progress_bar: str,
+ finder: PackageFinder,
+ require_hashes: bool,
+ use_user_site: bool,
+ lazy_wheel: bool,
+ verbosity: int,
+ legacy_resolver: bool,
+ ) -> None:
+ super().__init__()
+
+ self.src_dir = src_dir
+ self.build_dir = build_dir
+ self.build_tracker = build_tracker
+ self._session = session
+ self._download = Downloader(session, progress_bar)
+ self._batch_download = BatchDownloader(session, progress_bar)
+ self.finder = finder
+
+ # Where still-packed archives should be written to. If None, they are
+ # not saved, and are deleted immediately after unpacking.
+ self.download_dir = download_dir
+
+ # Is build isolation allowed?
+ self.build_isolation = build_isolation
+
+ # Should check build dependencies?
+ self.check_build_deps = check_build_deps
+
+ # Should hash-checking be required?
+ self.require_hashes = require_hashes
+
+ # Should install in user site-packages?
+ self.use_user_site = use_user_site
+
+ # Should wheels be downloaded lazily?
+ self.use_lazy_wheel = lazy_wheel
+
+ # How verbose should underlying tooling be?
+ self.verbosity = verbosity
+
+ # Are we using the legacy resolver?
+ self.legacy_resolver = legacy_resolver
+
+ # Memoized downloaded files, as mapping of url: path.
+ self._downloaded: Dict[str, str] = {}
+
+ # Previous "header" printed for a link-based InstallRequirement
+ self._previous_requirement_header = ("", "")
+
+ def _log_preparing_link(self, req: InstallRequirement) -> None:
+ """Provide context for the requirement being prepared."""
+ if req.link.is_file and not req.is_wheel_from_cache:
+ message = "Processing %s"
+ information = str(display_path(req.link.file_path))
+ else:
+ message = "Collecting %s"
+ information = redact_auth_from_requirement(req.req) if req.req else str(req)
+
+ # If we used req.req, inject requirement source if available (this
+ # would already be included if we used req directly)
+ if req.req and req.comes_from:
+ if isinstance(req.comes_from, str):
+ comes_from: Optional[str] = req.comes_from
+ else:
+ comes_from = req.comes_from.from_path()
+ if comes_from:
+ information += f" (from {comes_from})"
+
+ if (message, information) != self._previous_requirement_header:
+ self._previous_requirement_header = (message, information)
+ logger.info(message, information)
+
+ if req.is_wheel_from_cache:
+ with indent_log():
+ logger.info("Using cached %s", req.link.filename)
+
+ def _ensure_link_req_src_dir(
+ self, req: InstallRequirement, parallel_builds: bool
+ ) -> None:
+ """Ensure source_dir of a linked InstallRequirement."""
+ # Since source_dir is only set for editable requirements.
+ if req.link.is_wheel:
+ # We don't need to unpack wheels, so no need for a source
+ # directory.
+ return
+ assert req.source_dir is None
+ if req.link.is_existing_dir():
+ # build local directories in-tree
+ req.source_dir = req.link.file_path
+ return
+
+ # We always delete unpacked sdists after pip runs.
+ req.ensure_has_source_dir(
+ self.build_dir,
+ autodelete=True,
+ parallel_builds=parallel_builds,
+ )
+ req.ensure_pristine_source_checkout()
+
+ def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
+ # By the time this is called, the requirement's link should have
+ # been checked so we can tell what kind of requirements req is
+ # and raise some more informative errors than otherwise.
+ # (For example, we can raise VcsHashUnsupported for a VCS URL
+ # rather than HashMissing.)
+ if not self.require_hashes:
+ return req.hashes(trust_internet=True)
+
+ # We could check these first 2 conditions inside unpack_url
+ # and save repetition of conditions, but then we would
+ # report less-useful error messages for unhashable
+ # requirements, complaining that there's no hash provided.
+ if req.link.is_vcs:
+ raise VcsHashUnsupported()
+ if req.link.is_existing_dir():
+ raise DirectoryUrlHashUnsupported()
+
+ # Unpinned packages are asking for trouble when a new version
+ # is uploaded. This isn't a security check, but it saves users
+ # a surprising hash mismatch in the future.
+ # file:/// URLs aren't pinnable, so don't complain about them
+ # not being pinned.
+ if not req.is_direct and not req.is_pinned:
+ raise HashUnpinned()
+
+ # If known-good hashes are missing for this requirement,
+ # shim it with a facade object that will provoke hash
+ # computation and then raise a HashMissing exception
+ # showing the user what the hash should be.
+ return req.hashes(trust_internet=False) or MissingHashes()
+
+ def _fetch_metadata_only(
+ self,
+ req: InstallRequirement,
+ ) -> Optional[BaseDistribution]:
+ if self.legacy_resolver:
+ logger.debug(
+ "Metadata-only fetching is not used in the legacy resolver",
+ )
+ return None
+ if self.require_hashes:
+ logger.debug(
+ "Metadata-only fetching is not used as hash checking is required",
+ )
+ return None
+ # Try PEP 658 metadata first, then fall back to lazy wheel if unavailable.
+ return self._fetch_metadata_using_link_data_attr(
+ req
+ ) or self._fetch_metadata_using_lazy_wheel(req.link)
+
+ def _fetch_metadata_using_link_data_attr(
+ self,
+ req: InstallRequirement,
+ ) -> Optional[BaseDistribution]:
+ """Fetch metadata from the data-dist-info-metadata attribute, if possible."""
+ # (1) Get the link to the metadata file, if provided by the backend.
+ metadata_link = req.link.metadata_link()
+ if metadata_link is None:
+ return None
+ assert req.req is not None
+ logger.verbose(
+ "Obtaining dependency information for %s from %s",
+ req.req,
+ metadata_link,
+ )
+ # (2) Download the contents of the METADATA file, separate from the dist itself.
+ metadata_file = get_http_url(
+ metadata_link,
+ self._download,
+ hashes=metadata_link.as_hashes(),
+ )
+ with open(metadata_file.path, "rb") as f:
+ metadata_contents = f.read()
+ # (3) Generate a dist just from those file contents.
+ metadata_dist = get_metadata_distribution(
+ metadata_contents,
+ req.link.filename,
+ req.req.name,
+ )
+ # (4) Ensure the Name: field from the METADATA file matches the name from the
+ # install requirement.
+ #
+ # NB: raw_name will fall back to the name from the install requirement if
+ # the Name: field is not present, but it's noted in the raw_name docstring
+ # that that should NEVER happen anyway.
+ if canonicalize_name(metadata_dist.raw_name) != canonicalize_name(req.req.name):
+ raise MetadataInconsistent(
+ req, "Name", req.req.name, metadata_dist.raw_name
+ )
+ return metadata_dist
+
+ def _fetch_metadata_using_lazy_wheel(
+ self,
+ link: Link,
+ ) -> Optional[BaseDistribution]:
+ """Fetch metadata using lazy wheel, if possible."""
+ # --use-feature=fast-deps must be provided.
+ if not self.use_lazy_wheel:
+ return None
+ if link.is_file or not link.is_wheel:
+ logger.debug(
+ "Lazy wheel is not used as %r does not point to a remote wheel",
+ link,
+ )
+ return None
+
+ wheel = Wheel(link.filename)
+ name = canonicalize_name(wheel.name)
+ logger.info(
+ "Obtaining dependency information from %s %s",
+ name,
+ wheel.version,
+ )
+ url = link.url.split("#", 1)[0]
+ try:
+ return dist_from_wheel_url(name, url, self._session)
+ except HTTPRangeRequestUnsupported:
+ logger.debug("%s does not support range requests", url)
+ return None
+
+ def _complete_partial_requirements(
+ self,
+ partially_downloaded_reqs: Iterable[InstallRequirement],
+ parallel_builds: bool = False,
+ ) -> None:
+ """Download any requirements which were only fetched by metadata."""
+ # Download to a temporary directory. These will be copied over as
+ # needed for downstream 'download', 'wheel', and 'install' commands.
+ temp_dir = TempDirectory(kind="unpack", globally_managed=True).path
+
+ # Map each link to the requirement that owns it. This allows us to set
+ # `req.local_file_path` on the appropriate requirement after passing
+ # all the links at once into BatchDownloader.
+ links_to_fully_download: Dict[Link, InstallRequirement] = {}
+ for req in partially_downloaded_reqs:
+ assert req.link
+ links_to_fully_download[req.link] = req
+
+ batch_download = self._batch_download(
+ links_to_fully_download.keys(),
+ temp_dir,
+ )
+ for link, (filepath, _) in batch_download:
+ logger.debug("Downloading link %s to %s", link, filepath)
+ req = links_to_fully_download[link]
+ # Record the downloaded file path so wheel reqs can extract a Distribution
+ # in .get_dist().
+ req.local_file_path = filepath
+ # Record that the file is downloaded so we don't do it again in
+ # _prepare_linked_requirement().
+ self._downloaded[req.link.url] = filepath
+
+ # If this is an sdist, we need to unpack it after downloading, but the
+ # .source_dir won't be set up until we are in _prepare_linked_requirement().
+ # Add the downloaded archive to the install requirement to unpack after
+ # preparing the source dir.
+ if not req.is_wheel:
+ req.needs_unpacked_archive(Path(filepath))
+
+ # This step is necessary to ensure all lazy wheels are processed
+ # successfully by the 'download', 'wheel', and 'install' commands.
+ for req in partially_downloaded_reqs:
+ self._prepare_linked_requirement(req, parallel_builds)
+
+ def prepare_linked_requirement(
+ self, req: InstallRequirement, parallel_builds: bool = False
+ ) -> BaseDistribution:
+ """Prepare a requirement to be obtained from req.link."""
+ assert req.link
+ self._log_preparing_link(req)
+ with indent_log():
+ # Check if the relevant file is already available
+ # in the download directory
+ file_path = None
+ if self.download_dir is not None and req.link.is_wheel:
+ hashes = self._get_linked_req_hashes(req)
+ file_path = _check_download_dir(
+ req.link,
+ self.download_dir,
+ hashes,
+ # When a locally built wheel has been found in cache, we don't warn
+ # about re-downloading when the already downloaded wheel hash does
+ # not match. This is because the hash must be checked against the
+ # original link, not the cached link. It that case the already
+ # downloaded file will be removed and re-fetched from cache (which
+ # implies a hash check against the cache entry's origin.json).
+ warn_on_hash_mismatch=not req.is_wheel_from_cache,
+ )
+
+ if file_path is not None:
+ # The file is already available, so mark it as downloaded
+ self._downloaded[req.link.url] = file_path
+ else:
+ # The file is not available, attempt to fetch only metadata
+ metadata_dist = self._fetch_metadata_only(req)
+ if metadata_dist is not None:
+ req.needs_more_preparation = True
+ return metadata_dist
+
+ # None of the optimizations worked, fully prepare the requirement
+ return self._prepare_linked_requirement(req, parallel_builds)
+
+ def prepare_linked_requirements_more(
+ self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False
+ ) -> None:
+ """Prepare linked requirements more, if needed."""
+ reqs = [req for req in reqs if req.needs_more_preparation]
+ for req in reqs:
+ # Determine if any of these requirements were already downloaded.
+ if self.download_dir is not None and req.link.is_wheel:
+ hashes = self._get_linked_req_hashes(req)
+ file_path = _check_download_dir(req.link, self.download_dir, hashes)
+ if file_path is not None:
+ self._downloaded[req.link.url] = file_path
+ req.needs_more_preparation = False
+
+ # Prepare requirements we found were already downloaded for some
+ # reason. The other downloads will be completed separately.
+ partially_downloaded_reqs: List[InstallRequirement] = []
+ for req in reqs:
+ if req.needs_more_preparation:
+ partially_downloaded_reqs.append(req)
+ else:
+ self._prepare_linked_requirement(req, parallel_builds)
+
+ # TODO: separate this part out from RequirementPreparer when the v1
+ # resolver can be removed!
+ self._complete_partial_requirements(
+ partially_downloaded_reqs,
+ parallel_builds=parallel_builds,
+ )
+
+ def _prepare_linked_requirement(
+ self, req: InstallRequirement, parallel_builds: bool
+ ) -> BaseDistribution:
+ assert req.link
+ link = req.link
+
+ hashes = self._get_linked_req_hashes(req)
+
+ if hashes and req.is_wheel_from_cache:
+ assert req.download_info is not None
+ assert link.is_wheel
+ assert link.is_file
+ # We need to verify hashes, and we have found the requirement in the cache
+ # of locally built wheels.
+ if (
+ isinstance(req.download_info.info, ArchiveInfo)
+ and req.download_info.info.hashes
+ and hashes.has_one_of(req.download_info.info.hashes)
+ ):
+ # At this point we know the requirement was built from a hashable source
+ # artifact, and we verified that the cache entry's hash of the original
+ # artifact matches one of the hashes we expect. We don't verify hashes
+ # against the cached wheel, because the wheel is not the original.
+ hashes = None
+ else:
+ logger.warning(
+ "The hashes of the source archive found in cache entry "
+ "don't match, ignoring cached built wheel "
+ "and re-downloading source."
+ )
+ req.link = req.cached_wheel_source_link
+ link = req.link
+
+ self._ensure_link_req_src_dir(req, parallel_builds)
+
+ if link.is_existing_dir():
+ local_file = None
+ elif link.url not in self._downloaded:
+ try:
+ local_file = unpack_url(
+ link,
+ req.source_dir,
+ self._download,
+ self.verbosity,
+ self.download_dir,
+ hashes,
+ )
+ except NetworkConnectionError as exc:
+ raise InstallationError(
+ f"Could not install requirement {req} because of HTTP "
+ f"error {exc} for URL {link}"
+ )
+ else:
+ file_path = self._downloaded[link.url]
+ if hashes:
+ hashes.check_against_path(file_path)
+ local_file = File(file_path, content_type=None)
+
+ # If download_info is set, we got it from the wheel cache.
+ if req.download_info is None:
+ # Editables don't go through this function (see
+ # prepare_editable_requirement).
+ assert not req.editable
+ req.download_info = direct_url_from_link(link, req.source_dir)
+ # Make sure we have a hash in download_info. If we got it as part of the
+ # URL, it will have been verified and we can rely on it. Otherwise we
+ # compute it from the downloaded file.
+ # FIXME: https://github.com/pypa/pip/issues/11943
+ if (
+ isinstance(req.download_info.info, ArchiveInfo)
+ and not req.download_info.info.hashes
+ and local_file
+ ):
+ hash = hash_file(local_file.path)[0].hexdigest()
+ # We populate info.hash for backward compatibility.
+ # This will automatically populate info.hashes.
+ req.download_info.info.hash = f"sha256={hash}"
+
+ # For use in later processing,
+ # preserve the file path on the requirement.
+ if local_file:
+ req.local_file_path = local_file.path
+
+ dist = _get_prepared_distribution(
+ req,
+ self.build_tracker,
+ self.finder,
+ self.build_isolation,
+ self.check_build_deps,
+ )
+ return dist
+
+ def save_linked_requirement(self, req: InstallRequirement) -> None:
+ assert self.download_dir is not None
+ assert req.link is not None
+ link = req.link
+ if link.is_vcs or (link.is_existing_dir() and req.editable):
+ # Make a .zip of the source_dir we already created.
+ req.archive(self.download_dir)
+ return
+
+ if link.is_existing_dir():
+ logger.debug(
+ "Not copying link to destination directory "
+ "since it is a directory: %s",
+ link,
+ )
+ return
+ if req.local_file_path is None:
+ # No distribution was downloaded for this requirement.
+ return
+
+ download_location = os.path.join(self.download_dir, link.filename)
+ if not os.path.exists(download_location):
+ shutil.copy(req.local_file_path, download_location)
+ download_path = display_path(download_location)
+ logger.info("Saved %s", download_path)
+
+ def prepare_editable_requirement(
+ self,
+ req: InstallRequirement,
+ ) -> BaseDistribution:
+ """Prepare an editable requirement."""
+ assert req.editable, "cannot prepare a non-editable req as editable"
+
+ logger.info("Obtaining %s", req)
+
+ with indent_log():
+ if self.require_hashes:
+ raise InstallationError(
+ f"The editable requirement {req} cannot be installed when "
+ "requiring hashes, because there is no single file to "
+ "hash."
+ )
+ req.ensure_has_source_dir(self.src_dir)
+ req.update_editable()
+ assert req.source_dir
+ req.download_info = direct_url_for_editable(req.unpacked_source_directory)
+
+ dist = _get_prepared_distribution(
+ req,
+ self.build_tracker,
+ self.finder,
+ self.build_isolation,
+ self.check_build_deps,
+ )
+
+ req.check_if_exists(self.use_user_site)
+
+ return dist
+
+ def prepare_installed_requirement(
+ self,
+ req: InstallRequirement,
+ skip_reason: str,
+ ) -> BaseDistribution:
+ """Prepare an already-installed requirement."""
+ assert req.satisfied_by, "req should have been satisfied but isn't"
+ assert skip_reason is not None, (
+ "did not get skip reason skipped but req.satisfied_by "
+ f"is set to {req.satisfied_by}"
+ )
+ logger.info(
+ "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version
+ )
+ with indent_log():
+ if self.require_hashes:
+ logger.debug(
+ "Since it is already installed, we are trusting this "
+ "package without checking its hash. To ensure a "
+ "completely repeatable environment, install into an "
+ "empty virtualenv."
+ )
+ return InstalledDistribution(req).get_metadata_distribution()
diff --git a/env/Lib/site-packages/pip/_internal/pyproject.py b/env/Lib/site-packages/pip/_internal/pyproject.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a9cad4803e958ad701aee8eee55bd646ac25d83
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/pyproject.py
@@ -0,0 +1,185 @@
+import importlib.util
+import os
+import sys
+from collections import namedtuple
+from typing import Any, List, Optional
+
+if sys.version_info >= (3, 11):
+ import tomllib
+else:
+ from pip._vendor import tomli as tomllib
+
+from pip._vendor.packaging.requirements import InvalidRequirement
+
+from pip._internal.exceptions import (
+ InstallationError,
+ InvalidPyProjectBuildRequires,
+ MissingPyProjectBuildRequires,
+)
+from pip._internal.utils.packaging import get_requirement
+
+
+def _is_list_of_str(obj: Any) -> bool:
+ return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
+
+
+def make_pyproject_path(unpacked_source_directory: str) -> str:
+ return os.path.join(unpacked_source_directory, "pyproject.toml")
+
+
+BuildSystemDetails = namedtuple(
+ "BuildSystemDetails", ["requires", "backend", "check", "backend_path"]
+)
+
+
+def load_pyproject_toml(
+ use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
+) -> Optional[BuildSystemDetails]:
+ """Load the pyproject.toml file.
+
+ Parameters:
+ use_pep517 - Has the user requested PEP 517 processing? None
+ means the user hasn't explicitly specified.
+ pyproject_toml - Location of the project's pyproject.toml file
+ setup_py - Location of the project's setup.py file
+ req_name - The name of the requirement we're processing (for
+ error reporting)
+
+ Returns:
+ None if we should use the legacy code path, otherwise a tuple
+ (
+ requirements from pyproject.toml,
+ name of PEP 517 backend,
+ requirements we should check are installed after setting
+ up the build environment
+ directory paths to import the backend from (backend-path),
+ relative to the project root.
+ )
+ """
+ has_pyproject = os.path.isfile(pyproject_toml)
+ has_setup = os.path.isfile(setup_py)
+
+ if not has_pyproject and not has_setup:
+ raise InstallationError(
+ f"{req_name} does not appear to be a Python project: "
+ f"neither 'setup.py' nor 'pyproject.toml' found."
+ )
+
+ if has_pyproject:
+ with open(pyproject_toml, encoding="utf-8") as f:
+ pp_toml = tomllib.loads(f.read())
+ build_system = pp_toml.get("build-system")
+ else:
+ build_system = None
+
+ # The following cases must use PEP 517
+ # We check for use_pep517 being non-None and falsey because that means
+ # the user explicitly requested --no-use-pep517. The value 0 as
+ # opposed to False can occur when the value is provided via an
+ # environment variable or config file option (due to the quirk of
+ # strtobool() returning an integer in pip's configuration code).
+ if has_pyproject and not has_setup:
+ if use_pep517 is not None and not use_pep517:
+ raise InstallationError(
+ "Disabling PEP 517 processing is invalid: "
+ "project does not have a setup.py"
+ )
+ use_pep517 = True
+ elif build_system and "build-backend" in build_system:
+ if use_pep517 is not None and not use_pep517:
+ raise InstallationError(
+ "Disabling PEP 517 processing is invalid: "
+ "project specifies a build backend of {} "
+ "in pyproject.toml".format(build_system["build-backend"])
+ )
+ use_pep517 = True
+
+ # If we haven't worked out whether to use PEP 517 yet,
+ # and the user hasn't explicitly stated a preference,
+ # we do so if the project has a pyproject.toml file
+ # or if we cannot import setuptools or wheels.
+
+ # We fallback to PEP 517 when without setuptools or without the wheel package,
+ # so setuptools can be installed as a default build backend.
+ # For more info see:
+ # https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9
+ # https://github.com/pypa/pip/issues/8559
+ elif use_pep517 is None:
+ use_pep517 = (
+ has_pyproject
+ or not importlib.util.find_spec("setuptools")
+ or not importlib.util.find_spec("wheel")
+ )
+
+ # At this point, we know whether we're going to use PEP 517.
+ assert use_pep517 is not None
+
+ # If we're using the legacy code path, there is nothing further
+ # for us to do here.
+ if not use_pep517:
+ return None
+
+ if build_system is None:
+ # Either the user has a pyproject.toml with no build-system
+ # section, or the user has no pyproject.toml, but has opted in
+ # explicitly via --use-pep517.
+ # In the absence of any explicit backend specification, we
+ # assume the setuptools backend that most closely emulates the
+ # traditional direct setup.py execution, and require wheel and
+ # a version of setuptools that supports that backend.
+
+ build_system = {
+ "requires": ["setuptools>=40.8.0"],
+ "build-backend": "setuptools.build_meta:__legacy__",
+ }
+
+ # If we're using PEP 517, we have build system information (either
+ # from pyproject.toml, or defaulted by the code above).
+ # Note that at this point, we do not know if the user has actually
+ # specified a backend, though.
+ assert build_system is not None
+
+ # Ensure that the build-system section in pyproject.toml conforms
+ # to PEP 518.
+
+ # Specifying the build-system table but not the requires key is invalid
+ if "requires" not in build_system:
+ raise MissingPyProjectBuildRequires(package=req_name)
+
+ # Error out if requires is not a list of strings
+ requires = build_system["requires"]
+ if not _is_list_of_str(requires):
+ raise InvalidPyProjectBuildRequires(
+ package=req_name,
+ reason="It is not a list of strings.",
+ )
+
+ # Each requirement must be valid as per PEP 508
+ for requirement in requires:
+ try:
+ get_requirement(requirement)
+ except InvalidRequirement as error:
+ raise InvalidPyProjectBuildRequires(
+ package=req_name,
+ reason=f"It contains an invalid requirement: {requirement!r}",
+ ) from error
+
+ backend = build_system.get("build-backend")
+ backend_path = build_system.get("backend-path", [])
+ check: List[str] = []
+ if backend is None:
+ # If the user didn't specify a backend, we assume they want to use
+ # the setuptools backend. But we can't be sure they have included
+ # a version of setuptools which supplies the backend. So we
+ # make a note to check that this requirement is present once
+ # we have set up the environment.
+ # This is quite a lot of work to check for a very specific case. But
+ # the problem is, that case is potentially quite common - projects that
+ # adopted PEP 518 early for the ability to specify requirements to
+ # execute setup.py, but never considered needing to mention the build
+ # tools themselves. The original PEP 518 code had a similar check (but
+ # implemented in a different way).
+ backend = "setuptools.build_meta:__legacy__"
+ check = ["setuptools>=40.8.0"]
+
+ return BuildSystemDetails(requires, backend, check, backend_path)
diff --git a/env/Lib/site-packages/pip/_internal/req/__init__.py b/env/Lib/site-packages/pip/_internal/req/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..422d851d7293d702f1a4c5e79f92b428703dc852
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/req/__init__.py
@@ -0,0 +1,90 @@
+import collections
+import logging
+from dataclasses import dataclass
+from typing import Generator, List, Optional, Sequence, Tuple
+
+from pip._internal.utils.logging import indent_log
+
+from .req_file import parse_requirements
+from .req_install import InstallRequirement
+from .req_set import RequirementSet
+
+__all__ = [
+ "RequirementSet",
+ "InstallRequirement",
+ "parse_requirements",
+ "install_given_reqs",
+]
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass(frozen=True)
+class InstallationResult:
+ name: str
+
+
+def _validate_requirements(
+ requirements: List[InstallRequirement],
+) -> Generator[Tuple[str, InstallRequirement], None, None]:
+ for req in requirements:
+ assert req.name, f"invalid to-be-installed requirement: {req}"
+ yield req.name, req
+
+
+def install_given_reqs(
+ requirements: List[InstallRequirement],
+ global_options: Sequence[str],
+ root: Optional[str],
+ home: Optional[str],
+ prefix: Optional[str],
+ warn_script_location: bool,
+ use_user_site: bool,
+ pycompile: bool,
+) -> List[InstallationResult]:
+ """
+ Install everything in the given list.
+
+ (to be called after having downloaded and unpacked the packages)
+ """
+ to_install = collections.OrderedDict(_validate_requirements(requirements))
+
+ if to_install:
+ logger.info(
+ "Installing collected packages: %s",
+ ", ".join(to_install.keys()),
+ )
+
+ installed = []
+
+ with indent_log():
+ for req_name, requirement in to_install.items():
+ if requirement.should_reinstall:
+ logger.info("Attempting uninstall: %s", req_name)
+ with indent_log():
+ uninstalled_pathset = requirement.uninstall(auto_confirm=True)
+ else:
+ uninstalled_pathset = None
+
+ try:
+ requirement.install(
+ global_options,
+ root=root,
+ home=home,
+ prefix=prefix,
+ warn_script_location=warn_script_location,
+ use_user_site=use_user_site,
+ pycompile=pycompile,
+ )
+ except Exception:
+ # if install did not succeed, rollback previous uninstall
+ if uninstalled_pathset and not requirement.install_succeeded:
+ uninstalled_pathset.rollback()
+ raise
+ else:
+ if uninstalled_pathset and requirement.install_succeeded:
+ uninstalled_pathset.commit()
+
+ installed.append(InstallationResult(req_name))
+
+ return installed
diff --git a/env/Lib/site-packages/pip/_internal/req/constructors.py b/env/Lib/site-packages/pip/_internal/req/constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..56a964f3177dc47d747ccf55361fccb8aea70ebf
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/req/constructors.py
@@ -0,0 +1,560 @@
+"""Backing implementation for InstallRequirement's various constructors
+
+The idea here is that these formed a major chunk of InstallRequirement's size
+so, moving them and support code dedicated to them outside of that class
+helps creates for better understandability for the rest of the code.
+
+These are meant to be used elsewhere within pip to create instances of
+InstallRequirement.
+"""
+
+import copy
+import logging
+import os
+import re
+from dataclasses import dataclass
+from typing import Collection, Dict, List, Optional, Set, Tuple, Union
+
+from pip._vendor.packaging.markers import Marker
+from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
+from pip._vendor.packaging.specifiers import Specifier
+
+from pip._internal.exceptions import InstallationError
+from pip._internal.models.index import PyPI, TestPyPI
+from pip._internal.models.link import Link
+from pip._internal.models.wheel import Wheel
+from pip._internal.req.req_file import ParsedRequirement
+from pip._internal.req.req_install import InstallRequirement
+from pip._internal.utils.filetypes import is_archive_file
+from pip._internal.utils.misc import is_installable_dir
+from pip._internal.utils.packaging import get_requirement
+from pip._internal.utils.urls import path_to_url
+from pip._internal.vcs import is_url, vcs
+
+__all__ = [
+ "install_req_from_editable",
+ "install_req_from_line",
+ "parse_editable",
+]
+
+logger = logging.getLogger(__name__)
+operators = Specifier._operators.keys()
+
+
+def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
+ m = re.match(r"^(.+)(\[[^\]]+\])$", path)
+ extras = None
+ if m:
+ path_no_extras = m.group(1)
+ extras = m.group(2)
+ else:
+ path_no_extras = path
+
+ return path_no_extras, extras
+
+
+def convert_extras(extras: Optional[str]) -> Set[str]:
+ if not extras:
+ return set()
+ return get_requirement("placeholder" + extras.lower()).extras
+
+
+def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requirement:
+ """
+ Returns a new requirement based on the given one, with the supplied extras. If the
+ given requirement already has extras those are replaced (or dropped if no new extras
+ are given).
+ """
+ match: Optional[re.Match[str]] = re.fullmatch(
+ # see https://peps.python.org/pep-0508/#complete-grammar
+ r"([\w\t .-]+)(\[[^\]]*\])?(.*)",
+ str(req),
+ flags=re.ASCII,
+ )
+ # ireq.req is a valid requirement so the regex should always match
+ assert (
+ match is not None
+ ), f"regex match on requirement {req} failed, this should never happen"
+ pre: Optional[str] = match.group(1)
+ post: Optional[str] = match.group(3)
+ assert (
+ pre is not None and post is not None
+ ), f"regex group selection for requirement {req} failed, this should never happen"
+ extras: str = "[{}]".format(",".join(sorted(new_extras)) if new_extras else "")
+ return get_requirement(f"{pre}{extras}{post}")
+
+
+def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
+ """Parses an editable requirement into:
+ - a requirement name
+ - an URL
+ - extras
+ - editable options
+ Accepted requirements:
+ svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
+ .[some_extra]
+ """
+
+ url = editable_req
+
+ # If a file path is specified with extras, strip off the extras.
+ url_no_extras, extras = _strip_extras(url)
+
+ if os.path.isdir(url_no_extras):
+ # Treating it as code that has already been checked out
+ url_no_extras = path_to_url(url_no_extras)
+
+ if url_no_extras.lower().startswith("file:"):
+ package_name = Link(url_no_extras).egg_fragment
+ if extras:
+ return (
+ package_name,
+ url_no_extras,
+ get_requirement("placeholder" + extras.lower()).extras,
+ )
+ else:
+ return package_name, url_no_extras, set()
+
+ for version_control in vcs:
+ if url.lower().startswith(f"{version_control}:"):
+ url = f"{version_control}+{url}"
+ break
+
+ link = Link(url)
+
+ if not link.is_vcs:
+ backends = ", ".join(vcs.all_schemes)
+ raise InstallationError(
+ f"{editable_req} is not a valid editable requirement. "
+ f"It should either be a path to a local project or a VCS URL "
+ f"(beginning with {backends})."
+ )
+
+ package_name = link.egg_fragment
+ if not package_name:
+ raise InstallationError(
+ f"Could not detect requirement name for '{editable_req}', "
+ "please specify one with #egg=your_package_name"
+ )
+ return package_name, url, set()
+
+
+def check_first_requirement_in_file(filename: str) -> None:
+ """Check if file is parsable as a requirements file.
+
+ This is heavily based on ``pkg_resources.parse_requirements``, but
+ simplified to just check the first meaningful line.
+
+ :raises InvalidRequirement: If the first meaningful line cannot be parsed
+ as an requirement.
+ """
+ with open(filename, encoding="utf-8", errors="ignore") as f:
+ # Create a steppable iterator, so we can handle \-continuations.
+ lines = (
+ line
+ for line in (line.strip() for line in f)
+ if line and not line.startswith("#") # Skip blank lines/comments.
+ )
+
+ for line in lines:
+ # Drop comments -- a hash without a space may be in a URL.
+ if " #" in line:
+ line = line[: line.find(" #")]
+ # If there is a line continuation, drop it, and append the next line.
+ if line.endswith("\\"):
+ line = line[:-2].strip() + next(lines, "")
+ get_requirement(line)
+ return
+
+
+def deduce_helpful_msg(req: str) -> str:
+ """Returns helpful msg in case requirements file does not exist,
+ or cannot be parsed.
+
+ :params req: Requirements file path
+ """
+ if not os.path.exists(req):
+ return f" File '{req}' does not exist."
+ msg = " The path does exist. "
+ # Try to parse and check if it is a requirements file.
+ try:
+ check_first_requirement_in_file(req)
+ except InvalidRequirement:
+ logger.debug("Cannot parse '%s' as requirements file", req)
+ else:
+ msg += (
+ f"The argument you provided "
+ f"({req}) appears to be a"
+ f" requirements file. If that is the"
+ f" case, use the '-r' flag to install"
+ f" the packages specified within it."
+ )
+ return msg
+
+
+@dataclass(frozen=True)
+class RequirementParts:
+ requirement: Optional[Requirement]
+ link: Optional[Link]
+ markers: Optional[Marker]
+ extras: Set[str]
+
+
+def parse_req_from_editable(editable_req: str) -> RequirementParts:
+ name, url, extras_override = parse_editable(editable_req)
+
+ if name is not None:
+ try:
+ req: Optional[Requirement] = get_requirement(name)
+ except InvalidRequirement as exc:
+ raise InstallationError(f"Invalid requirement: {name!r}: {exc}")
+ else:
+ req = None
+
+ link = Link(url)
+
+ return RequirementParts(req, link, None, extras_override)
+
+
+# ---- The actual constructors follow ----
+
+
+def install_req_from_editable(
+ editable_req: str,
+ comes_from: Optional[Union[InstallRequirement, str]] = None,
+ *,
+ use_pep517: Optional[bool] = None,
+ isolated: bool = False,
+ global_options: Optional[List[str]] = None,
+ hash_options: Optional[Dict[str, List[str]]] = None,
+ constraint: bool = False,
+ user_supplied: bool = False,
+ permit_editable_wheels: bool = False,
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+) -> InstallRequirement:
+ parts = parse_req_from_editable(editable_req)
+
+ return InstallRequirement(
+ parts.requirement,
+ comes_from=comes_from,
+ user_supplied=user_supplied,
+ editable=True,
+ permit_editable_wheels=permit_editable_wheels,
+ link=parts.link,
+ constraint=constraint,
+ use_pep517=use_pep517,
+ isolated=isolated,
+ global_options=global_options,
+ hash_options=hash_options,
+ config_settings=config_settings,
+ extras=parts.extras,
+ )
+
+
+def _looks_like_path(name: str) -> bool:
+ """Checks whether the string "looks like" a path on the filesystem.
+
+ This does not check whether the target actually exists, only judge from the
+ appearance.
+
+ Returns true if any of the following conditions is true:
+ * a path separator is found (either os.path.sep or os.path.altsep);
+ * a dot is found (which represents the current directory).
+ """
+ if os.path.sep in name:
+ return True
+ if os.path.altsep is not None and os.path.altsep in name:
+ return True
+ if name.startswith("."):
+ return True
+ return False
+
+
+def _get_url_from_path(path: str, name: str) -> Optional[str]:
+ """
+ First, it checks whether a provided path is an installable directory. If it
+ is, returns the path.
+
+ If false, check if the path is an archive file (such as a .whl).
+ The function checks if the path is a file. If false, if the path has
+ an @, it will treat it as a PEP 440 URL requirement and return the path.
+ """
+ if _looks_like_path(name) and os.path.isdir(path):
+ if is_installable_dir(path):
+ return path_to_url(path)
+ # TODO: The is_installable_dir test here might not be necessary
+ # now that it is done in load_pyproject_toml too.
+ raise InstallationError(
+ f"Directory {name!r} is not installable. Neither 'setup.py' "
+ "nor 'pyproject.toml' found."
+ )
+ if not is_archive_file(path):
+ return None
+ if os.path.isfile(path):
+ return path_to_url(path)
+ urlreq_parts = name.split("@", 1)
+ if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
+ # If the path contains '@' and the part before it does not look
+ # like a path, try to treat it as a PEP 440 URL req instead.
+ return None
+ logger.warning(
+ "Requirement %r looks like a filename, but the file does not exist",
+ name,
+ )
+ return path_to_url(path)
+
+
+def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
+ if is_url(name):
+ marker_sep = "; "
+ else:
+ marker_sep = ";"
+ if marker_sep in name:
+ name, markers_as_string = name.split(marker_sep, 1)
+ markers_as_string = markers_as_string.strip()
+ if not markers_as_string:
+ markers = None
+ else:
+ markers = Marker(markers_as_string)
+ else:
+ markers = None
+ name = name.strip()
+ req_as_string = None
+ path = os.path.normpath(os.path.abspath(name))
+ link = None
+ extras_as_string = None
+
+ if is_url(name):
+ link = Link(name)
+ else:
+ p, extras_as_string = _strip_extras(path)
+ url = _get_url_from_path(p, name)
+ if url is not None:
+ link = Link(url)
+
+ # it's a local file, dir, or url
+ if link:
+ # Handle relative file URLs
+ if link.scheme == "file" and re.search(r"\.\./", link.url):
+ link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
+ # wheel file
+ if link.is_wheel:
+ wheel = Wheel(link.filename) # can raise InvalidWheelFilename
+ req_as_string = f"{wheel.name}=={wheel.version}"
+ else:
+ # set the req to the egg fragment. when it's not there, this
+ # will become an 'unnamed' requirement
+ req_as_string = link.egg_fragment
+
+ # a requirement specifier
+ else:
+ req_as_string = name
+
+ extras = convert_extras(extras_as_string)
+
+ def with_source(text: str) -> str:
+ if not line_source:
+ return text
+ return f"{text} (from {line_source})"
+
+ def _parse_req_string(req_as_string: str) -> Requirement:
+ try:
+ return get_requirement(req_as_string)
+ except InvalidRequirement as exc:
+ if os.path.sep in req_as_string:
+ add_msg = "It looks like a path."
+ add_msg += deduce_helpful_msg(req_as_string)
+ elif "=" in req_as_string and not any(
+ op in req_as_string for op in operators
+ ):
+ add_msg = "= is not a valid operator. Did you mean == ?"
+ else:
+ add_msg = ""
+ msg = with_source(f"Invalid requirement: {req_as_string!r}: {exc}")
+ if add_msg:
+ msg += f"\nHint: {add_msg}"
+ raise InstallationError(msg)
+
+ if req_as_string is not None:
+ req: Optional[Requirement] = _parse_req_string(req_as_string)
+ else:
+ req = None
+
+ return RequirementParts(req, link, markers, extras)
+
+
+def install_req_from_line(
+ name: str,
+ comes_from: Optional[Union[str, InstallRequirement]] = None,
+ *,
+ use_pep517: Optional[bool] = None,
+ isolated: bool = False,
+ global_options: Optional[List[str]] = None,
+ hash_options: Optional[Dict[str, List[str]]] = None,
+ constraint: bool = False,
+ line_source: Optional[str] = None,
+ user_supplied: bool = False,
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+) -> InstallRequirement:
+ """Creates an InstallRequirement from a name, which might be a
+ requirement, directory containing 'setup.py', filename, or URL.
+
+ :param line_source: An optional string describing where the line is from,
+ for logging purposes in case of an error.
+ """
+ parts = parse_req_from_line(name, line_source)
+
+ return InstallRequirement(
+ parts.requirement,
+ comes_from,
+ link=parts.link,
+ markers=parts.markers,
+ use_pep517=use_pep517,
+ isolated=isolated,
+ global_options=global_options,
+ hash_options=hash_options,
+ config_settings=config_settings,
+ constraint=constraint,
+ extras=parts.extras,
+ user_supplied=user_supplied,
+ )
+
+
+def install_req_from_req_string(
+ req_string: str,
+ comes_from: Optional[InstallRequirement] = None,
+ isolated: bool = False,
+ use_pep517: Optional[bool] = None,
+ user_supplied: bool = False,
+) -> InstallRequirement:
+ try:
+ req = get_requirement(req_string)
+ except InvalidRequirement as exc:
+ raise InstallationError(f"Invalid requirement: {req_string!r}: {exc}")
+
+ domains_not_allowed = [
+ PyPI.file_storage_domain,
+ TestPyPI.file_storage_domain,
+ ]
+ if (
+ req.url
+ and comes_from
+ and comes_from.link
+ and comes_from.link.netloc in domains_not_allowed
+ ):
+ # Explicitly disallow pypi packages that depend on external urls
+ raise InstallationError(
+ "Packages installed from PyPI cannot depend on packages "
+ "which are not also hosted on PyPI.\n"
+ f"{comes_from.name} depends on {req} "
+ )
+
+ return InstallRequirement(
+ req,
+ comes_from,
+ isolated=isolated,
+ use_pep517=use_pep517,
+ user_supplied=user_supplied,
+ )
+
+
+def install_req_from_parsed_requirement(
+ parsed_req: ParsedRequirement,
+ isolated: bool = False,
+ use_pep517: Optional[bool] = None,
+ user_supplied: bool = False,
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+) -> InstallRequirement:
+ if parsed_req.is_editable:
+ req = install_req_from_editable(
+ parsed_req.requirement,
+ comes_from=parsed_req.comes_from,
+ use_pep517=use_pep517,
+ constraint=parsed_req.constraint,
+ isolated=isolated,
+ user_supplied=user_supplied,
+ config_settings=config_settings,
+ )
+
+ else:
+ req = install_req_from_line(
+ parsed_req.requirement,
+ comes_from=parsed_req.comes_from,
+ use_pep517=use_pep517,
+ isolated=isolated,
+ global_options=(
+ parsed_req.options.get("global_options", [])
+ if parsed_req.options
+ else []
+ ),
+ hash_options=(
+ parsed_req.options.get("hashes", {}) if parsed_req.options else {}
+ ),
+ constraint=parsed_req.constraint,
+ line_source=parsed_req.line_source,
+ user_supplied=user_supplied,
+ config_settings=config_settings,
+ )
+ return req
+
+
+def install_req_from_link_and_ireq(
+ link: Link, ireq: InstallRequirement
+) -> InstallRequirement:
+ return InstallRequirement(
+ req=ireq.req,
+ comes_from=ireq.comes_from,
+ editable=ireq.editable,
+ link=link,
+ markers=ireq.markers,
+ use_pep517=ireq.use_pep517,
+ isolated=ireq.isolated,
+ global_options=ireq.global_options,
+ hash_options=ireq.hash_options,
+ config_settings=ireq.config_settings,
+ user_supplied=ireq.user_supplied,
+ )
+
+
+def install_req_drop_extras(ireq: InstallRequirement) -> InstallRequirement:
+ """
+ Creates a new InstallationRequirement using the given template but without
+ any extras. Sets the original requirement as the new one's parent
+ (comes_from).
+ """
+ return InstallRequirement(
+ req=(
+ _set_requirement_extras(ireq.req, set()) if ireq.req is not None else None
+ ),
+ comes_from=ireq,
+ editable=ireq.editable,
+ link=ireq.link,
+ markers=ireq.markers,
+ use_pep517=ireq.use_pep517,
+ isolated=ireq.isolated,
+ global_options=ireq.global_options,
+ hash_options=ireq.hash_options,
+ constraint=ireq.constraint,
+ extras=[],
+ config_settings=ireq.config_settings,
+ user_supplied=ireq.user_supplied,
+ permit_editable_wheels=ireq.permit_editable_wheels,
+ )
+
+
+def install_req_extend_extras(
+ ireq: InstallRequirement,
+ extras: Collection[str],
+) -> InstallRequirement:
+ """
+ Returns a copy of an installation requirement with some additional extras.
+ Makes a shallow copy of the ireq object.
+ """
+ result = copy.copy(ireq)
+ result.extras = {*ireq.extras, *extras}
+ result.req = (
+ _set_requirement_extras(ireq.req, result.extras)
+ if ireq.req is not None
+ else None
+ )
+ return result
diff --git a/env/Lib/site-packages/pip/_internal/req/req_file.py b/env/Lib/site-packages/pip/_internal/req/req_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb2a1f69921bad1ea6fa528ee791e7b5c05c33e4
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/req/req_file.py
@@ -0,0 +1,574 @@
+"""
+Requirements file parsing
+"""
+
+import logging
+import optparse
+import os
+import re
+import shlex
+import urllib.parse
+from optparse import Values
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Generator,
+ Iterable,
+ List,
+ NoReturn,
+ Optional,
+ Tuple,
+)
+
+from pip._internal.cli import cmdoptions
+from pip._internal.exceptions import InstallationError, RequirementsFileParseError
+from pip._internal.models.search_scope import SearchScope
+from pip._internal.utils.encoding import auto_decode
+
+if TYPE_CHECKING:
+ from pip._internal.index.package_finder import PackageFinder
+ from pip._internal.network.session import PipSession
+
+__all__ = ["parse_requirements"]
+
+ReqFileLines = Iterable[Tuple[int, str]]
+
+LineParser = Callable[[str], Tuple[str, Values]]
+
+SCHEME_RE = re.compile(r"^(http|https|file):", re.I)
+COMMENT_RE = re.compile(r"(^|\s+)#.*$")
+
+# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
+# variable name consisting of only uppercase letters, digits or the '_'
+# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
+# 2013 Edition.
+ENV_VAR_RE = re.compile(r"(?P\$\{(?P[A-Z0-9_]+)\})")
+
+SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
+ cmdoptions.index_url,
+ cmdoptions.extra_index_url,
+ cmdoptions.no_index,
+ cmdoptions.constraints,
+ cmdoptions.requirements,
+ cmdoptions.editable,
+ cmdoptions.find_links,
+ cmdoptions.no_binary,
+ cmdoptions.only_binary,
+ cmdoptions.prefer_binary,
+ cmdoptions.require_hashes,
+ cmdoptions.pre,
+ cmdoptions.trusted_host,
+ cmdoptions.use_new_feature,
+]
+
+# options to be passed to requirements
+SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
+ cmdoptions.global_options,
+ cmdoptions.hash,
+ cmdoptions.config_settings,
+]
+
+SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [
+ cmdoptions.config_settings,
+]
+
+
+# the 'dest' string values
+SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ]
+SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
+ str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ
+]
+
+logger = logging.getLogger(__name__)
+
+
+class ParsedRequirement:
+ def __init__(
+ self,
+ requirement: str,
+ is_editable: bool,
+ comes_from: str,
+ constraint: bool,
+ options: Optional[Dict[str, Any]] = None,
+ line_source: Optional[str] = None,
+ ) -> None:
+ self.requirement = requirement
+ self.is_editable = is_editable
+ self.comes_from = comes_from
+ self.options = options
+ self.constraint = constraint
+ self.line_source = line_source
+
+
+class ParsedLine:
+ def __init__(
+ self,
+ filename: str,
+ lineno: int,
+ args: str,
+ opts: Values,
+ constraint: bool,
+ ) -> None:
+ self.filename = filename
+ self.lineno = lineno
+ self.opts = opts
+ self.constraint = constraint
+
+ if args:
+ self.is_requirement = True
+ self.is_editable = False
+ self.requirement = args
+ elif opts.editables:
+ self.is_requirement = True
+ self.is_editable = True
+ # We don't support multiple -e on one line
+ self.requirement = opts.editables[0]
+ else:
+ self.is_requirement = False
+
+
+def parse_requirements(
+ filename: str,
+ session: "PipSession",
+ finder: Optional["PackageFinder"] = None,
+ options: Optional[optparse.Values] = None,
+ constraint: bool = False,
+) -> Generator[ParsedRequirement, None, None]:
+ """Parse a requirements file and yield ParsedRequirement instances.
+
+ :param filename: Path or url of requirements file.
+ :param session: PipSession instance.
+ :param finder: Instance of pip.index.PackageFinder.
+ :param options: cli options.
+ :param constraint: If true, parsing a constraint file rather than
+ requirements file.
+ """
+ line_parser = get_line_parser(finder)
+ parser = RequirementsFileParser(session, line_parser)
+
+ for parsed_line in parser.parse(filename, constraint):
+ parsed_req = handle_line(
+ parsed_line, options=options, finder=finder, session=session
+ )
+ if parsed_req is not None:
+ yield parsed_req
+
+
+def preprocess(content: str) -> ReqFileLines:
+ """Split, filter, and join lines, and return a line iterator
+
+ :param content: the content of the requirements file
+ """
+ lines_enum: ReqFileLines = enumerate(content.splitlines(), start=1)
+ lines_enum = join_lines(lines_enum)
+ lines_enum = ignore_comments(lines_enum)
+ lines_enum = expand_env_variables(lines_enum)
+ return lines_enum
+
+
+def handle_requirement_line(
+ line: ParsedLine,
+ options: Optional[optparse.Values] = None,
+) -> ParsedRequirement:
+ # preserve for the nested code path
+ line_comes_from = "{} {} (line {})".format(
+ "-c" if line.constraint else "-r",
+ line.filename,
+ line.lineno,
+ )
+
+ assert line.is_requirement
+
+ # get the options that apply to requirements
+ if line.is_editable:
+ supported_dest = SUPPORTED_OPTIONS_EDITABLE_REQ_DEST
+ else:
+ supported_dest = SUPPORTED_OPTIONS_REQ_DEST
+ req_options = {}
+ for dest in supported_dest:
+ if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
+ req_options[dest] = line.opts.__dict__[dest]
+
+ line_source = f"line {line.lineno} of {line.filename}"
+ return ParsedRequirement(
+ requirement=line.requirement,
+ is_editable=line.is_editable,
+ comes_from=line_comes_from,
+ constraint=line.constraint,
+ options=req_options,
+ line_source=line_source,
+ )
+
+
+def handle_option_line(
+ opts: Values,
+ filename: str,
+ lineno: int,
+ finder: Optional["PackageFinder"] = None,
+ options: Optional[optparse.Values] = None,
+ session: Optional["PipSession"] = None,
+) -> None:
+ if opts.hashes:
+ logger.warning(
+ "%s line %s has --hash but no requirement, and will be ignored.",
+ filename,
+ lineno,
+ )
+
+ if options:
+ # percolate options upward
+ if opts.require_hashes:
+ options.require_hashes = opts.require_hashes
+ if opts.features_enabled:
+ options.features_enabled.extend(
+ f for f in opts.features_enabled if f not in options.features_enabled
+ )
+
+ # set finder options
+ if finder:
+ find_links = finder.find_links
+ index_urls = finder.index_urls
+ no_index = finder.search_scope.no_index
+ if opts.no_index is True:
+ no_index = True
+ index_urls = []
+ if opts.index_url and not no_index:
+ index_urls = [opts.index_url]
+ if opts.extra_index_urls and not no_index:
+ index_urls.extend(opts.extra_index_urls)
+ if opts.find_links:
+ # FIXME: it would be nice to keep track of the source
+ # of the find_links: support a find-links local path
+ # relative to a requirements file.
+ value = opts.find_links[0]
+ req_dir = os.path.dirname(os.path.abspath(filename))
+ relative_to_reqs_file = os.path.join(req_dir, value)
+ if os.path.exists(relative_to_reqs_file):
+ value = relative_to_reqs_file
+ find_links.append(value)
+
+ if session:
+ # We need to update the auth urls in session
+ session.update_index_urls(index_urls)
+
+ search_scope = SearchScope(
+ find_links=find_links,
+ index_urls=index_urls,
+ no_index=no_index,
+ )
+ finder.search_scope = search_scope
+
+ if opts.pre:
+ finder.set_allow_all_prereleases()
+
+ if opts.prefer_binary:
+ finder.set_prefer_binary()
+
+ if session:
+ for host in opts.trusted_hosts or []:
+ source = f"line {lineno} of {filename}"
+ session.add_trusted_host(host, source=source)
+
+
+def handle_line(
+ line: ParsedLine,
+ options: Optional[optparse.Values] = None,
+ finder: Optional["PackageFinder"] = None,
+ session: Optional["PipSession"] = None,
+) -> Optional[ParsedRequirement]:
+ """Handle a single parsed requirements line; This can result in
+ creating/yielding requirements, or updating the finder.
+
+ :param line: The parsed line to be processed.
+ :param options: CLI options.
+ :param finder: The finder - updated by non-requirement lines.
+ :param session: The session - updated by non-requirement lines.
+
+ Returns a ParsedRequirement object if the line is a requirement line,
+ otherwise returns None.
+
+ For lines that contain requirements, the only options that have an effect
+ are from SUPPORTED_OPTIONS_REQ, and they are scoped to the
+ requirement. Other options from SUPPORTED_OPTIONS may be present, but are
+ ignored.
+
+ For lines that do not contain requirements, the only options that have an
+ effect are from SUPPORTED_OPTIONS. Options from SUPPORTED_OPTIONS_REQ may
+ be present, but are ignored. These lines may contain multiple options
+ (although our docs imply only one is supported), and all our parsed and
+ affect the finder.
+ """
+
+ if line.is_requirement:
+ parsed_req = handle_requirement_line(line, options)
+ return parsed_req
+ else:
+ handle_option_line(
+ line.opts,
+ line.filename,
+ line.lineno,
+ finder,
+ options,
+ session,
+ )
+ return None
+
+
+class RequirementsFileParser:
+ def __init__(
+ self,
+ session: "PipSession",
+ line_parser: LineParser,
+ ) -> None:
+ self._session = session
+ self._line_parser = line_parser
+
+ def parse(
+ self, filename: str, constraint: bool
+ ) -> Generator[ParsedLine, None, None]:
+ """Parse a given file, yielding parsed lines."""
+ yield from self._parse_and_recurse(
+ filename, constraint, [{os.path.abspath(filename): None}]
+ )
+
+ def _parse_and_recurse(
+ self,
+ filename: str,
+ constraint: bool,
+ parsed_files_stack: List[Dict[str, Optional[str]]],
+ ) -> Generator[ParsedLine, None, None]:
+ for line in self._parse_file(filename, constraint):
+ if not line.is_requirement and (
+ line.opts.requirements or line.opts.constraints
+ ):
+ # parse a nested requirements file
+ if line.opts.requirements:
+ req_path = line.opts.requirements[0]
+ nested_constraint = False
+ else:
+ req_path = line.opts.constraints[0]
+ nested_constraint = True
+
+ # original file is over http
+ if SCHEME_RE.search(filename):
+ # do a url join so relative paths work
+ req_path = urllib.parse.urljoin(filename, req_path)
+ # original file and nested file are paths
+ elif not SCHEME_RE.search(req_path):
+ # do a join so relative paths work
+ # and then abspath so that we can identify recursive references
+ req_path = os.path.abspath(
+ os.path.join(
+ os.path.dirname(filename),
+ req_path,
+ )
+ )
+ parsed_files = parsed_files_stack[0]
+ if req_path in parsed_files:
+ initial_file = parsed_files[req_path]
+ tail = (
+ f" and again in {initial_file}"
+ if initial_file is not None
+ else ""
+ )
+ raise RequirementsFileParseError(
+ f"{req_path} recursively references itself in {filename}{tail}"
+ )
+ # Keeping a track where was each file first included in
+ new_parsed_files = parsed_files.copy()
+ new_parsed_files[req_path] = filename
+ yield from self._parse_and_recurse(
+ req_path, nested_constraint, [new_parsed_files, *parsed_files_stack]
+ )
+ else:
+ yield line
+
+ def _parse_file(
+ self, filename: str, constraint: bool
+ ) -> Generator[ParsedLine, None, None]:
+ _, content = get_file_content(filename, self._session)
+
+ lines_enum = preprocess(content)
+
+ for line_number, line in lines_enum:
+ try:
+ args_str, opts = self._line_parser(line)
+ except OptionParsingError as e:
+ # add offending line
+ msg = f"Invalid requirement: {line}\n{e.msg}"
+ raise RequirementsFileParseError(msg)
+
+ yield ParsedLine(
+ filename,
+ line_number,
+ args_str,
+ opts,
+ constraint,
+ )
+
+
+def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
+ def parse_line(line: str) -> Tuple[str, Values]:
+ # Build new parser for each line since it accumulates appendable
+ # options.
+ parser = build_parser()
+ defaults = parser.get_default_values()
+ defaults.index_url = None
+ if finder:
+ defaults.format_control = finder.format_control
+
+ args_str, options_str = break_args_options(line)
+
+ try:
+ options = shlex.split(options_str)
+ except ValueError as e:
+ raise OptionParsingError(f"Could not split options: {options_str}") from e
+
+ opts, _ = parser.parse_args(options, defaults)
+
+ return args_str, opts
+
+ return parse_line
+
+
+def break_args_options(line: str) -> Tuple[str, str]:
+ """Break up the line into an args and options string. We only want to shlex
+ (and then optparse) the options, not the args. args can contain markers
+ which are corrupted by shlex.
+ """
+ tokens = line.split(" ")
+ args = []
+ options = tokens[:]
+ for token in tokens:
+ if token.startswith("-") or token.startswith("--"):
+ break
+ else:
+ args.append(token)
+ options.pop(0)
+ return " ".join(args), " ".join(options)
+
+
+class OptionParsingError(Exception):
+ def __init__(self, msg: str) -> None:
+ self.msg = msg
+
+
+def build_parser() -> optparse.OptionParser:
+ """
+ Return a parser for parsing requirement lines
+ """
+ parser = optparse.OptionParser(add_help_option=False)
+
+ option_factories = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ
+ for option_factory in option_factories:
+ option = option_factory()
+ parser.add_option(option)
+
+ # By default optparse sys.exits on parsing errors. We want to wrap
+ # that in our own exception.
+ def parser_exit(self: Any, msg: str) -> "NoReturn":
+ raise OptionParsingError(msg)
+
+ # NOTE: mypy disallows assigning to a method
+ # https://github.com/python/mypy/issues/2427
+ parser.exit = parser_exit # type: ignore
+
+ return parser
+
+
+def join_lines(lines_enum: ReqFileLines) -> ReqFileLines:
+ """Joins a line ending in '\' with the previous line (except when following
+ comments). The joined line takes on the index of the first line.
+ """
+ primary_line_number = None
+ new_line: List[str] = []
+ for line_number, line in lines_enum:
+ if not line.endswith("\\") or COMMENT_RE.match(line):
+ if COMMENT_RE.match(line):
+ # this ensures comments are always matched later
+ line = " " + line
+ if new_line:
+ new_line.append(line)
+ assert primary_line_number is not None
+ yield primary_line_number, "".join(new_line)
+ new_line = []
+ else:
+ yield line_number, line
+ else:
+ if not new_line:
+ primary_line_number = line_number
+ new_line.append(line.strip("\\"))
+
+ # last line contains \
+ if new_line:
+ assert primary_line_number is not None
+ yield primary_line_number, "".join(new_line)
+
+ # TODO: handle space after '\'.
+
+
+def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines:
+ """
+ Strips comments and filter empty lines.
+ """
+ for line_number, line in lines_enum:
+ line = COMMENT_RE.sub("", line)
+ line = line.strip()
+ if line:
+ yield line_number, line
+
+
+def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines:
+ """Replace all environment variables that can be retrieved via `os.getenv`.
+
+ The only allowed format for environment variables defined in the
+ requirement file is `${MY_VARIABLE_1}` to ensure two things:
+
+ 1. Strings that contain a `$` aren't accidentally (partially) expanded.
+ 2. Ensure consistency across platforms for requirement files.
+
+ These points are the result of a discussion on the `github pull
+ request #3514 `_.
+
+ Valid characters in variable names follow the `POSIX standard
+ `_ and are limited
+ to uppercase letter, digits and the `_` (underscore).
+ """
+ for line_number, line in lines_enum:
+ for env_var, var_name in ENV_VAR_RE.findall(line):
+ value = os.getenv(var_name)
+ if not value:
+ continue
+
+ line = line.replace(env_var, value)
+
+ yield line_number, line
+
+
+def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]:
+ """Gets the content of a file; it may be a filename, file: URL, or
+ http: URL. Returns (location, content). Content is unicode.
+ Respects # -*- coding: declarations on the retrieved files.
+
+ :param url: File path or url.
+ :param session: PipSession instance.
+ """
+ scheme = urllib.parse.urlsplit(url).scheme
+ # Pip has special support for file:// URLs (LocalFSAdapter).
+ if scheme in ["http", "https", "file"]:
+ # Delay importing heavy network modules until absolutely necessary.
+ from pip._internal.network.utils import raise_for_status
+
+ resp = session.get(url)
+ raise_for_status(resp)
+ return resp.url, resp.text
+
+ # Assume this is a bare path.
+ try:
+ with open(url, "rb") as f:
+ content = auto_decode(f.read())
+ except OSError as exc:
+ raise InstallationError(f"Could not open requirements file: {exc}")
+ return url, content
diff --git a/env/Lib/site-packages/pip/_internal/req/req_install.py b/env/Lib/site-packages/pip/_internal/req/req_install.py
new file mode 100644
index 0000000000000000000000000000000000000000..834bc513356150c34f37f07bf1855c97badaacbc
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/req/req_install.py
@@ -0,0 +1,934 @@
+import functools
+import logging
+import os
+import shutil
+import sys
+import uuid
+import zipfile
+from optparse import Values
+from pathlib import Path
+from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
+
+from pip._vendor.packaging.markers import Marker
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.specifiers import SpecifierSet
+from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.packaging.version import Version
+from pip._vendor.packaging.version import parse as parse_version
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
+
+from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
+from pip._internal.exceptions import InstallationError, PreviousBuildDirError
+from pip._internal.locations import get_scheme
+from pip._internal.metadata import (
+ BaseDistribution,
+ get_default_environment,
+ get_directory_distribution,
+ get_wheel_distribution,
+)
+from pip._internal.metadata.base import FilesystemWheel
+from pip._internal.models.direct_url import DirectUrl
+from pip._internal.models.link import Link
+from pip._internal.operations.build.metadata import generate_metadata
+from pip._internal.operations.build.metadata_editable import generate_editable_metadata
+from pip._internal.operations.build.metadata_legacy import (
+ generate_metadata as generate_metadata_legacy,
+)
+from pip._internal.operations.install.editable_legacy import (
+ install_editable as install_editable_legacy,
+)
+from pip._internal.operations.install.wheel import install_wheel
+from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path
+from pip._internal.req.req_uninstall import UninstallPathSet
+from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.hashes import Hashes
+from pip._internal.utils.misc import (
+ ConfiguredBuildBackendHookCaller,
+ ask_path_exists,
+ backup_dir,
+ display_path,
+ hide_url,
+ is_installable_dir,
+ redact_auth_from_requirement,
+ redact_auth_from_url,
+)
+from pip._internal.utils.packaging import get_requirement
+from pip._internal.utils.subprocess import runner_with_spinner_message
+from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
+from pip._internal.utils.unpacking import unpack_file
+from pip._internal.utils.virtualenv import running_under_virtualenv
+from pip._internal.vcs import vcs
+
+logger = logging.getLogger(__name__)
+
+
+class InstallRequirement:
+ """
+ Represents something that may be installed later on, may have information
+ about where to fetch the relevant requirement and also contains logic for
+ installing the said requirement.
+ """
+
+ def __init__(
+ self,
+ req: Optional[Requirement],
+ comes_from: Optional[Union[str, "InstallRequirement"]],
+ editable: bool = False,
+ link: Optional[Link] = None,
+ markers: Optional[Marker] = None,
+ use_pep517: Optional[bool] = None,
+ isolated: bool = False,
+ *,
+ global_options: Optional[List[str]] = None,
+ hash_options: Optional[Dict[str, List[str]]] = None,
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+ constraint: bool = False,
+ extras: Collection[str] = (),
+ user_supplied: bool = False,
+ permit_editable_wheels: bool = False,
+ ) -> None:
+ assert req is None or isinstance(req, Requirement), req
+ self.req = req
+ self.comes_from = comes_from
+ self.constraint = constraint
+ self.editable = editable
+ self.permit_editable_wheels = permit_editable_wheels
+
+ # source_dir is the local directory where the linked requirement is
+ # located, or unpacked. In case unpacking is needed, creating and
+ # populating source_dir is done by the RequirementPreparer. Note this
+ # is not necessarily the directory where pyproject.toml or setup.py is
+ # located - that one is obtained via unpacked_source_directory.
+ self.source_dir: Optional[str] = None
+ if self.editable:
+ assert link
+ if link.is_file:
+ self.source_dir = os.path.normpath(os.path.abspath(link.file_path))
+
+ # original_link is the direct URL that was provided by the user for the
+ # requirement, either directly or via a constraints file.
+ if link is None and req and req.url:
+ # PEP 508 URL requirement
+ link = Link(req.url)
+ self.link = self.original_link = link
+
+ # When this InstallRequirement is a wheel obtained from the cache of locally
+ # built wheels, this is the source link corresponding to the cache entry, which
+ # was used to download and build the cached wheel.
+ self.cached_wheel_source_link: Optional[Link] = None
+
+ # Information about the location of the artifact that was downloaded . This
+ # property is guaranteed to be set in resolver results.
+ self.download_info: Optional[DirectUrl] = None
+
+ # Path to any downloaded or already-existing package.
+ self.local_file_path: Optional[str] = None
+ if self.link and self.link.is_file:
+ self.local_file_path = self.link.file_path
+
+ if extras:
+ self.extras = extras
+ elif req:
+ self.extras = req.extras
+ else:
+ self.extras = set()
+ if markers is None and req:
+ markers = req.marker
+ self.markers = markers
+
+ # This holds the Distribution object if this requirement is already installed.
+ self.satisfied_by: Optional[BaseDistribution] = None
+ # Whether the installation process should try to uninstall an existing
+ # distribution before installing this requirement.
+ self.should_reinstall = False
+ # Temporary build location
+ self._temp_build_dir: Optional[TempDirectory] = None
+ # Set to True after successful installation
+ self.install_succeeded: Optional[bool] = None
+ # Supplied options
+ self.global_options = global_options if global_options else []
+ self.hash_options = hash_options if hash_options else {}
+ self.config_settings = config_settings
+ # Set to True after successful preparation of this requirement
+ self.prepared = False
+ # User supplied requirement are explicitly requested for installation
+ # by the user via CLI arguments or requirements files, as opposed to,
+ # e.g. dependencies, extras or constraints.
+ self.user_supplied = user_supplied
+
+ self.isolated = isolated
+ self.build_env: BuildEnvironment = NoOpBuildEnvironment()
+
+ # For PEP 517, the directory where we request the project metadata
+ # gets stored. We need this to pass to build_wheel, so the backend
+ # can ensure that the wheel matches the metadata (see the PEP for
+ # details).
+ self.metadata_directory: Optional[str] = None
+
+ # The static build requirements (from pyproject.toml)
+ self.pyproject_requires: Optional[List[str]] = None
+
+ # Build requirements that we will check are available
+ self.requirements_to_check: List[str] = []
+
+ # The PEP 517 backend we should use to build the project
+ self.pep517_backend: Optional[BuildBackendHookCaller] = None
+
+ # Are we using PEP 517 for this requirement?
+ # After pyproject.toml has been loaded, the only valid values are True
+ # and False. Before loading, None is valid (meaning "use the default").
+ # Setting an explicit value before loading pyproject.toml is supported,
+ # but after loading this flag should be treated as read only.
+ self.use_pep517 = use_pep517
+
+ # If config settings are provided, enforce PEP 517.
+ if self.config_settings:
+ if self.use_pep517 is False:
+ logger.warning(
+ "--no-use-pep517 ignored for %s "
+ "because --config-settings are specified.",
+ self,
+ )
+ self.use_pep517 = True
+
+ # This requirement needs more preparation before it can be built
+ self.needs_more_preparation = False
+
+ # This requirement needs to be unpacked before it can be installed.
+ self._archive_source: Optional[Path] = None
+
+ def __str__(self) -> str:
+ if self.req:
+ s = redact_auth_from_requirement(self.req)
+ if self.link:
+ s += f" from {redact_auth_from_url(self.link.url)}"
+ elif self.link:
+ s = redact_auth_from_url(self.link.url)
+ else:
+ s = ""
+ if self.satisfied_by is not None:
+ if self.satisfied_by.location is not None:
+ location = display_path(self.satisfied_by.location)
+ else:
+ location = ""
+ s += f" in {location}"
+ if self.comes_from:
+ if isinstance(self.comes_from, str):
+ comes_from: Optional[str] = self.comes_from
+ else:
+ comes_from = self.comes_from.from_path()
+ if comes_from:
+ s += f" (from {comes_from})"
+ return s
+
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__name__} object: "
+ f"{str(self)} editable={self.editable!r}>"
+ )
+
+ def format_debug(self) -> str:
+ """An un-tested helper for getting state, for debugging."""
+ attributes = vars(self)
+ names = sorted(attributes)
+
+ state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names))
+ return "<{name} object: {{{state}}}>".format(
+ name=self.__class__.__name__,
+ state=", ".join(state),
+ )
+
+ # Things that are valid for all kinds of requirements?
+ @property
+ def name(self) -> Optional[str]:
+ if self.req is None:
+ return None
+ return self.req.name
+
+ @functools.cached_property
+ def supports_pyproject_editable(self) -> bool:
+ if not self.use_pep517:
+ return False
+ assert self.pep517_backend
+ with self.build_env:
+ runner = runner_with_spinner_message(
+ "Checking if build backend supports build_editable"
+ )
+ with self.pep517_backend.subprocess_runner(runner):
+ return "build_editable" in self.pep517_backend._supported_features()
+
+ @property
+ def specifier(self) -> SpecifierSet:
+ assert self.req is not None
+ return self.req.specifier
+
+ @property
+ def is_direct(self) -> bool:
+ """Whether this requirement was specified as a direct URL."""
+ return self.original_link is not None
+
+ @property
+ def is_pinned(self) -> bool:
+ """Return whether I am pinned to an exact version.
+
+ For example, some-package==1.2 is pinned; some-package>1.2 is not.
+ """
+ assert self.req is not None
+ specifiers = self.req.specifier
+ return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
+
+ def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
+ if not extras_requested:
+ # Provide an extra to safely evaluate the markers
+ # without matching any extra
+ extras_requested = ("",)
+ if self.markers is not None:
+ return any(
+ self.markers.evaluate({"extra": extra}) for extra in extras_requested
+ )
+ else:
+ return True
+
+ @property
+ def has_hash_options(self) -> bool:
+ """Return whether any known-good hashes are specified as options.
+
+ These activate --require-hashes mode; hashes specified as part of a
+ URL do not.
+
+ """
+ return bool(self.hash_options)
+
+ def hashes(self, trust_internet: bool = True) -> Hashes:
+ """Return a hash-comparer that considers my option- and URL-based
+ hashes to be known-good.
+
+ Hashes in URLs--ones embedded in the requirements file, not ones
+ downloaded from an index server--are almost peers with ones from
+ flags. They satisfy --require-hashes (whether it was implicitly or
+ explicitly activated) but do not activate it. md5 and sha224 are not
+ allowed in flags, which should nudge people toward good algos. We
+ always OR all hashes together, even ones from URLs.
+
+ :param trust_internet: Whether to trust URL-based (#md5=...) hashes
+ downloaded from the internet, as by populate_link()
+
+ """
+ good_hashes = self.hash_options.copy()
+ if trust_internet:
+ link = self.link
+ elif self.is_direct and self.user_supplied:
+ link = self.original_link
+ else:
+ link = None
+ if link and link.hash:
+ assert link.hash_name is not None
+ good_hashes.setdefault(link.hash_name, []).append(link.hash)
+ return Hashes(good_hashes)
+
+ def from_path(self) -> Optional[str]:
+ """Format a nice indicator to show where this "comes from" """
+ if self.req is None:
+ return None
+ s = str(self.req)
+ if self.comes_from:
+ comes_from: Optional[str]
+ if isinstance(self.comes_from, str):
+ comes_from = self.comes_from
+ else:
+ comes_from = self.comes_from.from_path()
+ if comes_from:
+ s += "->" + comes_from
+ return s
+
+ def ensure_build_location(
+ self, build_dir: str, autodelete: bool, parallel_builds: bool
+ ) -> str:
+ assert build_dir is not None
+ if self._temp_build_dir is not None:
+ assert self._temp_build_dir.path
+ return self._temp_build_dir.path
+ if self.req is None:
+ # Some systems have /tmp as a symlink which confuses custom
+ # builds (such as numpy). Thus, we ensure that the real path
+ # is returned.
+ self._temp_build_dir = TempDirectory(
+ kind=tempdir_kinds.REQ_BUILD, globally_managed=True
+ )
+
+ return self._temp_build_dir.path
+
+ # This is the only remaining place where we manually determine the path
+ # for the temporary directory. It is only needed for editables where
+ # it is the value of the --src option.
+
+ # When parallel builds are enabled, add a UUID to the build directory
+ # name so multiple builds do not interfere with each other.
+ dir_name: str = canonicalize_name(self.req.name)
+ if parallel_builds:
+ dir_name = f"{dir_name}_{uuid.uuid4().hex}"
+
+ # FIXME: Is there a better place to create the build_dir? (hg and bzr
+ # need this)
+ if not os.path.exists(build_dir):
+ logger.debug("Creating directory %s", build_dir)
+ os.makedirs(build_dir)
+ actual_build_dir = os.path.join(build_dir, dir_name)
+ # `None` indicates that we respect the globally-configured deletion
+ # settings, which is what we actually want when auto-deleting.
+ delete_arg = None if autodelete else False
+ return TempDirectory(
+ path=actual_build_dir,
+ delete=delete_arg,
+ kind=tempdir_kinds.REQ_BUILD,
+ globally_managed=True,
+ ).path
+
+ def _set_requirement(self) -> None:
+ """Set requirement after generating metadata."""
+ assert self.req is None
+ assert self.metadata is not None
+ assert self.source_dir is not None
+
+ # Construct a Requirement object from the generated metadata
+ if isinstance(parse_version(self.metadata["Version"]), Version):
+ op = "=="
+ else:
+ op = "==="
+
+ self.req = get_requirement(
+ "".join(
+ [
+ self.metadata["Name"],
+ op,
+ self.metadata["Version"],
+ ]
+ )
+ )
+
+ def warn_on_mismatching_name(self) -> None:
+ assert self.req is not None
+ metadata_name = canonicalize_name(self.metadata["Name"])
+ if canonicalize_name(self.req.name) == metadata_name:
+ # Everything is fine.
+ return
+
+ # If we're here, there's a mismatch. Log a warning about it.
+ logger.warning(
+ "Generating metadata for package %s "
+ "produced metadata for project name %s. Fix your "
+ "#egg=%s fragments.",
+ self.name,
+ metadata_name,
+ self.name,
+ )
+ self.req = get_requirement(metadata_name)
+
+ def check_if_exists(self, use_user_site: bool) -> None:
+ """Find an installed distribution that satisfies or conflicts
+ with this requirement, and set self.satisfied_by or
+ self.should_reinstall appropriately.
+ """
+ if self.req is None:
+ return
+ existing_dist = get_default_environment().get_distribution(self.req.name)
+ if not existing_dist:
+ return
+
+ version_compatible = self.req.specifier.contains(
+ existing_dist.version,
+ prereleases=True,
+ )
+ if not version_compatible:
+ self.satisfied_by = None
+ if use_user_site:
+ if existing_dist.in_usersite:
+ self.should_reinstall = True
+ elif running_under_virtualenv() and existing_dist.in_site_packages:
+ raise InstallationError(
+ f"Will not install to the user site because it will "
+ f"lack sys.path precedence to {existing_dist.raw_name} "
+ f"in {existing_dist.location}"
+ )
+ else:
+ self.should_reinstall = True
+ else:
+ if self.editable:
+ self.should_reinstall = True
+ # when installing editables, nothing pre-existing should ever
+ # satisfy
+ self.satisfied_by = None
+ else:
+ self.satisfied_by = existing_dist
+
+ # Things valid for wheels
+ @property
+ def is_wheel(self) -> bool:
+ if not self.link:
+ return False
+ return self.link.is_wheel
+
+ @property
+ def is_wheel_from_cache(self) -> bool:
+ # When True, it means that this InstallRequirement is a local wheel file in the
+ # cache of locally built wheels.
+ return self.cached_wheel_source_link is not None
+
+ # Things valid for sdists
+ @property
+ def unpacked_source_directory(self) -> str:
+ assert self.source_dir, f"No source dir for {self}"
+ return os.path.join(
+ self.source_dir, self.link and self.link.subdirectory_fragment or ""
+ )
+
+ @property
+ def setup_py_path(self) -> str:
+ assert self.source_dir, f"No source dir for {self}"
+ setup_py = os.path.join(self.unpacked_source_directory, "setup.py")
+
+ return setup_py
+
+ @property
+ def setup_cfg_path(self) -> str:
+ assert self.source_dir, f"No source dir for {self}"
+ setup_cfg = os.path.join(self.unpacked_source_directory, "setup.cfg")
+
+ return setup_cfg
+
+ @property
+ def pyproject_toml_path(self) -> str:
+ assert self.source_dir, f"No source dir for {self}"
+ return make_pyproject_path(self.unpacked_source_directory)
+
+ def load_pyproject_toml(self) -> None:
+ """Load the pyproject.toml file.
+
+ After calling this routine, all of the attributes related to PEP 517
+ processing for this requirement have been set. In particular, the
+ use_pep517 attribute can be used to determine whether we should
+ follow the PEP 517 or legacy (setup.py) code path.
+ """
+ pyproject_toml_data = load_pyproject_toml(
+ self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self)
+ )
+
+ if pyproject_toml_data is None:
+ assert not self.config_settings
+ self.use_pep517 = False
+ return
+
+ self.use_pep517 = True
+ requires, backend, check, backend_path = pyproject_toml_data
+ self.requirements_to_check = check
+ self.pyproject_requires = requires
+ self.pep517_backend = ConfiguredBuildBackendHookCaller(
+ self,
+ self.unpacked_source_directory,
+ backend,
+ backend_path=backend_path,
+ )
+
+ def isolated_editable_sanity_check(self) -> None:
+ """Check that an editable requirement if valid for use with PEP 517/518.
+
+ This verifies that an editable that has a pyproject.toml either supports PEP 660
+ or as a setup.py or a setup.cfg
+ """
+ if (
+ self.editable
+ and self.use_pep517
+ and not self.supports_pyproject_editable
+ and not os.path.isfile(self.setup_py_path)
+ and not os.path.isfile(self.setup_cfg_path)
+ ):
+ raise InstallationError(
+ f"Project {self} has a 'pyproject.toml' and its build "
+ f"backend is missing the 'build_editable' hook. Since it does not "
+ f"have a 'setup.py' nor a 'setup.cfg', "
+ f"it cannot be installed in editable mode. "
+ f"Consider using a build backend that supports PEP 660."
+ )
+
+ def prepare_metadata(self) -> None:
+ """Ensure that project metadata is available.
+
+ Under PEP 517 and PEP 660, call the backend hook to prepare the metadata.
+ Under legacy processing, call setup.py egg-info.
+ """
+ assert self.source_dir, f"No source dir for {self}"
+ details = self.name or f"from {self.link}"
+
+ if self.use_pep517:
+ assert self.pep517_backend is not None
+ if (
+ self.editable
+ and self.permit_editable_wheels
+ and self.supports_pyproject_editable
+ ):
+ self.metadata_directory = generate_editable_metadata(
+ build_env=self.build_env,
+ backend=self.pep517_backend,
+ details=details,
+ )
+ else:
+ self.metadata_directory = generate_metadata(
+ build_env=self.build_env,
+ backend=self.pep517_backend,
+ details=details,
+ )
+ else:
+ self.metadata_directory = generate_metadata_legacy(
+ build_env=self.build_env,
+ setup_py_path=self.setup_py_path,
+ source_dir=self.unpacked_source_directory,
+ isolated=self.isolated,
+ details=details,
+ )
+
+ # Act on the newly generated metadata, based on the name and version.
+ if not self.name:
+ self._set_requirement()
+ else:
+ self.warn_on_mismatching_name()
+
+ self.assert_source_matches_version()
+
+ @property
+ def metadata(self) -> Any:
+ if not hasattr(self, "_metadata"):
+ self._metadata = self.get_dist().metadata
+
+ return self._metadata
+
+ def get_dist(self) -> BaseDistribution:
+ if self.metadata_directory:
+ return get_directory_distribution(self.metadata_directory)
+ elif self.local_file_path and self.is_wheel:
+ assert self.req is not None
+ return get_wheel_distribution(
+ FilesystemWheel(self.local_file_path),
+ canonicalize_name(self.req.name),
+ )
+ raise AssertionError(
+ f"InstallRequirement {self} has no metadata directory and no wheel: "
+ f"can't make a distribution."
+ )
+
+ def assert_source_matches_version(self) -> None:
+ assert self.source_dir, f"No source dir for {self}"
+ version = self.metadata["version"]
+ if self.req and self.req.specifier and version not in self.req.specifier:
+ logger.warning(
+ "Requested %s, but installing version %s",
+ self,
+ version,
+ )
+ else:
+ logger.debug(
+ "Source in %s has version %s, which satisfies requirement %s",
+ display_path(self.source_dir),
+ version,
+ self,
+ )
+
+ # For both source distributions and editables
+ def ensure_has_source_dir(
+ self,
+ parent_dir: str,
+ autodelete: bool = False,
+ parallel_builds: bool = False,
+ ) -> None:
+ """Ensure that a source_dir is set.
+
+ This will create a temporary build dir if the name of the requirement
+ isn't known yet.
+
+ :param parent_dir: The ideal pip parent_dir for the source_dir.
+ Generally src_dir for editables and build_dir for sdists.
+ :return: self.source_dir
+ """
+ if self.source_dir is None:
+ self.source_dir = self.ensure_build_location(
+ parent_dir,
+ autodelete=autodelete,
+ parallel_builds=parallel_builds,
+ )
+
+ def needs_unpacked_archive(self, archive_source: Path) -> None:
+ assert self._archive_source is None
+ self._archive_source = archive_source
+
+ def ensure_pristine_source_checkout(self) -> None:
+ """Ensure the source directory has not yet been built in."""
+ assert self.source_dir is not None
+ if self._archive_source is not None:
+ unpack_file(str(self._archive_source), self.source_dir)
+ elif is_installable_dir(self.source_dir):
+ # If a checkout exists, it's unwise to keep going.
+ # version inconsistencies are logged later, but do not fail
+ # the installation.
+ raise PreviousBuildDirError(
+ f"pip can't proceed with requirements '{self}' due to a "
+ f"pre-existing build directory ({self.source_dir}). This is likely "
+ "due to a previous installation that failed . pip is "
+ "being responsible and not assuming it can delete this. "
+ "Please delete it and try again."
+ )
+
+ # For editable installations
+ def update_editable(self) -> None:
+ if not self.link:
+ logger.debug(
+ "Cannot update repository at %s; repository location is unknown",
+ self.source_dir,
+ )
+ return
+ assert self.editable
+ assert self.source_dir
+ if self.link.scheme == "file":
+ # Static paths don't get updated
+ return
+ vcs_backend = vcs.get_backend_for_scheme(self.link.scheme)
+ # Editable requirements are validated in Requirement constructors.
+ # So here, if it's neither a path nor a valid VCS URL, it's a bug.
+ assert vcs_backend, f"Unsupported VCS URL {self.link.url}"
+ hidden_url = hide_url(self.link.url)
+ vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0)
+
+ # Top-level Actions
+ def uninstall(
+ self, auto_confirm: bool = False, verbose: bool = False
+ ) -> Optional[UninstallPathSet]:
+ """
+ Uninstall the distribution currently satisfying this requirement.
+
+ Prompts before removing or modifying files unless
+ ``auto_confirm`` is True.
+
+ Refuses to delete or modify files outside of ``sys.prefix`` -
+ thus uninstallation within a virtual environment can only
+ modify that virtual environment, even if the virtualenv is
+ linked to global site-packages.
+
+ """
+ assert self.req
+ dist = get_default_environment().get_distribution(self.req.name)
+ if not dist:
+ logger.warning("Skipping %s as it is not installed.", self.name)
+ return None
+ logger.info("Found existing installation: %s", dist)
+
+ uninstalled_pathset = UninstallPathSet.from_dist(dist)
+ uninstalled_pathset.remove(auto_confirm, verbose)
+ return uninstalled_pathset
+
+ def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str:
+ def _clean_zip_name(name: str, prefix: str) -> str:
+ assert name.startswith(
+ prefix + os.path.sep
+ ), f"name {name!r} doesn't start with prefix {prefix!r}"
+ name = name[len(prefix) + 1 :]
+ name = name.replace(os.path.sep, "/")
+ return name
+
+ assert self.req is not None
+ path = os.path.join(parentdir, path)
+ name = _clean_zip_name(path, rootdir)
+ return self.req.name + "/" + name
+
+ def archive(self, build_dir: Optional[str]) -> None:
+ """Saves archive to provided build_dir.
+
+ Used for saving downloaded VCS requirements as part of `pip download`.
+ """
+ assert self.source_dir
+ if build_dir is None:
+ return
+
+ create_archive = True
+ archive_name = "{}-{}.zip".format(self.name, self.metadata["version"])
+ archive_path = os.path.join(build_dir, archive_name)
+
+ if os.path.exists(archive_path):
+ response = ask_path_exists(
+ f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, "
+ "(b)ackup, (a)bort ",
+ ("i", "w", "b", "a"),
+ )
+ if response == "i":
+ create_archive = False
+ elif response == "w":
+ logger.warning("Deleting %s", display_path(archive_path))
+ os.remove(archive_path)
+ elif response == "b":
+ dest_file = backup_dir(archive_path)
+ logger.warning(
+ "Backing up %s to %s",
+ display_path(archive_path),
+ display_path(dest_file),
+ )
+ shutil.move(archive_path, dest_file)
+ elif response == "a":
+ sys.exit(-1)
+
+ if not create_archive:
+ return
+
+ zip_output = zipfile.ZipFile(
+ archive_path,
+ "w",
+ zipfile.ZIP_DEFLATED,
+ allowZip64=True,
+ )
+ with zip_output:
+ dir = os.path.normcase(os.path.abspath(self.unpacked_source_directory))
+ for dirpath, dirnames, filenames in os.walk(dir):
+ for dirname in dirnames:
+ dir_arcname = self._get_archive_name(
+ dirname,
+ parentdir=dirpath,
+ rootdir=dir,
+ )
+ zipdir = zipfile.ZipInfo(dir_arcname + "/")
+ zipdir.external_attr = 0x1ED << 16 # 0o755
+ zip_output.writestr(zipdir, "")
+ for filename in filenames:
+ file_arcname = self._get_archive_name(
+ filename,
+ parentdir=dirpath,
+ rootdir=dir,
+ )
+ filename = os.path.join(dirpath, filename)
+ zip_output.write(filename, file_arcname)
+
+ logger.info("Saved %s", display_path(archive_path))
+
+ def install(
+ self,
+ global_options: Optional[Sequence[str]] = None,
+ root: Optional[str] = None,
+ home: Optional[str] = None,
+ prefix: Optional[str] = None,
+ warn_script_location: bool = True,
+ use_user_site: bool = False,
+ pycompile: bool = True,
+ ) -> None:
+ assert self.req is not None
+ scheme = get_scheme(
+ self.req.name,
+ user=use_user_site,
+ home=home,
+ root=root,
+ isolated=self.isolated,
+ prefix=prefix,
+ )
+
+ if self.editable and not self.is_wheel:
+ deprecated(
+ reason=(
+ f"Legacy editable install of {self} (setup.py develop) "
+ "is deprecated."
+ ),
+ replacement=(
+ "to add a pyproject.toml or enable --use-pep517, "
+ "and use setuptools >= 64. "
+ "If the resulting installation is not behaving as expected, "
+ "try using --config-settings editable_mode=compat. "
+ "Please consult the setuptools documentation for more information"
+ ),
+ gone_in="25.0",
+ issue=11457,
+ )
+ if self.config_settings:
+ logger.warning(
+ "--config-settings ignored for legacy editable install of %s. "
+ "Consider upgrading to a version of setuptools "
+ "that supports PEP 660 (>= 64).",
+ self,
+ )
+ install_editable_legacy(
+ global_options=global_options if global_options is not None else [],
+ prefix=prefix,
+ home=home,
+ use_user_site=use_user_site,
+ name=self.req.name,
+ setup_py_path=self.setup_py_path,
+ isolated=self.isolated,
+ build_env=self.build_env,
+ unpacked_source_directory=self.unpacked_source_directory,
+ )
+ self.install_succeeded = True
+ return
+
+ assert self.is_wheel
+ assert self.local_file_path
+
+ install_wheel(
+ self.req.name,
+ self.local_file_path,
+ scheme=scheme,
+ req_description=str(self.req),
+ pycompile=pycompile,
+ warn_script_location=warn_script_location,
+ direct_url=self.download_info if self.is_direct else None,
+ requested=self.user_supplied,
+ )
+ self.install_succeeded = True
+
+
+def check_invalid_constraint_type(req: InstallRequirement) -> str:
+ # Check for unsupported forms
+ problem = ""
+ if not req.name:
+ problem = "Unnamed requirements are not allowed as constraints"
+ elif req.editable:
+ problem = "Editable requirements are not allowed as constraints"
+ elif req.extras:
+ problem = "Constraints cannot have extras"
+
+ if problem:
+ deprecated(
+ reason=(
+ "Constraints are only allowed to take the form of a package "
+ "name and a version specifier. Other forms were originally "
+ "permitted as an accident of the implementation, but were "
+ "undocumented. The new implementation of the resolver no "
+ "longer supports these forms."
+ ),
+ replacement="replacing the constraint with a requirement",
+ # No plan yet for when the new resolver becomes default
+ gone_in=None,
+ issue=8210,
+ )
+
+ return problem
+
+
+def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool:
+ if getattr(options, option, None):
+ return True
+ for req in reqs:
+ if getattr(req, option, None):
+ return True
+ return False
+
+
+def check_legacy_setup_py_options(
+ options: Values,
+ reqs: List[InstallRequirement],
+) -> None:
+ has_build_options = _has_option(options, reqs, "build_options")
+ has_global_options = _has_option(options, reqs, "global_options")
+ if has_build_options or has_global_options:
+ deprecated(
+ reason="--build-option and --global-option are deprecated.",
+ issue=11859,
+ replacement="to use --config-settings",
+ gone_in="25.0",
+ )
+ logger.warning(
+ "Implying --no-binary=:all: due to the presence of "
+ "--build-option / --global-option. "
+ )
+ options.format_control.disallow_binaries()
diff --git a/env/Lib/site-packages/pip/_internal/req/req_set.py b/env/Lib/site-packages/pip/_internal/req/req_set.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec7a6e07a25acfa978030c65ae7c1d8609163249
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/req/req_set.py
@@ -0,0 +1,82 @@
+import logging
+from collections import OrderedDict
+from typing import Dict, List
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from pip._internal.req.req_install import InstallRequirement
+
+logger = logging.getLogger(__name__)
+
+
+class RequirementSet:
+ def __init__(self, check_supported_wheels: bool = True) -> None:
+ """Create a RequirementSet."""
+
+ self.requirements: Dict[str, InstallRequirement] = OrderedDict()
+ self.check_supported_wheels = check_supported_wheels
+
+ self.unnamed_requirements: List[InstallRequirement] = []
+
+ def __str__(self) -> str:
+ requirements = sorted(
+ (req for req in self.requirements.values() if not req.comes_from),
+ key=lambda req: canonicalize_name(req.name or ""),
+ )
+ return " ".join(str(req.req) for req in requirements)
+
+ def __repr__(self) -> str:
+ requirements = sorted(
+ self.requirements.values(),
+ key=lambda req: canonicalize_name(req.name or ""),
+ )
+
+ format_string = "<{classname} object; {count} requirement(s): {reqs}>"
+ return format_string.format(
+ classname=self.__class__.__name__,
+ count=len(requirements),
+ reqs=", ".join(str(req.req) for req in requirements),
+ )
+
+ def add_unnamed_requirement(self, install_req: InstallRequirement) -> None:
+ assert not install_req.name
+ self.unnamed_requirements.append(install_req)
+
+ def add_named_requirement(self, install_req: InstallRequirement) -> None:
+ assert install_req.name
+
+ project_name = canonicalize_name(install_req.name)
+ self.requirements[project_name] = install_req
+
+ def has_requirement(self, name: str) -> bool:
+ project_name = canonicalize_name(name)
+
+ return (
+ project_name in self.requirements
+ and not self.requirements[project_name].constraint
+ )
+
+ def get_requirement(self, name: str) -> InstallRequirement:
+ project_name = canonicalize_name(name)
+
+ if project_name in self.requirements:
+ return self.requirements[project_name]
+
+ raise KeyError(f"No project with the name {name!r}")
+
+ @property
+ def all_requirements(self) -> List[InstallRequirement]:
+ return self.unnamed_requirements + list(self.requirements.values())
+
+ @property
+ def requirements_to_install(self) -> List[InstallRequirement]:
+ """Return the list of requirements that need to be installed.
+
+ TODO remove this property together with the legacy resolver, since the new
+ resolver only returns requirements that need to be installed.
+ """
+ return [
+ install_req
+ for install_req in self.all_requirements
+ if not install_req.constraint and not install_req.satisfied_by
+ ]
diff --git a/env/Lib/site-packages/pip/_internal/req/req_uninstall.py b/env/Lib/site-packages/pip/_internal/req/req_uninstall.py
new file mode 100644
index 0000000000000000000000000000000000000000..26df20844b379bd7c06f3db7d75cddad5755d22e
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/req/req_uninstall.py
@@ -0,0 +1,633 @@
+import functools
+import os
+import sys
+import sysconfig
+from importlib.util import cache_from_source
+from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple
+
+from pip._internal.exceptions import LegacyDistutilsInstall, UninstallMissingRecord
+from pip._internal.locations import get_bin_prefix, get_bin_user
+from pip._internal.metadata import BaseDistribution
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.egg_link import egg_link_path_from_location
+from pip._internal.utils.logging import getLogger, indent_log
+from pip._internal.utils.misc import ask, normalize_path, renames, rmtree
+from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
+from pip._internal.utils.virtualenv import running_under_virtualenv
+
+logger = getLogger(__name__)
+
+
+def _script_names(
+ bin_dir: str, script_name: str, is_gui: bool
+) -> Generator[str, None, None]:
+ """Create the fully qualified name of the files created by
+ {console,gui}_scripts for the given ``dist``.
+ Returns the list of file names
+ """
+ exe_name = os.path.join(bin_dir, script_name)
+ yield exe_name
+ if not WINDOWS:
+ return
+ yield f"{exe_name}.exe"
+ yield f"{exe_name}.exe.manifest"
+ if is_gui:
+ yield f"{exe_name}-script.pyw"
+ else:
+ yield f"{exe_name}-script.py"
+
+
+def _unique(
+ fn: Callable[..., Generator[Any, None, None]]
+) -> Callable[..., Generator[Any, None, None]]:
+ @functools.wraps(fn)
+ def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]:
+ seen: Set[Any] = set()
+ for item in fn(*args, **kw):
+ if item not in seen:
+ seen.add(item)
+ yield item
+
+ return unique
+
+
+@_unique
+def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
+ """
+ Yield all the uninstallation paths for dist based on RECORD-without-.py[co]
+
+ Yield paths to all the files in RECORD. For each .py file in RECORD, add
+ the .pyc and .pyo in the same directory.
+
+ UninstallPathSet.add() takes care of the __pycache__ .py[co].
+
+ If RECORD is not found, raises an error,
+ with possible information from the INSTALLER file.
+
+ https://packaging.python.org/specifications/recording-installed-packages/
+ """
+ location = dist.location
+ assert location is not None, "not installed"
+
+ entries = dist.iter_declared_entries()
+ if entries is None:
+ raise UninstallMissingRecord(distribution=dist)
+
+ for entry in entries:
+ path = os.path.join(location, entry)
+ yield path
+ if path.endswith(".py"):
+ dn, fn = os.path.split(path)
+ base = fn[:-3]
+ path = os.path.join(dn, base + ".pyc")
+ yield path
+ path = os.path.join(dn, base + ".pyo")
+ yield path
+
+
+def compact(paths: Iterable[str]) -> Set[str]:
+ """Compact a path set to contain the minimal number of paths
+ necessary to contain all paths in the set. If /a/path/ and
+ /a/path/to/a/file.txt are both in the set, leave only the
+ shorter path."""
+
+ sep = os.path.sep
+ short_paths: Set[str] = set()
+ for path in sorted(paths, key=len):
+ should_skip = any(
+ path.startswith(shortpath.rstrip("*"))
+ and path[len(shortpath.rstrip("*").rstrip(sep))] == sep
+ for shortpath in short_paths
+ )
+ if not should_skip:
+ short_paths.add(path)
+ return short_paths
+
+
+def compress_for_rename(paths: Iterable[str]) -> Set[str]:
+ """Returns a set containing the paths that need to be renamed.
+
+ This set may include directories when the original sequence of paths
+ included every file on disk.
+ """
+ case_map = {os.path.normcase(p): p for p in paths}
+ remaining = set(case_map)
+ unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len)
+ wildcards: Set[str] = set()
+
+ def norm_join(*a: str) -> str:
+ return os.path.normcase(os.path.join(*a))
+
+ for root in unchecked:
+ if any(os.path.normcase(root).startswith(w) for w in wildcards):
+ # This directory has already been handled.
+ continue
+
+ all_files: Set[str] = set()
+ all_subdirs: Set[str] = set()
+ for dirname, subdirs, files in os.walk(root):
+ all_subdirs.update(norm_join(root, dirname, d) for d in subdirs)
+ all_files.update(norm_join(root, dirname, f) for f in files)
+ # If all the files we found are in our remaining set of files to
+ # remove, then remove them from the latter set and add a wildcard
+ # for the directory.
+ if not (all_files - remaining):
+ remaining.difference_update(all_files)
+ wildcards.add(root + os.sep)
+
+ return set(map(case_map.__getitem__, remaining)) | wildcards
+
+
+def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]:
+ """Returns a tuple of 2 sets of which paths to display to user
+
+ The first set contains paths that would be deleted. Files of a package
+ are not added and the top-level directory of the package has a '*' added
+ at the end - to signify that all it's contents are removed.
+
+ The second set contains files that would have been skipped in the above
+ folders.
+ """
+
+ will_remove = set(paths)
+ will_skip = set()
+
+ # Determine folders and files
+ folders = set()
+ files = set()
+ for path in will_remove:
+ if path.endswith(".pyc"):
+ continue
+ if path.endswith("__init__.py") or ".dist-info" in path:
+ folders.add(os.path.dirname(path))
+ files.add(path)
+
+ _normcased_files = set(map(os.path.normcase, files))
+
+ folders = compact(folders)
+
+ # This walks the tree using os.walk to not miss extra folders
+ # that might get added.
+ for folder in folders:
+ for dirpath, _, dirfiles in os.walk(folder):
+ for fname in dirfiles:
+ if fname.endswith(".pyc"):
+ continue
+
+ file_ = os.path.join(dirpath, fname)
+ if (
+ os.path.isfile(file_)
+ and os.path.normcase(file_) not in _normcased_files
+ ):
+ # We are skipping this file. Add it to the set.
+ will_skip.add(file_)
+
+ will_remove = files | {os.path.join(folder, "*") for folder in folders}
+
+ return will_remove, will_skip
+
+
+class StashedUninstallPathSet:
+ """A set of file rename operations to stash files while
+ tentatively uninstalling them."""
+
+ def __init__(self) -> None:
+ # Mapping from source file root to [Adjacent]TempDirectory
+ # for files under that directory.
+ self._save_dirs: Dict[str, TempDirectory] = {}
+ # (old path, new path) tuples for each move that may need
+ # to be undone.
+ self._moves: List[Tuple[str, str]] = []
+
+ def _get_directory_stash(self, path: str) -> str:
+ """Stashes a directory.
+
+ Directories are stashed adjacent to their original location if
+ possible, or else moved/copied into the user's temp dir."""
+
+ try:
+ save_dir: TempDirectory = AdjacentTempDirectory(path)
+ except OSError:
+ save_dir = TempDirectory(kind="uninstall")
+ self._save_dirs[os.path.normcase(path)] = save_dir
+
+ return save_dir.path
+
+ def _get_file_stash(self, path: str) -> str:
+ """Stashes a file.
+
+ If no root has been provided, one will be created for the directory
+ in the user's temp directory."""
+ path = os.path.normcase(path)
+ head, old_head = os.path.dirname(path), None
+ save_dir = None
+
+ while head != old_head:
+ try:
+ save_dir = self._save_dirs[head]
+ break
+ except KeyError:
+ pass
+ head, old_head = os.path.dirname(head), head
+ else:
+ # Did not find any suitable root
+ head = os.path.dirname(path)
+ save_dir = TempDirectory(kind="uninstall")
+ self._save_dirs[head] = save_dir
+
+ relpath = os.path.relpath(path, head)
+ if relpath and relpath != os.path.curdir:
+ return os.path.join(save_dir.path, relpath)
+ return save_dir.path
+
+ def stash(self, path: str) -> str:
+ """Stashes the directory or file and returns its new location.
+ Handle symlinks as files to avoid modifying the symlink targets.
+ """
+ path_is_dir = os.path.isdir(path) and not os.path.islink(path)
+ if path_is_dir:
+ new_path = self._get_directory_stash(path)
+ else:
+ new_path = self._get_file_stash(path)
+
+ self._moves.append((path, new_path))
+ if path_is_dir and os.path.isdir(new_path):
+ # If we're moving a directory, we need to
+ # remove the destination first or else it will be
+ # moved to inside the existing directory.
+ # We just created new_path ourselves, so it will
+ # be removable.
+ os.rmdir(new_path)
+ renames(path, new_path)
+ return new_path
+
+ def commit(self) -> None:
+ """Commits the uninstall by removing stashed files."""
+ for save_dir in self._save_dirs.values():
+ save_dir.cleanup()
+ self._moves = []
+ self._save_dirs = {}
+
+ def rollback(self) -> None:
+ """Undoes the uninstall by moving stashed files back."""
+ for p in self._moves:
+ logger.info("Moving to %s\n from %s", *p)
+
+ for new_path, path in self._moves:
+ try:
+ logger.debug("Replacing %s from %s", new_path, path)
+ if os.path.isfile(new_path) or os.path.islink(new_path):
+ os.unlink(new_path)
+ elif os.path.isdir(new_path):
+ rmtree(new_path)
+ renames(path, new_path)
+ except OSError as ex:
+ logger.error("Failed to restore %s", new_path)
+ logger.debug("Exception: %s", ex)
+
+ self.commit()
+
+ @property
+ def can_rollback(self) -> bool:
+ return bool(self._moves)
+
+
+class UninstallPathSet:
+ """A set of file paths to be removed in the uninstallation of a
+ requirement."""
+
+ def __init__(self, dist: BaseDistribution) -> None:
+ self._paths: Set[str] = set()
+ self._refuse: Set[str] = set()
+ self._pth: Dict[str, UninstallPthEntries] = {}
+ self._dist = dist
+ self._moved_paths = StashedUninstallPathSet()
+ # Create local cache of normalize_path results. Creating an UninstallPathSet
+ # can result in hundreds/thousands of redundant calls to normalize_path with
+ # the same args, which hurts performance.
+ self._normalize_path_cached = functools.lru_cache(normalize_path)
+
+ def _permitted(self, path: str) -> bool:
+ """
+ Return True if the given path is one we are permitted to
+ remove/modify, False otherwise.
+
+ """
+ # aka is_local, but caching normalized sys.prefix
+ if not running_under_virtualenv():
+ return True
+ return path.startswith(self._normalize_path_cached(sys.prefix))
+
+ def add(self, path: str) -> None:
+ head, tail = os.path.split(path)
+
+ # we normalize the head to resolve parent directory symlinks, but not
+ # the tail, since we only want to uninstall symlinks, not their targets
+ path = os.path.join(self._normalize_path_cached(head), os.path.normcase(tail))
+
+ if not os.path.exists(path):
+ return
+ if self._permitted(path):
+ self._paths.add(path)
+ else:
+ self._refuse.add(path)
+
+ # __pycache__ files can show up after 'installed-files.txt' is created,
+ # due to imports
+ if os.path.splitext(path)[1] == ".py":
+ self.add(cache_from_source(path))
+
+ def add_pth(self, pth_file: str, entry: str) -> None:
+ pth_file = self._normalize_path_cached(pth_file)
+ if self._permitted(pth_file):
+ if pth_file not in self._pth:
+ self._pth[pth_file] = UninstallPthEntries(pth_file)
+ self._pth[pth_file].add(entry)
+ else:
+ self._refuse.add(pth_file)
+
+ def remove(self, auto_confirm: bool = False, verbose: bool = False) -> None:
+ """Remove paths in ``self._paths`` with confirmation (unless
+ ``auto_confirm`` is True)."""
+
+ if not self._paths:
+ logger.info(
+ "Can't uninstall '%s'. No files were found to uninstall.",
+ self._dist.raw_name,
+ )
+ return
+
+ dist_name_version = f"{self._dist.raw_name}-{self._dist.raw_version}"
+ logger.info("Uninstalling %s:", dist_name_version)
+
+ with indent_log():
+ if auto_confirm or self._allowed_to_proceed(verbose):
+ moved = self._moved_paths
+
+ for_rename = compress_for_rename(self._paths)
+
+ for path in sorted(compact(for_rename)):
+ moved.stash(path)
+ logger.verbose("Removing file or directory %s", path)
+
+ for pth in self._pth.values():
+ pth.remove()
+
+ logger.info("Successfully uninstalled %s", dist_name_version)
+
+ def _allowed_to_proceed(self, verbose: bool) -> bool:
+ """Display which files would be deleted and prompt for confirmation"""
+
+ def _display(msg: str, paths: Iterable[str]) -> None:
+ if not paths:
+ return
+
+ logger.info(msg)
+ with indent_log():
+ for path in sorted(compact(paths)):
+ logger.info(path)
+
+ if not verbose:
+ will_remove, will_skip = compress_for_output_listing(self._paths)
+ else:
+ # In verbose mode, display all the files that are going to be
+ # deleted.
+ will_remove = set(self._paths)
+ will_skip = set()
+
+ _display("Would remove:", will_remove)
+ _display("Would not remove (might be manually added):", will_skip)
+ _display("Would not remove (outside of prefix):", self._refuse)
+ if verbose:
+ _display("Will actually move:", compress_for_rename(self._paths))
+
+ return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n"
+
+ def rollback(self) -> None:
+ """Rollback the changes previously made by remove()."""
+ if not self._moved_paths.can_rollback:
+ logger.error(
+ "Can't roll back %s; was not uninstalled",
+ self._dist.raw_name,
+ )
+ return
+ logger.info("Rolling back uninstall of %s", self._dist.raw_name)
+ self._moved_paths.rollback()
+ for pth in self._pth.values():
+ pth.rollback()
+
+ def commit(self) -> None:
+ """Remove temporary save dir: rollback will no longer be possible."""
+ self._moved_paths.commit()
+
+ @classmethod
+ def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet":
+ dist_location = dist.location
+ info_location = dist.info_location
+ if dist_location is None:
+ logger.info(
+ "Not uninstalling %s since it is not installed",
+ dist.canonical_name,
+ )
+ return cls(dist)
+
+ normalized_dist_location = normalize_path(dist_location)
+ if not dist.local:
+ logger.info(
+ "Not uninstalling %s at %s, outside environment %s",
+ dist.canonical_name,
+ normalized_dist_location,
+ sys.prefix,
+ )
+ return cls(dist)
+
+ if normalized_dist_location in {
+ p
+ for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
+ if p
+ }:
+ logger.info(
+ "Not uninstalling %s at %s, as it is in the standard library.",
+ dist.canonical_name,
+ normalized_dist_location,
+ )
+ return cls(dist)
+
+ paths_to_remove = cls(dist)
+ develop_egg_link = egg_link_path_from_location(dist.raw_name)
+
+ # Distribution is installed with metadata in a "flat" .egg-info
+ # directory. This means it is not a modern .dist-info installation, an
+ # egg, or legacy editable.
+ setuptools_flat_installation = (
+ dist.installed_with_setuptools_egg_info
+ and info_location is not None
+ and os.path.exists(info_location)
+ # If dist is editable and the location points to a ``.egg-info``,
+ # we are in fact in the legacy editable case.
+ and not info_location.endswith(f"{dist.setuptools_filename}.egg-info")
+ )
+
+ # Uninstall cases order do matter as in the case of 2 installs of the
+ # same package, pip needs to uninstall the currently detected version
+ if setuptools_flat_installation:
+ if info_location is not None:
+ paths_to_remove.add(info_location)
+ installed_files = dist.iter_declared_entries()
+ if installed_files is not None:
+ for installed_file in installed_files:
+ paths_to_remove.add(os.path.join(dist_location, installed_file))
+ # FIXME: need a test for this elif block
+ # occurs with --single-version-externally-managed/--record outside
+ # of pip
+ elif dist.is_file("top_level.txt"):
+ try:
+ namespace_packages = dist.read_text("namespace_packages.txt")
+ except FileNotFoundError:
+ namespaces = []
+ else:
+ namespaces = namespace_packages.splitlines(keepends=False)
+ for top_level_pkg in [
+ p
+ for p in dist.read_text("top_level.txt").splitlines()
+ if p and p not in namespaces
+ ]:
+ path = os.path.join(dist_location, top_level_pkg)
+ paths_to_remove.add(path)
+ paths_to_remove.add(f"{path}.py")
+ paths_to_remove.add(f"{path}.pyc")
+ paths_to_remove.add(f"{path}.pyo")
+
+ elif dist.installed_by_distutils:
+ raise LegacyDistutilsInstall(distribution=dist)
+
+ elif dist.installed_as_egg:
+ # package installed by easy_install
+ # We cannot match on dist.egg_name because it can slightly vary
+ # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
+ paths_to_remove.add(dist_location)
+ easy_install_egg = os.path.split(dist_location)[1]
+ easy_install_pth = os.path.join(
+ os.path.dirname(dist_location),
+ "easy-install.pth",
+ )
+ paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
+
+ elif dist.installed_with_dist_info:
+ for path in uninstallation_paths(dist):
+ paths_to_remove.add(path)
+
+ elif develop_egg_link:
+ # PEP 660 modern editable is handled in the ``.dist-info`` case
+ # above, so this only covers the setuptools-style editable.
+ with open(develop_egg_link) as fh:
+ link_pointer = os.path.normcase(fh.readline().strip())
+ normalized_link_pointer = paths_to_remove._normalize_path_cached(
+ link_pointer
+ )
+ assert os.path.samefile(
+ normalized_link_pointer, normalized_dist_location
+ ), (
+ f"Egg-link {develop_egg_link} (to {link_pointer}) does not match "
+ f"installed location of {dist.raw_name} (at {dist_location})"
+ )
+ paths_to_remove.add(develop_egg_link)
+ easy_install_pth = os.path.join(
+ os.path.dirname(develop_egg_link), "easy-install.pth"
+ )
+ paths_to_remove.add_pth(easy_install_pth, dist_location)
+
+ else:
+ logger.debug(
+ "Not sure how to uninstall: %s - Check: %s",
+ dist,
+ dist_location,
+ )
+
+ if dist.in_usersite:
+ bin_dir = get_bin_user()
+ else:
+ bin_dir = get_bin_prefix()
+
+ # find distutils scripts= scripts
+ try:
+ for script in dist.iter_distutils_script_names():
+ paths_to_remove.add(os.path.join(bin_dir, script))
+ if WINDOWS:
+ paths_to_remove.add(os.path.join(bin_dir, f"{script}.bat"))
+ except (FileNotFoundError, NotADirectoryError):
+ pass
+
+ # find console_scripts and gui_scripts
+ def iter_scripts_to_remove(
+ dist: BaseDistribution,
+ bin_dir: str,
+ ) -> Generator[str, None, None]:
+ for entry_point in dist.iter_entry_points():
+ if entry_point.group == "console_scripts":
+ yield from _script_names(bin_dir, entry_point.name, False)
+ elif entry_point.group == "gui_scripts":
+ yield from _script_names(bin_dir, entry_point.name, True)
+
+ for s in iter_scripts_to_remove(dist, bin_dir):
+ paths_to_remove.add(s)
+
+ return paths_to_remove
+
+
+class UninstallPthEntries:
+ def __init__(self, pth_file: str) -> None:
+ self.file = pth_file
+ self.entries: Set[str] = set()
+ self._saved_lines: Optional[List[bytes]] = None
+
+ def add(self, entry: str) -> None:
+ entry = os.path.normcase(entry)
+ # On Windows, os.path.normcase converts the entry to use
+ # backslashes. This is correct for entries that describe absolute
+ # paths outside of site-packages, but all the others use forward
+ # slashes.
+ # os.path.splitdrive is used instead of os.path.isabs because isabs
+ # treats non-absolute paths with drive letter markings like c:foo\bar
+ # as absolute paths. It also does not recognize UNC paths if they don't
+ # have more than "\\sever\share". Valid examples: "\\server\share\" or
+ # "\\server\share\folder".
+ if WINDOWS and not os.path.splitdrive(entry)[0]:
+ entry = entry.replace("\\", "/")
+ self.entries.add(entry)
+
+ def remove(self) -> None:
+ logger.verbose("Removing pth entries from %s:", self.file)
+
+ # If the file doesn't exist, log a warning and return
+ if not os.path.isfile(self.file):
+ logger.warning("Cannot remove entries from nonexistent file %s", self.file)
+ return
+ with open(self.file, "rb") as fh:
+ # windows uses '\r\n' with py3k, but uses '\n' with py2.x
+ lines = fh.readlines()
+ self._saved_lines = lines
+ if any(b"\r\n" in line for line in lines):
+ endline = "\r\n"
+ else:
+ endline = "\n"
+ # handle missing trailing newline
+ if lines and not lines[-1].endswith(endline.encode("utf-8")):
+ lines[-1] = lines[-1] + endline.encode("utf-8")
+ for entry in self.entries:
+ try:
+ logger.verbose("Removing entry: %s", entry)
+ lines.remove((entry + endline).encode("utf-8"))
+ except ValueError:
+ pass
+ with open(self.file, "wb") as fh:
+ fh.writelines(lines)
+
+ def rollback(self) -> bool:
+ if self._saved_lines is None:
+ logger.error("Cannot roll back changes to %s, none were made", self.file)
+ return False
+ logger.debug("Rolling %s back to previous state", self.file)
+ with open(self.file, "wb") as fh:
+ fh.writelines(self._saved_lines)
+ return True
diff --git a/env/Lib/site-packages/pip/_internal/resolution/__init__.py b/env/Lib/site-packages/pip/_internal/resolution/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/pip/_internal/resolution/base.py b/env/Lib/site-packages/pip/_internal/resolution/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..42dade18c1ec2b825f756dad4aaa89f2d9e6ce21
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/resolution/base.py
@@ -0,0 +1,20 @@
+from typing import Callable, List, Optional
+
+from pip._internal.req.req_install import InstallRequirement
+from pip._internal.req.req_set import RequirementSet
+
+InstallRequirementProvider = Callable[
+ [str, Optional[InstallRequirement]], InstallRequirement
+]
+
+
+class BaseResolver:
+ def resolve(
+ self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
+ ) -> RequirementSet:
+ raise NotImplementedError()
+
+ def get_installation_order(
+ self, req_set: RequirementSet
+ ) -> List[InstallRequirement]:
+ raise NotImplementedError()
diff --git a/env/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py b/env/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/env/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py b/env/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py
new file mode 100644
index 0000000000000000000000000000000000000000..1dd0d7041bb7a0a32bdf22f825c52f87276e5e07
--- /dev/null
+++ b/env/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py
@@ -0,0 +1,597 @@
+"""Dependency Resolution
+
+The dependency resolution in pip is performed as follows:
+
+for top-level requirements:
+ a. only one spec allowed per project, regardless of conflicts or not.
+ otherwise a "double requirement" exception is raised
+ b. they override sub-dependency requirements.
+for sub-dependencies
+ a. "first found, wins" (where the order is breadth first)
+"""
+
+import logging
+import sys
+from collections import defaultdict
+from itertools import chain
+from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
+
+from pip._vendor.packaging import specifiers
+from pip._vendor.packaging.requirements import Requirement
+
+from pip._internal.cache import WheelCache
+from pip._internal.exceptions import (
+ BestVersionAlreadyInstalled,
+ DistributionNotFound,
+ HashError,
+ HashErrors,
+ InstallationError,
+ NoneMetadataError,
+ UnsupportedPythonVersion,
+)
+from pip._internal.index.package_finder import PackageFinder
+from pip._internal.metadata import BaseDistribution
+from pip._internal.models.link import Link
+from pip._internal.models.wheel import Wheel
+from pip._internal.operations.prepare import RequirementPreparer
+from pip._internal.req.req_install import (
+ InstallRequirement,
+ check_invalid_constraint_type,
+)
+from pip._internal.req.req_set import RequirementSet
+from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
+from pip._internal.utils import compatibility_tags
+from pip._internal.utils.compatibility_tags import get_supported
+from pip._internal.utils.direct_url_helpers import direct_url_from_link
+from pip._internal.utils.logging import indent_log
+from pip._internal.utils.misc import normalize_version_info
+from pip._internal.utils.packaging import check_requires_python
+
+logger = logging.getLogger(__name__)
+
+DiscoveredDependencies = DefaultDict[Optional[str], List[InstallRequirement]]
+
+
+def _check_dist_requires_python(
+ dist: BaseDistribution,
+ version_info: Tuple[int, int, int],
+ ignore_requires_python: bool = False,
+) -> None:
+ """
+ Check whether the given Python version is compatible with a distribution's
+ "Requires-Python" value.
+
+ :param version_info: A 3-tuple of ints representing the Python
+ major-minor-micro version to check.
+ :param ignore_requires_python: Whether to ignore the "Requires-Python"
+ value if the given Python version isn't compatible.
+
+ :raises UnsupportedPythonVersion: When the given Python version isn't
+ compatible.
+ """
+ # This idiosyncratically converts the SpecifierSet to str and let
+ # check_requires_python then parse it again into SpecifierSet. But this
+ # is the legacy resolver so I'm just not going to bother refactoring.
+ try:
+ requires_python = str(dist.requires_python)
+ except FileNotFoundError as e:
+ raise NoneMetadataError(dist, str(e))
+ try:
+ is_compatible = check_requires_python(
+ requires_python,
+ version_info=version_info,
+ )
+ except specifiers.InvalidSpecifier as exc:
+ logger.warning(
+ "Package %r has an invalid Requires-Python: %s", dist.raw_name, exc
+ )
+ return
+
+ if is_compatible:
+ return
+
+ version = ".".join(map(str, version_info))
+ if ignore_requires_python:
+ logger.debug(
+ "Ignoring failed Requires-Python check for package %r: %s not in %r",
+ dist.raw_name,
+ version,
+ requires_python,
+ )
+ return
+
+ raise UnsupportedPythonVersion(
+ f"Package {dist.raw_name!r} requires a different Python: "
+ f"{version} not in {requires_python!r}"
+ )
+
+
+class Resolver(BaseResolver):
+ """Resolves which packages need to be installed/uninstalled to perform \
+ the requested operation without breaking the requirements of any package.
+ """
+
+ _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
+
+ def __init__(
+ self,
+ preparer: RequirementPreparer,
+ finder: PackageFinder,
+ wheel_cache: Optional[WheelCache],
+ make_install_req: InstallRequirementProvider,
+ use_user_site: bool,
+ ignore_dependencies: bool,
+ ignore_installed: bool,
+ ignore_requires_python: bool,
+ force_reinstall: bool,
+ upgrade_strategy: str,
+ py_version_info: Optional[Tuple[int, ...]] = None,
+ ) -> None:
+ super().__init__()
+ assert upgrade_strategy in self._allowed_strategies
+
+ if py_version_info is None:
+ py_version_info = sys.version_info[:3]
+ else:
+ py_version_info = normalize_version_info(py_version_info)
+
+ self._py_version_info = py_version_info
+
+ self.preparer = preparer
+ self.finder = finder
+ self.wheel_cache = wheel_cache
+
+ self.upgrade_strategy = upgrade_strategy
+ self.force_reinstall = force_reinstall
+ self.ignore_dependencies = ignore_dependencies
+ self.ignore_installed = ignore_installed
+ self.ignore_requires_python = ignore_requires_python
+ self.use_user_site = use_user_site
+ self._make_install_req = make_install_req
+
+ self._discovered_dependencies: DiscoveredDependencies = defaultdict(list)
+
+ def resolve(
+ self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
+ ) -> RequirementSet:
+ """Resolve what operations need to be done
+
+ As a side-effect of this method, the packages (and their dependencies)
+ are downloaded, unpacked and prepared for installation. This
+ preparation is done by ``pip.operations.prepare``.
+
+ Once PyPI has static dependency metadata available, it would be
+ possible to move the preparation to become a step separated from
+ dependency resolution.
+ """
+ requirement_set = RequirementSet(check_supported_wheels=check_supported_wheels)
+ for req in root_reqs:
+ if req.constraint:
+ check_invalid_constraint_type(req)
+ self._add_requirement_to_set(requirement_set, req)
+
+ # Actually prepare the files, and collect any exceptions. Most hash
+ # exceptions cannot be checked ahead of time, because
+ # _populate_link() needs to be called before we can make decisions
+ # based on link type.
+ discovered_reqs: List[InstallRequirement] = []
+ hash_errors = HashErrors()
+ for req in chain(requirement_set.all_requirements, discovered_reqs):
+ try:
+ discovered_reqs.extend(self._resolve_one(requirement_set, req))
+ except HashError as exc:
+ exc.req = req
+ hash_errors.append(exc)
+
+ if hash_errors:
+ raise hash_errors
+
+ return requirement_set
+
+ def _add_requirement_to_set(
+ self,
+ requirement_set: RequirementSet,
+ install_req: InstallRequirement,
+ parent_req_name: Optional[str] = None,
+ extras_requested: Optional[Iterable[str]] = None,
+ ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
+ """Add install_req as a requirement to install.
+
+ :param parent_req_name: The name of the requirement that needed this
+ added. The name is used because when multiple unnamed requirements
+ resolve to the same name, we could otherwise end up with dependency
+ links that point outside the Requirements set. parent_req must
+ already be added. Note that None implies that this is a user
+ supplied requirement, vs an inferred one.
+ :param extras_requested: an iterable of extras used to evaluate the
+ environment markers.
+ :return: Additional requirements to scan. That is either [] if
+ the requirement is not applicable, or [install_req] if the
+ requirement is applicable and has just been added.
+ """
+ # If the markers do not match, ignore this requirement.
+ if not install_req.match_markers(extras_requested):
+ logger.info(
+ "Ignoring %s: markers '%s' don't match your environment",
+ install_req.name,
+ install_req.markers,
+ )
+ return [], None
+
+ # If the wheel is not supported, raise an error.
+ # Should check this after filtering out based on environment markers to
+ # allow specifying different wheels based on the environment/OS, in a
+ # single requirements file.
+ if install_req.link and install_req.link.is_wheel:
+ wheel = Wheel(install_req.link.filename)
+ tags = compatibility_tags.get_supported()
+ if requirement_set.check_supported_wheels and not wheel.supported(tags):
+ raise InstallationError(
+ f"{wheel.filename} is not a supported wheel on this platform."
+ )
+
+ # This next bit is really a sanity check.
+ assert (
+ not install_req.user_supplied or parent_req_name is None
+ ), "a user supplied req shouldn't have a parent"
+
+ # Unnamed requirements are scanned again and the requirement won't be
+ # added as a dependency until after scanning.
+ if not install_req.name:
+ requirement_set.add_unnamed_requirement(install_req)
+ return [install_req], None
+
+ try:
+ existing_req: Optional[InstallRequirement] = (
+ requirement_set.get_requirement(install_req.name)
+ )
+ except KeyError:
+ existing_req = None
+
+ has_conflicting_requirement = (
+ parent_req_name is None
+ and existing_req
+ and not existing_req.constraint
+ and existing_req.extras == install_req.extras
+ and existing_req.req
+ and install_req.req
+ and existing_req.req.specifier != install_req.req.specifier
+ )
+ if has_conflicting_requirement:
+ raise InstallationError(
+ f"Double requirement given: {install_req} "
+ f"(already in {existing_req}, name={install_req.name!r})"
+ )
+
+ # When no existing requirement exists, add the requirement as a
+ # dependency and it will be scanned again after.
+ if not existing_req:
+ requirement_set.add_named_requirement(install_req)
+ # We'd want to rescan this requirement later
+ return [install_req], install_req
+
+ # Assume there's no need to scan, and that we've already
+ # encountered this for scanning.
+ if install_req.constraint or not existing_req.constraint:
+ return [], existing_req
+
+ does_not_satisfy_constraint = install_req.link and not (
+ existing_req.link and install_req.link.path == existing_req.link.path
+ )
+ if does_not_satisfy_constraint:
+ raise InstallationError(
+ f"Could not satisfy constraints for '{install_req.name}': "
+ "installation from path or url cannot be "
+ "constrained to a version"
+ )
+ # If we're now installing a constraint, mark the existing
+ # object for real installation.
+ existing_req.constraint = False
+ # If we're now installing a user supplied requirement,
+ # mark the existing object as such.
+ if install_req.user_supplied:
+ existing_req.user_supplied = True
+ existing_req.extras = tuple(
+ sorted(set(existing_req.extras) | set(install_req.extras))
+ )
+ logger.debug(
+ "Setting %s extras to: %s",
+ existing_req,
+ existing_req.extras,
+ )
+ # Return the existing requirement for addition to the parent and
+ # scanning again.
+ return [existing_req], existing_req
+
+ def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
+ if self.upgrade_strategy == "to-satisfy-only":
+ return False
+ elif self.upgrade_strategy == "eager":
+ return True
+ else:
+ assert self.upgrade_strategy == "only-if-needed"
+ return req.user_supplied or req.constraint
+
+ def _set_req_to_reinstall(self, req: InstallRequirement) -> None:
+ """
+ Set a requirement to be installed.
+ """
+ # Don't uninstall the conflict if doing a user install and the
+ # conflict is not a user install.
+ assert req.satisfied_by is not None
+ if not self.use_user_site or req.satisfied_by.in_usersite:
+ req.should_reinstall = True
+ req.satisfied_by = None
+
+ def _check_skip_installed(
+ self, req_to_install: InstallRequirement
+ ) -> Optional[str]:
+ """Check if req_to_install should be skipped.
+
+ This will check if the req is installed, and whether we should upgrade
+ or reinstall it, taking into account all the relevant user options.
+
+ After calling this req_to_install will only have satisfied_by set to
+ None if the req_to_install is to be upgraded/reinstalled etc. Any
+ other value will be a dist recording the current thing installed that
+ satisfies the requirement.
+
+ Note that for vcs urls and the like we can't assess skipping in this
+ routine - we simply identify that we need to pull the thing down,
+ then later on it is pulled down and introspected to assess upgrade/
+ reinstalls etc.
+
+ :return: A text reason for why it was skipped, or None.
+ """
+ if self.ignore_installed:
+ return None
+
+ req_to_install.check_if_exists(self.use_user_site)
+ if not req_to_install.satisfied_by:
+ return None
+
+ if self.force_reinstall:
+ self._set_req_to_reinstall(req_to_install)
+ return None
+
+ if not self._is_upgrade_allowed(req_to_install):
+ if self.upgrade_strategy == "only-if-needed":
+ return "already satisfied, skipping upgrade"
+ return "already satisfied"
+
+ # Check for the possibility of an upgrade. For link-based
+ # requirements we have to pull the tree down and inspect to assess
+ # the version #, so it's handled way down.
+ if not req_to_install.link:
+ try:
+ self.finder.find_requirement(req_to_install, upgrade=True)
+ except BestVersionAlreadyInstalled:
+ # Then the best version is installed.
+ return "already up-to-date"
+ except DistributionNotFound:
+ # No distribution found, so we squash the error. It will
+ # be raised later when we re-try later to do the install.
+ # Why don't we just raise here?
+ pass
+
+ self._set_req_to_reinstall(req_to_install)
+ return None
+
+ def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]:
+ upgrade = self._is_upgrade_allowed(req)
+ best_candidate = self.finder.find_requirement(req, upgrade)
+ if not best_candidate:
+ return None
+
+ # Log a warning per PEP 592 if necessary before returning.
+ link = best_candidate.link
+ if link.is_yanked:
+ reason = link.yanked_reason or "