From 3b4c8a115167320bf7d0719811dfa684424dc19a Mon Sep 17 00:00:00 2001 From: Gloomer Date: Tue, 12 May 2026 21:31:46 +0300 Subject: [PATCH] =?UTF-8?q?Edited:=201.=20agent.py=20-=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D0=B0=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=20stream=5Fresponce.=202.=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B/=D0=BE=D1=82=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20chat.js,=20style.css.=203.=20flask.py=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=B1=D0=BB=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20OllamaProvider,=20Character,=20Agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/agent/__pycache__/agent.cpython-310.pyc | Bin 5184 -> 5310 bytes core/agent/agent.py | 30 + .../__pycache__/character.cpython-310.pyc | Bin 2491 -> 2491 bytes core/character/character.py | 3 +- .../flaskui/__pycache__/flask.cpython-310.pyc | Bin 524 -> 1281 bytes core/flaskui/flask.py | 65 +- core/flaskui/static/js/chat.js | 20 +- .../llm/__pycache__/ollamaapi.cpython-310.pyc | Bin 1245 -> 1245 bytes core/llm/ollamaapi.py | 1 - data/memory/test_char_001.json | 17 + .../requests-2.34.0.dist-info/INSTALLER | 1 + .../requests-2.34.0.dist-info/METADATA | 120 ++ .../requests-2.34.0.dist-info/RECORD | 47 + .../requests-2.34.0.dist-info/REQUESTED | 0 .../requests-2.34.0.dist-info/WHEEL | 5 + .../licenses/LICENSE | 175 +++ .../requests-2.34.0.dist-info/licenses/NOTICE | 2 + .../requests-2.34.0.dist-info/top_level.txt | 1 + venv/Lib/site-packages/requests/__init__.py | 219 +++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 4269 bytes .../__pycache__/__version__.cpython-310.pyc | Bin 0 -> 518 bytes .../_internal_utils.cpython-310.pyc | Bin 0 -> 1657 bytes .../__pycache__/_types.cpython-310.pyc | Bin 0 -> 5655 bytes .../__pycache__/adapters.cpython-310.pyc | Bin 0 -> 23034 bytes .../requests/__pycache__/api.cpython-310.pyc | Bin 0 -> 7356 bytes .../requests/__pycache__/auth.cpython-310.pyc | Bin 0 -> 9812 bytes .../__pycache__/certs.cpython-310.pyc | Bin 0 -> 595 bytes .../__pycache__/compat.cpython-310.pyc | Bin 0 -> 2053 bytes .../__pycache__/cookies.cpython-310.pyc | Bin 0 -> 21575 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 6620 bytes .../requests/__pycache__/help.cpython-310.pyc | Bin 0 -> 2875 bytes .../__pycache__/hooks.cpython-310.pyc | Bin 0 -> 1454 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 29086 bytes .../__pycache__/packages.cpython-310.pyc | Bin 0 -> 599 bytes .../__pycache__/sessions.cpython-310.pyc | Bin 0 -> 22925 bytes .../__pycache__/status_codes.cpython-310.pyc | Bin 0 -> 4786 bytes .../__pycache__/structures.cpython-310.pyc | Bin 0 -> 5841 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 28090 bytes .../Lib/site-packages/requests/__version__.py | 14 + .../site-packages/requests/_internal_utils.py | 51 + venv/Lib/site-packages/requests/_types.py | 178 +++ venv/Lib/site-packages/requests/adapters.py | 748 +++++++++++ venv/Lib/site-packages/requests/api.py | 180 +++ venv/Lib/site-packages/requests/auth.py | 354 +++++ venv/Lib/site-packages/requests/certs.py | 18 + venv/Lib/site-packages/requests/compat.py | 113 ++ venv/Lib/site-packages/requests/cookies.py | 625 +++++++++ venv/Lib/site-packages/requests/exceptions.py | 162 +++ venv/Lib/site-packages/requests/help.py | 134 ++ venv/Lib/site-packages/requests/hooks.py | 48 + venv/Lib/site-packages/requests/models.py | 1180 +++++++++++++++++ venv/Lib/site-packages/requests/packages.py | 23 + venv/Lib/site-packages/requests/py.typed | 0 venv/Lib/site-packages/requests/sessions.py | 920 +++++++++++++ .../site-packages/requests/status_codes.py | 128 ++ venv/Lib/site-packages/requests/structures.py | 130 ++ venv/Lib/site-packages/requests/utils.py | 1155 ++++++++++++++++ 57 files changed, 6859 insertions(+), 8 deletions(-) create mode 100644 data/memory/test_char_001.json create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/NOTICE create mode 100644 venv/Lib/site-packages/requests-2.34.0.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/requests/__init__.py create mode 100644 venv/Lib/site-packages/requests/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/__version__.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/_internal_utils.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/_types.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/adapters.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/api.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/auth.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/certs.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/compat.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/cookies.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/exceptions.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/help.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/hooks.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/models.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/packages.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/sessions.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/status_codes.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/structures.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/utils.cpython-310.pyc create mode 100644 venv/Lib/site-packages/requests/__version__.py create mode 100644 venv/Lib/site-packages/requests/_internal_utils.py create mode 100644 venv/Lib/site-packages/requests/_types.py create mode 100644 venv/Lib/site-packages/requests/adapters.py create mode 100644 venv/Lib/site-packages/requests/api.py create mode 100644 venv/Lib/site-packages/requests/auth.py create mode 100644 venv/Lib/site-packages/requests/certs.py create mode 100644 venv/Lib/site-packages/requests/compat.py create mode 100644 venv/Lib/site-packages/requests/cookies.py create mode 100644 venv/Lib/site-packages/requests/exceptions.py create mode 100644 venv/Lib/site-packages/requests/help.py create mode 100644 venv/Lib/site-packages/requests/hooks.py create mode 100644 venv/Lib/site-packages/requests/models.py create mode 100644 venv/Lib/site-packages/requests/packages.py create mode 100644 venv/Lib/site-packages/requests/py.typed create mode 100644 venv/Lib/site-packages/requests/sessions.py create mode 100644 venv/Lib/site-packages/requests/status_codes.py create mode 100644 venv/Lib/site-packages/requests/structures.py create mode 100644 venv/Lib/site-packages/requests/utils.py diff --git a/core/agent/__pycache__/agent.cpython-310.pyc b/core/agent/__pycache__/agent.cpython-310.pyc index 46fbd662ed3190838bd53d6875a3c4e52a466a6f..8c2379ca01150ff919ae5a8b623481eef02fe36d 100644 GIT binary patch delta 707 zcmZ9K&2JJx7{-|`g=JX=SiWl!O1~J4m1v_uW5h(QSVC-SJ#g8O?nG%|sk5aA6RQUk z4_<8EiwPI6B%b78qNy?Q<{#i;ZytN@-8e6{9NfcipZDw8c{BUH_>IwCD5QDhQ+_(N z^Ly@)4(N`IxTi@hPgAi{ho5Viv=y`BuSrw4(pJJsN)@tzx$vIdL z{3NT8)CMy1Nh^hl(ugTSmoIS{j2nsopG delta 622 zcmYL_O=}Zj5XX16dD(2TyGb^&FSMy{)#RzDEmdhvOFj737DAxa!(L)w_YmwRVKylW zf(U{j2$nfX4}#z)2<4uuEUeKd?rhEhn`u%fg~VYwEhgiZo-2 zF~|5f0&ASkGqMc6zexTccLv)o`ee$ zKS~lJiyK^sud~U~mPC{Y+j~B0oUbr=g?O!KY}fDNs!Wex@ireFbtXBi)4SrCM1t7SEZNO`8E&c4lhP_I{{?uIf!qK9 diff --git a/core/agent/agent.py b/core/agent/agent.py index 061a870..a85c872 100644 --- a/core/agent/agent.py +++ b/core/agent/agent.py @@ -32,6 +32,7 @@ There are some memories of your character: User`s name: {self.user_name} """ + '''def save_history(self): os.makedirs("data/history", exist_ok=True) path = f"data/history/{self.character.id}.json" @@ -185,6 +186,35 @@ User`s name: {self.user_name} return response + def stream_responce(self, user_input, temperature=None, max_tokens=None): #В ollamaapi нужно будет создать новый метод чисто на стриминг, пока что закомментил строки с цельным ответом + messages = self.build_messages(user_input) + if temperature is not None: + self.character.temperature = temperature + if max_tokens is not None: + self.character.max_tokens = max_tokens + self.character.save() + response = self.llm.generate_stream( + messages, + temperature=self.character.temperature, + max_tokens=self.character.max_tokens + ) + + # сохраняем историю + self.chat_history.append({ + "role": "user", + "content": user_input + }) + + self.chat_history.append({ + "role": "assistant", + "content": response + }) + + if len(self.chat_history) > 20: + self.summarize_history() + + return response + """ Пример использования agent`а (пример вызовов): diff --git a/core/character/__pycache__/character.cpython-310.pyc b/core/character/__pycache__/character.cpython-310.pyc index 28f7ba5a0d9bf4665ac19b249b2b1e7556c20ed8..fe17eb48512793cd6160b62e5da156c4210a726c 100644 GIT binary patch delta 373 zcmdljyjz$zpO=@50SG!1n6q3q@@{8hzr|WwnwerUnV-dcG83~CqvU2+W+p}z8K5Ff z#v%zIRU`=~q=2kj>?Qf}DVfP78k@6O@)>PqK`L3mDsS-=6y;~7CYQt)<>!~&;!Ms+ zEQ&8kEXlaVn1*f&OL1aZs>Nn;HWfw*IgkbNAVL91++r*$QUY<5CkL}Tuz_8|HF+Mp zvS*P#NSY-lKQZMNb9!otCfhCMz$D9X=DO)iNq%Fi#k#hILu zSQKB7SdwvzF%8`mmg2;+RP)W^Y$}WrvLFlOK!iNV0>+{uMG!}6axl9CH`payMe0D2 z$urrNU2d`DDoSaj9iydrHW=fF(NSh&uFai-^b5UJiWD1n9pUlcJ z*H8+i1myZ6V<2&hJ2@w@xHvbpBqKistP07+U^65qU*-s7l$or>>8_#&(#8%VK<+By O01*g_pvtFmssR8~Y)}IL diff --git a/core/character/character.py b/core/character/character.py index ba5c854..cc9e19d 100644 --- a/core/character/character.py +++ b/core/character/character.py @@ -34,7 +34,8 @@ class Character(): self.first_message = first_message self.temperature = temperature self.max_tokens = max_tokens - # Methods: + + #методы: def to_dict(self): return { diff --git a/core/flaskui/__pycache__/flask.cpython-310.pyc b/core/flaskui/__pycache__/flask.cpython-310.pyc index 2437825185c27e092e36babb3a03078af2ea39d1..b58e66ff1cd46a5f7e664ffff60772eac10934b4 100644 GIT binary patch literal 1281 zcmZWoOK&7K5N>@a=5F&~IamlWTylbbTBkOXS+rT*vqFk!^SW+FumiOI~wjC45x zPW0pr)lB&_d z&(9XY)&+44qQ53?04j=ipQ&f3P}$PVgFij`Vcy!6YO1z5owv@Mo*~?IwbirLWtYZm z`*;!FD3}|8x=Nv{DoFPIx38e$o;6|7YX83?fi2r2KLR5pfh;L328%D#C^JpoQcd5o zSUx)`XvYz{%+!Sn$`wosYrI$I#=Aab>H65P*vq2lka}GT!6wKd1VXu}d}!TeJnN&V4e6Lwf*qixMGx0)dD!V}j;XP(+*k z?GC_yQXF<^WP!tZNB+c8;!SdjESbfAZ>7T#%iZX%YddQr_0_6!Jq$dKf%g-ey|*0F zwi9LKd0BRJT}cKBI$pO*ZwD7)4{(2v7G87X*Upq@m4laa4^*bEUBlBtV#Y~DAP6J706b?f^Z)<= literal 524 zcmZXR%}T>S5XX0uY`PX(U%`Wy2)1~YQv3kHOA+-H0x@J)nwpQYNkDs&R`fOO(OdE4 zMW11F%~cS5g`S+<9xOOvXJ%)1=l>&{wOUOCc70)vz9D|8&AM5zIfRuTgJX!{7+oQb zxiQ18$;}sw8FQ?oImgUmyN%1`;$>q7sJaxGKRHPLG7O*DmNhz8^Xr%@A4 z?2=OjUQ^#sgM|BjZIqEqL{=7D1MBl^Q}zGQqiHY6SQ+!aLeJaaD53^@coS2zjeh|_ Cjdz6r diff --git a/core/flaskui/flask.py b/core/flaskui/flask.py index 88429e7..b649939 100644 --- a/core/flaskui/flask.py +++ b/core/flaskui/flask.py @@ -1,13 +1,70 @@ -from flask import * +from flask import Flask, render_template, request, jsonify +from core.agent.agent import Agent +from core.llm.ollamaapi import OllamaProvider +from core.character.character import Character ui = Flask(__name__) +#блок инициализации объектов + +llm = OllamaProvider("gemma3:4b") + +character = Character.load("test_char_001") + +agent = Agent(character, llm, user_name="Alex") + +agent.load_memory() +agent.ensure_first_message() + +#база @ui.route("/") @ui.route("/index") def index(): return render_template('index.html') -@ui.route("/chats") -def chats(): - return "

Страница чатов

" +#история чата +@ui.route("/init", methods=["GET"]) +def init(): + return jsonify({ + "messages": agent.chat_history, + "user_name": agent.user_name, + "character": { + "name": agent.character.name, + "avatar": agent.character.avatar_path + } + }) + +#запрос на генерацию +@ui.route("/chat", methods=["POST"]) +def chat(): + + data = request.json + + user_message = data["message"] + + response = agent.respond(user_message) + + agent.save_memory() + + return jsonify({ + "response": response + }) + + +"""#список персонажей +@ui.route("/characters", methods=["GET"]) +def get_characters(): + return Agent.get_all_char_info() + +#выбор персонажа (не робит) +@ui.route("/select_character", methods=["POST"]) +def select_character(): + pass + '''data = request.json + char_id = data["id"] + + global agent + agent = Agent(characters[char_id], llm) + + return jsonify({"status": "ok"})'''""" diff --git a/core/flaskui/static/js/chat.js b/core/flaskui/static/js/chat.js index 244b762..e40bf0d 100644 --- a/core/flaskui/static/js/chat.js +++ b/core/flaskui/static/js/chat.js @@ -1,5 +1,5 @@ const sendBtn = document.getElementById("send-btn"); - +loadChat(); sendBtn.addEventListener("click", async () => { const input = document.getElementById("user-input"); @@ -8,7 +8,11 @@ sendBtn.addEventListener("click", async () => { const messages = document.getElementById("messages"); messages.innerHTML += `

You: ${text}

`; + const source = new EventSource("/stream"); + source.onmessage = function(event) { + messages.innerHTML += event.data; + }; const response = await fetch("/chat", { method: "POST", headers: { @@ -24,4 +28,16 @@ sendBtn.addEventListener("click", async () => { messages.innerHTML += `

AI: ${data.response}

`; input.value = ""; -}); \ No newline at end of file +}); + +async function loadChat() { + + const res = await fetch("/init"); + const data = await res.json(); + + const messages = document.getElementById("messages"); + + data.messages.forEach(msg => { + messages.innerHTML += `

${msg.role}: ${msg.content}

`; + }); +} \ No newline at end of file diff --git a/core/llm/__pycache__/ollamaapi.cpython-310.pyc b/core/llm/__pycache__/ollamaapi.cpython-310.pyc index 096e3f7351e48fe9fdf84e7374c22f8768f36651..832c56f1137ce4f00ae3d793aa294c1e1de8a13c 100644 GIT binary patch delta 40 vcmcc1d6$znpO=@50SK<;F=wsb$m`3@cx!Srb0TBZ +Maintainer-email: Ian Stapleton Cordasco , Nate Prewitt +License: Apache-2.0 +Project-URL: Documentation, https://requests.readthedocs.io +Project-URL: Source, https://github.com/psf/requests +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +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 :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-File: LICENSE +License-File: NOTICE +Requires-Dist: charset_normalizer<4,>=2 +Requires-Dist: idna<4,>=2.5 +Requires-Dist: urllib3<3,>=1.26 +Requires-Dist: certifi>=2023.5.7 +Provides-Extra: security +Provides-Extra: socks +Requires-Dist: PySocks!=1.5.7,>=1.5.6; extra == "socks" +Provides-Extra: use-chardet-on-py3 +Requires-Dist: chardet<8,>=3.0.2; extra == "use-chardet-on-py3" +Dynamic: license-file + +# Requests + +[![Version](https://img.shields.io/pypi/v/requests.svg?maxAge=86400)](https://pypi.org/project/requests/) +[![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests) +[![Downloads](https://static.pepy.tech/badge/requests/month)](https://pepy.tech/project/requests) +[![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors) +[![Documentation](https://readthedocs.org/projects/requests/badge/?version=latest)](https://requests.readthedocs.io) + +**Requests** is a simple, yet elegant, HTTP library. + +```python +>>> import requests +>>> r = requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass')) +>>> r.status_code +200 +>>> r.headers['content-type'] +'application/json; charset=utf8' +>>> r.encoding +'utf-8' +>>> r.text +'{"authenticated": true, ...' +>>> r.json() +{'authenticated': True, ...} +``` + +Requests allows you to send HTTP/1.1 requests extremely easily. There’s no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method! + +Requests is one of the most downloaded Python packages today, pulling in around `300M downloads / week` — according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `4,000,000+` repositories. + +## Installing Requests and Supported Versions + +Requests is available on PyPI: + +```console +$ python -m pip install requests +``` + +Requests officially supports Python 3.10+. + +## Supported Features & Best–Practices + +Requests is ready for the demands of building robust and reliable HTTP–speaking applications, for the needs of today. + +- Keep-Alive & Connection Pooling +- International Domains and URLs +- Sessions with Cookie Persistence +- Browser-style TLS/SSL Verification +- Basic & Digest Authentication +- Familiar `dict`–like Cookies +- Automatic Content Decompression and Decoding +- Multi-part File Uploads +- SOCKS Proxy Support +- Connection Timeouts +- Streaming Downloads +- Automatic honoring of `.netrc` +- Chunked HTTP Requests + +## Cloning the repository + +When cloning the Requests repository, you may need to add the `-c +fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit timestamp (see +[this issue](https://github.com/psf/requests/issues/2690) for more background): + +```shell +git clone -c fetch.fsck.badTimezone=ignore https://github.com/psf/requests.git +``` + +You can also apply this setting to your global Git config: + +```shell +git config --global fetch.fsck.badTimezone ignore +``` + +--- + +[![Kenneth Reitz](https://raw.githubusercontent.com/psf/requests/main/ext/kr.png)](https://kennethreitz.org) [![Python Software Foundation](https://raw.githubusercontent.com/psf/requests/main/ext/psf.png)](https://www.python.org/psf) diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/RECORD b/venv/Lib/site-packages/requests-2.34.0.dist-info/RECORD new file mode 100644 index 0000000..5a4044a --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/RECORD @@ -0,0 +1,47 @@ +requests-2.34.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +requests-2.34.0.dist-info/METADATA,sha256=64w-xXm2cnAgXrcsEJ6g6498dx3VZgqS8TRITZnoerk,4806 +requests-2.34.0.dist-info/RECORD,, +requests-2.34.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +requests-2.34.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +requests-2.34.0.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142 +requests-2.34.0.dist-info/licenses/NOTICE,sha256=9REJct7a0rTp0xRRja87fXLW4C5Jms2AIYHeb3RXHcw,38 +requests-2.34.0.dist-info/top_level.txt,sha256=fMSVmHfb5rbGOo6xv-O_tUX6j-WyixssE-SnwcDRxNQ,9 +requests/__init__.py,sha256=MRFX5_pKqRZsgn1SN-b9aU7rXKVJ7kDi6Q-J8hq81W4,5922 +requests/__pycache__/__init__.cpython-310.pyc,, +requests/__pycache__/__version__.cpython-310.pyc,, +requests/__pycache__/_internal_utils.cpython-310.pyc,, +requests/__pycache__/_types.cpython-310.pyc,, +requests/__pycache__/adapters.cpython-310.pyc,, +requests/__pycache__/api.cpython-310.pyc,, +requests/__pycache__/auth.cpython-310.pyc,, +requests/__pycache__/certs.cpython-310.pyc,, +requests/__pycache__/compat.cpython-310.pyc,, +requests/__pycache__/cookies.cpython-310.pyc,, +requests/__pycache__/exceptions.cpython-310.pyc,, +requests/__pycache__/help.cpython-310.pyc,, +requests/__pycache__/hooks.cpython-310.pyc,, +requests/__pycache__/models.cpython-310.pyc,, +requests/__pycache__/packages.cpython-310.pyc,, +requests/__pycache__/sessions.cpython-310.pyc,, +requests/__pycache__/status_codes.cpython-310.pyc,, +requests/__pycache__/structures.cpython-310.pyc,, +requests/__pycache__/utils.cpython-310.pyc,, +requests/__version__.py,sha256=Uw6k1hzJNkFZyzAXYVHE-Qc_JiJOGDlZM6AsxLpMAyY,435 +requests/_internal_utils.py,sha256=TH2NEyyYmPx9cV5HPzrHR4XdxKuW0skkD4eDXcbZgf8,1542 +requests/_types.py,sha256=Z3uDlyGkslMrN0q5b0WQgqaL9KE-jL-Yi6p5Py926ec,5882 +requests/adapters.py,sha256=ZH6r1EU94jKqQLVf8DPW8Zd4kQt-cGnFH99jB2_HdJM,28064 +requests/api.py,sha256=TRVICsBG8Ikgl5joZQR270oo6-b4G0AHWPjvQuxrVQk,7152 +requests/auth.py,sha256=6TlRpVLUw8X9m4Sl4dSbA8qrpHI8T_ES2mBTYA7IWmU,12233 +requests/certs.py,sha256=_ZxrgzWc75D_bE7uq43MI4jaOC68p9AKSZs8C0NKh-Q,430 +requests/compat.py,sha256=XXm6KJaQtQFQRDQaH3vH0kE0_BOi2z78-FJRW_mqGLQ,2465 +requests/cookies.py,sha256=ChzmFA8rlCBSLFCA41bmifp5bCUi9VyhCCb17Y4Kcjw,21549 +requests/exceptions.py,sha256=xeGPM1JFSWjjN7swVvCGffLzfAoNLBp33AXn4LPAQRs,4564 +requests/help.py,sha256=cjUZuxiE2hjYT2svt49-v3-1f3MgcLYCWuBx2sai0Xk,4210 +requests/hooks.py,sha256=69igJHXTGg5HOo9VPpUB_0NkW5VjiFrVKETnpj8Ndqs,1138 +requests/models.py,sha256=PhqYjte-BQbUY_KJGMH4hYXtZm1Q0lboWEg0kwHL7-w,41698 +requests/packages.py,sha256=_g0gZ681UyAlKHRjH6kanbaoxx2eAb6qzcXiODyTIoc,904 +requests/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +requests/sessions.py,sha256=iuFhQXbkGx-MP7uGiTC1d0YqZmRSp0eelZ424p37Va8,34266 +requests/status_codes.py,sha256=GVD0fInPGAGXh-B9jOSPZtazjmIvfZTXCpAkf-5sBA4,4351 +requests/structures.py,sha256=upRgw5B48l5vHSokrJQaxvjS7pcZf6jI0MJi2KHmegI,4134 +requests/utils.py,sha256=ZX_QI0OyWGu9E4pOwPA87rdhDCcS-WrFTyinCL99B5w,36322 diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/REQUESTED b/venv/Lib/site-packages/requests-2.34.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/WHEEL b/venv/Lib/site-packages/requests-2.34.0.dist-info/WHEEL new file mode 100644 index 0000000..14a883f --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/LICENSE b/venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/LICENSE @@ -0,0 +1,175 @@ + + 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. diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/NOTICE b/venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/NOTICE new file mode 100644 index 0000000..1ff62db --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/licenses/NOTICE @@ -0,0 +1,2 @@ +Requests +Copyright 2019 Kenneth Reitz diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/top_level.txt b/venv/Lib/site-packages/requests-2.34.0.dist-info/top_level.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/top_level.txt @@ -0,0 +1 @@ +requests diff --git a/venv/Lib/site-packages/requests/__init__.py b/venv/Lib/site-packages/requests/__init__.py new file mode 100644 index 0000000..ad84240 --- /dev/null +++ b/venv/Lib/site-packages/requests/__init__.py @@ -0,0 +1,219 @@ +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +from __future__ import annotations + +import warnings + +import urllib3 + +from .exceptions import RequestsDependencyWarning + +try: + from charset_normalizer import __version__ as charset_normalizer_version +except ImportError: + charset_normalizer_version = None + +try: + from chardet import __version__ as chardet_version # type: ignore[import-not-found] +except ImportError: + chardet_version = None + + +def check_compatibility( + urllib3_version: str, + chardet_version: str | None, + charset_normalizer_version: str | None, +) -> None: + urllib3_version_list = urllib3_version.split(".")[:3] + assert urllib3_version_list != ["dev"] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version_list) == 2: + urllib3_version_list.append("0") + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version_list # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1 + assert major >= 1 + if major == 1: + assert minor >= 21 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 8.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (8, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 4.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0) + else: + warnings.warn( + "Unable to find acceptable character detection dependency " + "(chardet or charset_normalizer).", + RequestsDependencyWarning, + ) + + +def _check_cryptography(cryptography_version: str) -> None: + # cryptography < 1.3.4 + try: + cryptography_version_list = list(map(int, cryptography_version.split("."))) + except ValueError: + return + + if cryptography_version_list < [1, 3, 4]: + warning = f"Old version of cryptography ({cryptography_version_list}) may cause slowdown." + warnings.warn(warning, RequestsDependencyWarning) + + +# Check imported dependencies for compatibility. +try: + check_compatibility( + urllib3.__version__, # type: ignore[reportPrivateImportUsage] + chardet_version, # type: ignore[reportUnknownArgumentType] + charset_normalizer_version, + ) +except (AssertionError, ValueError): + warnings.warn( + f"urllib3 ({urllib3.__version__}) or chardet " # type: ignore[reportPrivateImportUsage] + f"({chardet_version})/charset_normalizer ({charset_normalizer_version}) " + "doesn't match a supported version!", + RequestsDependencyWarning, + ) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from urllib3.contrib import pyopenssl + + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import ( # type: ignore[reportMissingImports] + __version__ as cryptography_version, # type: ignore[reportUnknownVariableType] + ) + + _check_cryptography(cryptography_version) # type: ignore[reportUnknownArgumentType] +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from urllib3.exceptions import DependencyWarning + +warnings.simplefilter("ignore", DependencyWarning) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +from . import packages, utils +from .__version__ import ( + __author__, + __author_email__, + __build__, + __cake__, + __copyright__, + __description__, + __license__, + __title__, + __url__, + __version__, +) +from .api import delete, get, head, options, patch, post, put, request +from .exceptions import ( + ConnectionError, + ConnectTimeout, + FileModeWarning, + HTTPError, + JSONDecodeError, + ReadTimeout, + RequestException, + Timeout, + TooManyRedirects, + URLRequired, +) +from .models import PreparedRequest, Request, Response +from .sessions import Session, session +from .status_codes import codes + +__all__ = ( + "ConnectionError", + "ConnectTimeout", + "HTTPError", + "JSONDecodeError", + "PreparedRequest", + "ReadTimeout", + "Request", + "RequestException", + "Response", + "Session", + "Timeout", + "TooManyRedirects", + "URLRequired", + "codes", + "delete", + "get", + "head", + "options", + "packages", + "patch", + "post", + "put", + "request", + "session", + "utils", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter("default", FileModeWarning, append=True) diff --git a/venv/Lib/site-packages/requests/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22261eb05e37f91c2890f1fc88b98f408038b4b5 GIT binary patch literal 4269 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 Tr^!TZnhe)aw`~rM4qN{PE**yV literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/__version__.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/__version__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e080c5a1c7042614cd5a5ee0c06e8c0c670c4eb GIT binary patch literal 518 zcmY*W(MrQG6iv6fb=9Gu;G=yNv=bGCA>t53QDmr3p%7|wUE|uOCh1`P2me6uRo?{v zW{>&7yK4c??p?ofxfXp6fSWOa7pD7Y;^8(l8<$0nAUcXWB#_pYV#j*2wQv|p~o z#^k!&=}fo!3PNsT;4;#_kY!`j-`e&!1lg{UvV8(s1}65-L71{!Okscct*A=yr5coO zFAt*_);Il)a``~!V z6H!%JU+{6*>jr%#Zy+){=p9Eg&BF|D4MvcSf>Uu7XklO-TOEc2(82WiI)Cc(^YO&l VO(ic9*sK0gFCo~rZT%mP^$8cSqig^G literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/_internal_utils.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/_internal_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bccf50ee16e034586802e2cace645236a36e902 GIT binary patch literal 1657 zcmZuw&5j#I5bmBCk7sA?-Sr{`CFIaIFajeXBqR$VoUDP3k`S#opg7J>#?$3>XU9FJ zyJr`#3ia5dHba8z^Aq=x?-TNm zRbE|nRKCKkf5(H9A>oueDIHSoaxZme?$D(K=o63Ep3-5hlzf!@QmUg=FQovb0DK50 z&Ts>2P{;oeHX(!tYzSxA;s*nd#o|; zY#y6-_zZhUZa<@VwrR0B&P8g3jI>dD7i~xPkGdoMz@BO(AAC3(p6|YC(=F>d8r(ms zyp~7k*?PlN)MA|oQTP~N$m1danPl8bz5K5p}#q5V@ZB? zme}wmdEzYTQ@YaH^!)E{#3@J?vsDI`jUPc`K4J=vW5rX@nq?Da7IUyRn^L2*g1G_> z(!{p?=n*npB?#*{LZS*xqQfav!B=6b8d!`Fa99<@&ZeNC3?m`Iw6R)r*x^)QI&Bfv ztT(v6zt3`6KFVY%;)f~Jnb6QFFSgwx6k14aVwr%lttk950kmCJLsj8?kiyM!^9NCz zDF1t-*Bv3RY|(nu+b?03Sknl*ayq&x#v?5Zd_0enllU0)s3Pkp7s%R~FNz(LMXQ1> z9}uN0m_gg&3~L_syzsf_sHS~Ekgq)wOQdR9Ilx`w_ZbQme!6~x{7TOQiylYxDEWcd z@$rap7ndoqzk7tc19C>%-uYj9fHa;72~13|VFFwH0)tE<*KtZrjE!3NG?~UKPVkQI zgk>kYb2Q8>Y^-`|_hKNEv1AI#Nr&CawP9nZ9Lflm4;EjU8Fpu3rpS^{*+UHYaD`P@ zSu%9lILp%Vg7UE7!>`+(vMnfFbS?anDp2g+UDeRH?1#QmnQBwDg?=T7tD|AB?X0Vf zgpSJ5c^|FXdT!Mn8d9G&a6A7gJFFPPCT+KhdKB?2iK4<$Q247^U-74I6_G-5CHlU1 zbAK=Tw%^<9A4Io%xBAgRKe~4BuwPt$`?ta2LGj+}pD@PVm-jBjeDM)44lh-i%Cx=1 z?+yl$wZHood-;gkcws=RZLFwhtzY@_?uvJwZFgDwD~+>rpvvwAU(7O|r|_j^qP~iU aM}nZ?gieUP3u)j4)N_!^&#(HIn*RghVA{d} literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/_types.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/_types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad4a47ec7c7b3e3c816f02dda7d3ad24fe82ab19 GIT binary patch literal 5655 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 G^1lJfNp1`P literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/api.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80ec85dc90cf460d53061b2dc1a6d0f5d0a99360 GIT binary patch literal 7356 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/models.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e5273bd91e547768398165eb72575a02051e0d3 GIT binary patch literal 29086 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 J;u9mz{{e1vWETJc literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/packages.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/packages.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fb2f783467775787b90c42493505a9b796f5ce8 GIT binary patch literal 599 zcmZ8d&1w`u5U%Q;ooQ!h7g3PJC)h<_3h>KnXQ6aZsWSC4B$;{5rZg+1ao&tOE z9YVkyeWgBl@svly?|Qc3U$%0`7Dp8Q%jl3J8p_~` zx#|CU>xINax7Q&|#kJcJ-d>}NQ?x;Tk|{}M%51PvRaG1vSg(*xW^wlEo}%@Jw9&Iw zmM^no(lh1m2h*|T`b=9tZ|hZN`kD>?U~HJCMZId3NmJ`rZJt%ydR3u~4eC~ID$~&yI{|(8`Ev0rd9T`%a-);Q z>t0M#v`unK&1v$cI7)P3= U;rTemJq+xa2MgQUtiGon3k`m~IdQ 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/status_codes.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/status_codes.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91e9d0343b4aa30b2ae44a746c11770fa9e58099 GIT binary patch literal 4786 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 Aw*UYD literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__pycache__/structures.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/structures.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14824e0f689d6ad7e8193b61ec27db35661c25cf GIT binary patch literal 5841 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/requests/__version__.py b/venv/Lib/site-packages/requests/__version__.py new file mode 100644 index 0000000..872a8aa --- /dev/null +++ b/venv/Lib/site-packages/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = "requests" +__description__ = "Python HTTP for Humans." +__url__ = "https://requests.readthedocs.io" +__version__ = "2.34.0" +__build__ = 0x023400 +__author__ = "Kenneth Reitz" +__author_email__ = "me@kennethreitz.org" +__license__ = "Apache-2.0" +__copyright__ = "Copyright Kenneth Reitz" +__cake__ = "\u2728 \U0001f370 \u2728" diff --git a/venv/Lib/site-packages/requests/_internal_utils.py b/venv/Lib/site-packages/requests/_internal_utils.py new file mode 100644 index 0000000..0466a7d --- /dev/null +++ b/venv/Lib/site-packages/requests/_internal_utils.py @@ -0,0 +1,51 @@ +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +import re + +from .compat import builtin_str + +_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*\Z") +_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*\Z") +_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*\Z|^\Z") +_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*\Z|^\Z") + +_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR) +_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE) +HEADER_VALIDATORS = { + bytes: _HEADER_VALIDATORS_BYTE, + str: _HEADER_VALIDATORS_STR, +} + + +def to_native_string(string: str | bytes, encoding: str = "ascii") -> str: + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string: str) -> bool: + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode("ascii") + return True + except UnicodeEncodeError: + return False diff --git a/venv/Lib/site-packages/requests/_types.py b/venv/Lib/site-packages/requests/_types.py new file mode 100644 index 0000000..963867b --- /dev/null +++ b/venv/Lib/site-packages/requests/_types.py @@ -0,0 +1,178 @@ +""" +requests._types +~~~~~~~~~~~~~~~ + +This module contains type aliases used internally by the Requests library. +These types are not part of the public API and must not be relied upon +by external code. +""" + +from __future__ import annotations + +from collections.abc import Callable, Iterable, Mapping, MutableMapping +from typing import ( + TYPE_CHECKING, + Any, + Protocol, + TypeAlias, + TypeVar, + runtime_checkable, +) + +_T_co = TypeVar("_T_co", covariant=True) +_KT_co = TypeVar("_KT_co", covariant=True) +_VT_co = TypeVar("_VT_co", covariant=True) + + +@runtime_checkable +class SupportsRead(Protocol[_T_co]): + def read(self, length: int = ..., /) -> _T_co: ... + + +@runtime_checkable +class SupportsItems(Protocol[_KT_co, _VT_co]): + def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ... + + +# These are needed at runtime for default_hooks() return type +HookType: TypeAlias = Callable[["Response"], Any] +HooksInputType: TypeAlias = Mapping[str, Iterable[HookType] | HookType] + + +def is_prepared(request: PreparedRequest) -> TypeIs[_ValidatedRequest]: + """Verify a PreparedRequest has been fully prepared.""" + if TYPE_CHECKING: + return request.url is not None and request.method is not None + # noop at runtime to avoid AssertionError + return True + + +if TYPE_CHECKING: + from http.cookiejar import CookieJar + from typing import TypeAlias, TypedDict + + from typing_extensions import ( + Buffer, # TODO: move to collections.abc when Python >= 3.12 + TypeIs, # TODO: move to typing when Python >= 3.13 + ) + + from .auth import AuthBase + from .cookies import RequestsCookieJar + from .models import PreparedRequest, Response + from .structures import CaseInsensitiveDict + + class _ValidatedRequest(PreparedRequest): + """Subtype asserting a PreparedRequest has been fully prepared before calling. + + The override suppression is required because mutable attribute types are + invariant (Liskov), but we only narrow after preparation is complete. This + is the explicit contract for Requests but Python's typing doesn't have a + better way to represent the requirement. + """ + + url: str # type: ignore[reportIncompatibleVariableOverride] + method: str # type: ignore[reportIncompatibleVariableOverride] + + # Type aliases for core API concepts (ordered by request() signature) + UriType: TypeAlias = str | bytes + + _ParamsMappingKeyType: TypeAlias = str | bytes | int | float + _ParamsMappingValueType: TypeAlias = ( + str | bytes | int | float | Iterable[str | bytes | int | float] | None + ) + ParamsType: TypeAlias = ( + SupportsItems[_ParamsMappingKeyType, _ParamsMappingValueType] + | tuple[tuple[_ParamsMappingKeyType, _ParamsMappingValueType], ...] + | Iterable[tuple[_ParamsMappingKeyType, _ParamsMappingValueType]] + | str + | bytes + | None + ) + + KVDataType: TypeAlias = Iterable[tuple[Any, Any]] | SupportsItems[Any, Any] + + RawDataType: TypeAlias = KVDataType | str | bytes + StreamDataType: TypeAlias = SupportsRead[str | bytes] + EncodableDataType: TypeAlias = RawDataType | StreamDataType + + DataType: TypeAlias = ( + KVDataType + | Iterable[bytes | str] + | str + | bytes + | Buffer + | SupportsRead[str | bytes] + | None + ) + + BodyType: TypeAlias = ( + bytes | str | Iterable[bytes | str] | SupportsRead[bytes | str] | None + ) + + HeadersType: TypeAlias = CaseInsensitiveDict[str] | Mapping[str, str | bytes] + HeadersUpdateType: TypeAlias = Mapping[str, str | bytes | None] + + CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str] + + # Building blocks for FilesType + _FileName: TypeAlias = str | None + _FileContent: TypeAlias = SupportsRead[str | bytes] | str | bytes + _FileSpecBasic: TypeAlias = tuple[_FileName, _FileContent] + _FileSpecWithContentType: TypeAlias = tuple[_FileName, _FileContent, str] + _FileSpecWithHeaders: TypeAlias = tuple[ + _FileName, _FileContent, str, CaseInsensitiveDict[str] | Mapping[str, str] + ] + _FileSpec: TypeAlias = ( + _FileContent | _FileSpecBasic | _FileSpecWithContentType | _FileSpecWithHeaders + ) + FilesType: TypeAlias = ( + Mapping[str, _FileSpec] | Iterable[tuple[str, _FileSpec]] | None + ) + + AuthType: TypeAlias = ( + tuple[str, str] | AuthBase | Callable[[PreparedRequest], PreparedRequest] | None + ) + + TimeoutType: TypeAlias = float | tuple[float | None, float | None] | None + ProxiesType: TypeAlias = MutableMapping[str, str] + HooksType: TypeAlias = dict[str, list[HookType]] | None + VerifyType: TypeAlias = bool | str + CertType: TypeAlias = str | tuple[str, str] | None + JsonType: TypeAlias = ( + None | bool | int | float | str | list["JsonType"] | dict[str, "JsonType"] + ) + + # TypedDicts for Unpack kwargs (PEP 692) + + class BaseRequestKwargs(TypedDict, total=False): + headers: Mapping[str, str | bytes] | None + cookies: RequestsCookieJar | CookieJar | dict[str, str] | None + files: FilesType + auth: AuthType + timeout: TimeoutType + allow_redirects: bool + proxies: dict[str, str] | None + hooks: HooksInputType | None + stream: bool | None + verify: VerifyType | None + cert: CertType + + class RequestKwargs(BaseRequestKwargs, total=False): + """kwargs for request(), options(), head(), delete().""" + + params: ParamsType + data: DataType + json: JsonType + + class GetKwargs(BaseRequestKwargs, total=False): + data: DataType + json: JsonType + + class PostKwargs(BaseRequestKwargs, total=False): + params: ParamsType + + class DataKwargs(BaseRequestKwargs, total=False): + """kwargs for put(), patch().""" + + params: ParamsType + json: JsonType diff --git a/venv/Lib/site-packages/requests/adapters.py b/venv/Lib/site-packages/requests/adapters.py new file mode 100644 index 0000000..40fe8a6 --- /dev/null +++ b/venv/Lib/site-packages/requests/adapters.py @@ -0,0 +1,748 @@ +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +from __future__ import annotations + +import os.path +import socket # noqa: F401 # type: ignore[reportUnusedImport] +import typing +import warnings +from typing import Any + +from urllib3.exceptions import ( + ClosedPoolError, + ConnectTimeoutError, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ReadTimeoutError, + ResponseError, +) +from urllib3.exceptions import HTTPError as _HTTPError +from urllib3.exceptions import InvalidHeader as _InvalidHeader +from urllib3.exceptions import ProxyError as _ProxyError +from urllib3.exceptions import SSLError as _SSLError +from urllib3.poolmanager import PoolManager, proxy_from_url +from urllib3.util import Timeout as TimeoutSauce +from urllib3.util import parse_url +from urllib3.util.retry import Retry + +from .auth import _basic_auth_str # type: ignore[reportPrivateUsage] +from .compat import basestring, urlparse +from .cookies import extract_cookies_to_jar +from .exceptions import ( + ConnectionError, + ConnectTimeout, + InvalidHeader, + InvalidProxyURL, + InvalidSchema, + InvalidURL, + ProxyError, + ReadTimeout, + RetryError, + SSLError, +) +from .models import Response +from .structures import CaseInsensitiveDict +from .utils import ( + DEFAULT_CA_BUNDLE_PATH, + get_auth_from_url, + get_encoding_from_headers, + prepend_scheme_if_needed, + select_proxy, + urldefragauth, +) + +try: + from urllib3.contrib.socks import SOCKSProxyManager # type: ignore[assignment] +except ImportError: + + def SOCKSProxyManager(*args: Any, **kwargs: Any) -> None: + raise InvalidSchema("Missing dependencies for SOCKS support.") + + +if typing.TYPE_CHECKING: + from urllib3.connectionpool import HTTPConnectionPool + from urllib3.poolmanager import PoolManager as _PoolManager + + from . import _types as _t + from .models import PreparedRequest + +from ._types import is_prepared as _is_prepared + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +def _urllib3_request_context( + request: PreparedRequest, + verify: bool | str | None, + client_cert: tuple[str, str] | str | None, + poolmanager: PoolManager, +) -> tuple[dict[str, Any], dict[str, Any]]: + host_params: dict[str, Any] = {} + pool_kwargs: dict[str, Any] = {} + parsed_request_url = urlparse(request.url) + scheme = parsed_request_url.scheme.lower() + port = parsed_request_url.port + + cert_reqs = "CERT_REQUIRED" + if verify is False: + cert_reqs = "CERT_NONE" + elif isinstance(verify, str): + if not os.path.isdir(verify): + pool_kwargs["ca_certs"] = verify + else: + pool_kwargs["ca_cert_dir"] = verify + pool_kwargs["cert_reqs"] = cert_reqs + if client_cert is not None: + if isinstance(client_cert, tuple) and len(client_cert) == 2: + pool_kwargs["cert_file"] = client_cert[0] + pool_kwargs["key_file"] = client_cert[1] + else: + # According to our docs, we allow users to specify just the client + # cert path + pool_kwargs["cert_file"] = client_cert + host_params = { + "scheme": scheme, + "host": parsed_request_url.hostname, + "port": port, + } + return host_params, pool_kwargs + + +class BaseAdapter: + """The Base Transport Adapter""" + + def __init__(self) -> None: + super().__init__() + + def send( + self, + request: PreparedRequest, + stream: bool = False, + timeout: _t.TimeoutType = None, + verify: _t.VerifyType = True, + cert: _t.CertType = None, + proxies: dict[str, str] | None = None, + ) -> Response: + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self) -> None: + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + + __attrs__: list[str] = [ + "max_retries", + "config", + "_pool_connections", + "_pool_maxsize", + "_pool_block", + ] + + max_retries: Retry + config: dict[str, Any] + proxy_manager: dict[str, Any] + _pool_connections: int + _pool_maxsize: int + _pool_block: bool + poolmanager: _PoolManager + + def __init__( + self, + pool_connections: int = DEFAULT_POOLSIZE, + pool_maxsize: int = DEFAULT_POOLSIZE, + max_retries: int | Retry = DEFAULT_RETRIES, + pool_block: bool = DEFAULT_POOLBLOCK, + ) -> None: + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super().__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self) -> dict[str, Any]: + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state: dict[str, Any]) -> None: + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager( + self._pool_connections, self._pool_maxsize, block=self._pool_block + ) + + def init_poolmanager( + self, + connections: int, + maxsize: int, + block: bool = DEFAULT_POOLBLOCK, + **pool_kwargs: Any, + ) -> None: + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager( + num_pools=connections, + maxsize=maxsize, + block=block, + **pool_kwargs, + ) + + def proxy_manager_for(self, proxy: str, **proxy_kwargs: Any) -> Any: + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith("socks"): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + + return manager + + def cert_verify( + self, conn: Any, url: str, verify: _t.VerifyType, cert: _t.CertType + ) -> None: + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith("https") and verify: + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = DEFAULT_CA_BUNDLE_PATH + + if not cert_loc or not os.path.exists(cert_loc): + raise OSError( + f"Could not find a suitable TLS CA certificate bundle, " + f"invalid path: {cert_loc}" + ) + + conn.cert_reqs = "CERT_REQUIRED" + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = "CERT_NONE" + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise OSError( + f"Could not find the TLS certificate file, " + f"invalid path: {conn.cert_file}" + ) + if conn.key_file and not os.path.exists(conn.key_file): + raise OSError( + f"Could not find the TLS key file, invalid path: {conn.key_file}" + ) + + def build_response(self, req: PreparedRequest, resp: Any) -> Response: + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + assert _is_prepared(req) + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, "status", None) # type: ignore[assignment] + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, "headers", {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode("utf-8") + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def build_connection_pool_key_attributes( + self, request: PreparedRequest, verify: _t.VerifyType, cert: _t.CertType = None + ) -> tuple[dict[str, Any], dict[str, Any]]: + """Build the PoolKey attributes used by urllib3 to return a connection. + + This looks at the PreparedRequest, the user-specified verify value, + and the value of the cert parameter to determine what PoolKey values + to use to select a connection from a given urllib3 Connection Pool. + + The SSL related pool key arguments are not consistently set. As of + this writing, use the following to determine what keys may be in that + dictionary: + + * If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the + default Requests SSL Context + * If ``verify`` is ``False``, ``"ssl_context"`` will not be set but + ``"cert_reqs"`` will be set + * If ``verify`` is a string, (i.e., it is a user-specified trust bundle) + ``"ca_certs"`` will be set if the string is not a directory recognized + by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be + set. + * If ``"cert"`` is specified, ``"cert_file"`` will always be set. If + ``"cert"`` is a tuple with a second item, ``"key_file"`` will also + be present + + To override these settings, one may subclass this class, call this + method and use the above logic to change parameters as desired. For + example, if one wishes to use a custom :py:class:`ssl.SSLContext` one + must both set ``"ssl_context"`` and based on what else they require, + alter the other keys to ensure the desired behaviour. + + :param request: + The PreparedRequest being sent over the connection. + :type request: + :class:`~requests.models.PreparedRequest` + :param verify: + Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use. + :param cert: + (optional) Any user-provided SSL certificate for client + authentication (a.k.a., mTLS). This may be a string (i.e., just + the path to a file which holds both certificate and key) or a + tuple of length 2 with the certificate file path and key file + path. + :returns: + A tuple of two dictionaries. The first is the "host parameters" + portion of the Pool Key including scheme, hostname, and port. The + second is a dictionary of SSLContext related parameters. + """ + return _urllib3_request_context(request, verify, cert, self.poolmanager) + + def get_connection_with_tls_context( + self, + request: PreparedRequest, + verify: _t.VerifyType, + proxies: dict[str, str] | None = None, + cert: _t.CertType = None, + ) -> HTTPConnectionPool: + """Returns a urllib3 connection for the given request and TLS settings. + This should not be called from user code, and is only exposed for use + when subclassing the :class:`HTTPAdapter `. + + :param request: + The :class:`PreparedRequest ` object to be sent + over the connection. + :param verify: + Either a boolean, in which case it controls whether we verify the + server's TLS certificate, or a string, in which case it must be a + path to a CA bundle to use. + :param proxies: + (optional) The proxies dictionary to apply to the request. + :param cert: + (optional) Any user-provided SSL certificate to be used for client + authentication (a.k.a., mTLS). + :rtype: + urllib3.HTTPConnectionPool + """ + assert _is_prepared(request) + + proxy = select_proxy(request.url, proxies) + try: + host_params, pool_kwargs = self.build_connection_pool_key_attributes( + request, + verify, + cert, + ) + except ValueError as e: + raise InvalidURL(e, request=request) + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_host( + **host_params, pool_kwargs=pool_kwargs + ) + else: + # Only scheme should be lower case + conn = self.poolmanager.connection_from_host( + **host_params, pool_kwargs=pool_kwargs + ) + + return conn + + def get_connection( + self, url: str, proxies: dict[str, str] | None = None + ) -> HTTPConnectionPool: + """DEPRECATED: Users should move to `get_connection_with_tls_context` + for all subclasses of HTTPAdapter using Requests>=2.32.2. + + Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.HTTPConnectionPool + """ + warnings.warn( + ( + "`get_connection` has been deprecated in favor of " + "`get_connection_with_tls_context`. Custom HTTPAdapter subclasses " + "will need to migrate for Requests>=2.32.2. Please see " + "https://github.com/psf/requests/pull/6710 for more details." + ), + DeprecationWarning, + ) + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self) -> None: + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url( + self, request: PreparedRequest, proxies: dict[str, str] | None + ) -> str: + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + assert _is_prepared(request) + + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = proxy and scheme != "https" + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith("socks") + + url = request.path_url + + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request: PreparedRequest, **kwargs: Any) -> None: + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy: str) -> dict[str, str]: + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers: dict[str, str] = {} + username, password = get_auth_from_url(proxy) + + if username: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return headers + + def send( + self, + request: PreparedRequest, + stream: bool = False, + timeout: _t.TimeoutType = None, + verify: _t.VerifyType = True, + cert: _t.CertType = None, + proxies: dict[str, str] | None = None, + ) -> Response: + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + assert _is_prepared(request) + + try: + conn = self.get_connection_with_tls_context( + request, verify, proxies=proxies, cert=cert + ) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers( + request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + ) + + chunked = not (request.body is None or "Content-Length" in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + resolved_timeout = TimeoutSauce(connect=connect, read=read) + except ValueError: + raise ValueError( + f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, " + f"or a single float to set both timeouts to the same value." + ) + elif isinstance(timeout, TimeoutSauce): + resolved_timeout = timeout + else: + resolved_timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, # type: ignore[arg-type] # urllib3 stubs don't accept Iterable[bytes | str] + headers=request.headers, # type: ignore[arg-type] # urllib3#3072 + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=resolved_timeout, + chunked=chunked, + ) + + except (ProtocolError, OSError) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/venv/Lib/site-packages/requests/api.py b/venv/Lib/site-packages/requests/api.py new file mode 100644 index 0000000..eeb3b54 --- /dev/null +++ b/venv/Lib/site-packages/requests/api.py @@ -0,0 +1,180 @@ +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from . import sessions +from .models import Response + +if TYPE_CHECKING: + from typing_extensions import Unpack + + from . import _types as _t + + +def request( + method: str, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs] +) -> Response: + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get( + url: _t.UriType, params: _t.ParamsType = None, **kwargs: Unpack[_t.GetKwargs] +) -> Response: + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("get", url, params=params, **kwargs) + + +def options(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response: + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("options", url, **kwargs) + + +def head(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response: + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return request("head", url, **kwargs) + + +def post( + url: _t.UriType, + data: _t.DataType = None, + json: _t.JsonType = None, + **kwargs: Unpack[_t.PostKwargs], +) -> Response: + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("post", url, data=data, json=json, **kwargs) + + +def put( + url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs] +) -> Response: + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("put", url, data=data, **kwargs) + + +def patch( + url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs] +) -> Response: + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("patch", url, data=data, **kwargs) + + +def delete(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response: + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("delete", url, **kwargs) diff --git a/venv/Lib/site-packages/requests/auth.py b/venv/Lib/site-packages/requests/auth.py new file mode 100644 index 0000000..2af481d --- /dev/null +++ b/venv/Lib/site-packages/requests/auth.py @@ -0,0 +1,354 @@ +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +from __future__ import annotations + +import hashlib +import os +import re +import threading +import time +import warnings +from base64 import b64encode +from typing import TYPE_CHECKING, Any, Final, cast, overload + +from ._internal_utils import to_native_string +from .compat import basestring, str, urlparse +from .cookies import extract_cookies_to_jar +from .utils import parse_dict_header + +if TYPE_CHECKING: + from http.cookiejar import CookieJar + from typing import Any + + from .models import PreparedRequest, Response + +CONTENT_TYPE_FORM_URLENCODED: Final = "application/x-www-form-urlencoded" +CONTENT_TYPE_MULTI_PART: Final = "multipart/form-data" + + +def _basic_auth_str(username: bytes | str, password: bytes | str) -> str: + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for non-str/bytes + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + f"3.0.0. Please convert the object you've passed in ({username!r}) to " + "a string or bytes object in the near future to avoid " + "problems.", + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for non-str/bytes + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + f"3.0.0. Please convert the object you've passed in ({type(password)!r}) to " + "a string or bytes object in the near future to avoid " + "problems.", + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode("latin1") + + if isinstance(password, str): + password = password.encode("latin1") + + authstr = "Basic " + to_native_string( + b64encode(b":".join((username, password))).strip() + ) + + return authstr + + +class AuthBase: + """Base class that all auth implementations derive from""" + + def __call__(self, r: PreparedRequest) -> PreparedRequest: + raise NotImplementedError("Auth hooks must be callable.") + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + username: bytes | str + password: bytes | str + + @overload + def __init__(self, username: str, password: str) -> None: ... + @overload + def __init__(self, username: bytes, password: bytes) -> None: ... + + def __init__(self, username: bytes | str, password: bytes | str) -> None: + self.username = username + self.password = password + + def __eq__(self, other: object) -> bool: + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other: Any) -> bool: + return not self == other + + def __call__(self, r: PreparedRequest) -> PreparedRequest: + r.headers["Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r: PreparedRequest) -> PreparedRequest: + r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + username: bytes | str + password: bytes | str + _thread_local: threading.local + last_nonce: str + nonce_count: int + chal: dict[str, str] + pos: int | None + num_401_calls: int | None + + @overload + def __init__(self, username: str, password: str) -> None: ... + @overload + def __init__(self, username: bytes, password: bytes) -> None: ... + + def __init__(self, username: bytes | str, password: bytes | str) -> None: + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self) -> None: + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, "init"): + self._thread_local.init = True + self._thread_local.last_nonce = "" + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method: str, url: str) -> str | None: + """ + :rtype: str + """ + + realm = self._thread_local.chal["realm"] + nonce = self._thread_local.chal["nonce"] + qop = self._thread_local.chal.get("qop") + algorithm = self._thread_local.chal.get("algorithm") + opaque = self._thread_local.chal.get("opaque") + hash_utf8 = None + + if algorithm is None: + _algorithm = "MD5" + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == "MD5" or _algorithm == "MD5-SESS": + + def md5_utf8(x: str | bytes) -> str: + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.md5(x, usedforsecurity=False).hexdigest() + + hash_utf8 = md5_utf8 + elif _algorithm == "SHA": + + def sha_utf8(x: str | bytes) -> str: + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha1(x, usedforsecurity=False).hexdigest() + + hash_utf8 = sha_utf8 + elif _algorithm == "SHA-256": + + def sha256_utf8(x: str | bytes) -> str: + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha256(x, usedforsecurity=False).hexdigest() + + hash_utf8 = sha256_utf8 + elif _algorithm == "SHA-512": + + def sha512_utf8(x: str | bytes) -> str: + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha512(x, usedforsecurity=False).hexdigest() + + hash_utf8 = sha512_utf8 + + if hash_utf8 is None: + return None + + def KD(s: str, d: str) -> str: + return hash_utf8(f"{s}:{d}") + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += f"?{p_parsed.query}" + + A1 = f"{self.username}:{realm}:{self.password}" + A2 = f"{method}:{path}" + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = f"{self._thread_local.nonce_count:08x}" + s = str(self._thread_local.nonce_count).encode("utf-8") + s += nonce.encode("utf-8") + s += time.ctime().encode("utf-8") + s += os.urandom(8) + + cnonce = hashlib.sha1(s, usedforsecurity=False).hexdigest()[:16] + if _algorithm == "MD5-SESS": + HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}") # type: ignore[reportConstantRedefinition] # RFC 2617 terminology + + if not qop: + respdig = KD(HA1, f"{nonce}:{HA2}") + elif qop == "auth" or "auth" in qop.split(","): + noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}" + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = ( + f'username="{self.username}", realm="{realm}", nonce="{nonce}", ' + f'uri="{path}", response="{respdig}"' + ) + if opaque: + base += f', opaque="{opaque}"' + if algorithm: + base += f', algorithm="{algorithm}"' + if entdig: + base += f', digest="{entdig}"' + if qop: + base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"' + + return f"Digest {base}" + + def handle_redirect(self, r: Response, **kwargs: Any) -> None: + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r: Response, **kwargs: Any) -> Response: + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + if (seek := getattr(r.request.body, "seek", None)) is not None: + seek(self._thread_local.pos) + s_auth = r.headers.get("www-authenticate", "") + + if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2: + self._thread_local.num_401_calls += 1 + pat = re.compile(r"digest ", flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + cookie_jar = cast("CookieJar", prep._cookies) # type: ignore[reportPrivateUsage] + extract_cookies_to_jar(cookie_jar, r.request, r.raw) + prep.prepare_cookies(cookie_jar) + + _digest_auth = self.build_digest_header( + cast(str, prep.method), cast(str, prep.url) + ) + if _digest_auth: + prep.headers["Authorization"] = _digest_auth + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r: PreparedRequest) -> PreparedRequest: + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + _digest_auth = self.build_digest_header( + cast(str, r.method), cast(str, r.url) + ) + if _digest_auth: + r.headers["Authorization"] = _digest_auth + if (tell := getattr(r.body, "tell", None)) is not None: + self._thread_local.pos = tell() + else: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook("response", self.handle_401) + r.register_hook("response", self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other: object) -> bool: + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other: Any) -> bool: + return not self == other diff --git a/venv/Lib/site-packages/requests/certs.py b/venv/Lib/site-packages/requests/certs.py new file mode 100644 index 0000000..4f85ac0 --- /dev/null +++ b/venv/Lib/site-packages/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" + +from certifi import where + +if __name__ == "__main__": + print(where()) diff --git a/venv/Lib/site-packages/requests/compat.py b/venv/Lib/site-packages/requests/compat.py new file mode 100644 index 0000000..deab3c0 --- /dev/null +++ b/venv/Lib/site-packages/requests/compat.py @@ -0,0 +1,113 @@ +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module previously handled import compatibility issues +between Python 2 and Python 3. It remains for backwards +compatibility until the next major version. +""" + +# pyright: reportUnusedImport=false + +from __future__ import annotations + +import importlib +import sys +from types import ModuleType + +# ------- +# urllib3 +# ------- +from urllib3 import ( + __version__ as urllib3_version, # type: ignore[reportPrivateImportUsage] +) + +# Detect which major version of urllib3 is being used. +try: + is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1 +except (TypeError, AttributeError): + # If we can't discern a version, prefer old functionality. + is_urllib3_1 = True + +# ------------------- +# Character Detection +# ------------------- + + +def _resolve_char_detection() -> ModuleType | None: + """Find supported character detection libraries.""" + chardet = None + for lib in ("chardet", "charset_normalizer"): + if chardet is None: + try: + chardet = importlib.import_module(lib) + except ImportError: + pass + return chardet + + +chardet = _resolve_char_detection() + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = _ver[0] == 2 + +#: Python 3.x? +is_py3 = _ver[0] == 3 + +# json/simplejson module import resolution +has_simplejson = False +try: + import simplejson as json # type: ignore[import-not-found] + + has_simplejson = True +except ImportError: + import json + +if has_simplejson: + from simplejson import JSONDecodeError # type: ignore[import-not-found] +else: + from json import JSONDecodeError + +# Keep OrderedDict for backwards compatibility. +from collections import OrderedDict +from collections.abc import Callable, Mapping, MutableMapping +from http import cookiejar as cookielib +from http.cookies import Morsel +from io import StringIO + +# -------------- +# Legacy Imports +# -------------- +from urllib.parse import ( + quote, + quote_plus, + unquote, + unquote_plus, + urldefrag, + urlencode, + urljoin, + urlparse, + urlsplit, + urlunparse, +) +from urllib.request import ( + getproxies, + getproxies_environment, + parse_http_list, + proxy_bypass, + proxy_bypass_environment, # type: ignore[attr-defined] # https://github.com/python/cpython/issues/145331 +) + +builtin_str = str +str = str +bytes = bytes +basestring = (str, bytes) +numeric_types = (int, float) +integer_types = (int,) diff --git a/venv/Lib/site-packages/requests/cookies.py b/venv/Lib/site-packages/requests/cookies.py new file mode 100644 index 0000000..2e3fc21 --- /dev/null +++ b/venv/Lib/site-packages/requests/cookies.py @@ -0,0 +1,625 @@ +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `http.cookiejar.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +from __future__ import annotations + +import calendar +import copy +import time +from collections.abc import Iterator, MutableMapping +from http.cookiejar import Cookie, CookieJar, CookiePolicy +from typing import TYPE_CHECKING, Any, TypeVar, overload + +from ._internal_utils import to_native_string +from ._types import is_prepared as _is_prepared +from .compat import Morsel, cookielib, urlparse, urlunparse + +if TYPE_CHECKING: + from _typeshed import SupportsKeysAndGetItem + + from .models import PreparedRequest + +import threading + + +class MockRequest: + """Wraps a `requests.PreparedRequest` to mimic a `urllib2.Request`. + + The code in `http.cookiejar.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + type: str + + def __init__(self, request: PreparedRequest) -> None: + assert _is_prepared(request) + self._r = request + self._new_headers: dict[str, str] = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self) -> str: + return self.type + + def get_host(self) -> str: + return urlparse(self._r.url).netloc + + def get_origin_req_host(self) -> str: + return self.get_host() + + def get_full_url(self) -> str: + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get("Host"): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers["Host"], encoding="utf-8") + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse( + [ + parsed.scheme, + host, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment, + ] + ) + + def is_unverifiable(self) -> bool: + return True + + def has_header(self, name: str) -> bool: + return name in self._r.headers or name in self._new_headers + + def get_header(self, name: str, default: str | None = None) -> str | None: + return self._r.headers.get(name, self._new_headers.get(name, default)) # type: ignore[return-value] + + def add_header(self, key: str, val: str) -> None: + """cookiejar has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError( + "Cookie headers should be added with add_unredirected_header()" + ) + + def add_unredirected_header(self, name: str, value: str) -> None: + self._new_headers[name] = value + + def get_new_headers(self) -> dict[str, str]: + return self._new_headers + + @property + def unverifiable(self) -> bool: + return self.is_unverifiable() + + @property + def origin_req_host(self) -> str: + return self.get_origin_req_host() + + @property + def host(self) -> str: + return self.get_host() + + +class MockResponse: + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `http.cookiejar` expects to see them. + """ + + def __init__(self, headers: Any) -> None: + """Make a MockResponse for `cookiejar` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self) -> Any: + return self._headers + + def getheaders(self, name: str) -> Any: + self._headers.getheaders(name) + + +def extract_cookies_to_jar( + jar: CookieJar, request: PreparedRequest, response: Any +) -> None: + """Extract the cookies from the response into a CookieJar. + + :param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, "_original_response") and response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) # type: ignore[arg-type] + + +def get_cookie_header(jar: CookieJar, request: PreparedRequest) -> str | None: + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) # type: ignore[arg-type] + return r.get_new_headers().get("Cookie") + + +def remove_cookie_by_name( + cookiejar: CookieJar, name: str, domain: str | None = None, path: str | None = None +) -> None: + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables: list[tuple[str, str, str]] = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(CookieJar, MutableMapping[str, str | None]): # type: ignore[misc] + """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + _policy: CookiePolicy + + def get( # type: ignore[override] + self, + name: str, + default: str | None = None, + domain: str | None = None, + path: str | None = None, + ) -> str | None: + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set( + self, name: str, value: str | Morsel[dict[str, str]] | None, **kwargs: Any + ) -> Cookie | None: + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name( + self, name, domain=kwargs.get("domain"), path=kwargs.get("path") + ) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self) -> Iterator[str]: + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self) -> list[str]: # type: ignore[override] + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self) -> Iterator[str | None]: + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self) -> list[str | None]: # type: ignore[override] + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self) -> Iterator[tuple[str, str | None]]: + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self) -> list[tuple[str, str | None]]: # type: ignore[override] + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self) -> list[str]: + """Utility method to list all the domains in the jar.""" + domains: list[str] = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self) -> list[str]: + """Utility method to list all the paths in the jar.""" + paths: list[str] = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self) -> bool: + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains: list[str] = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: # type: ignore[reportUnnecessaryComparison] # defensive check + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict( + self, domain: str | None = None, path: str | None = None + ) -> dict[str, str | None]: + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary: dict[str, str | None] = {} + for cookie in iter(self): + if (domain is None or cookie.domain == domain) and ( + path is None or cookie.path == path + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __iter__(self) -> Iterator[Cookie]: # type: ignore[override] + """RequestCookieJar's __iter__ comes from CookieJar not MutableMapping.""" + return super().__iter__() + + def __contains__(self, name: object) -> bool: + try: + return super().__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name: str) -> str | None: + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__( + self, name: str, value: str | Morsel[dict[str, str]] | None + ) -> None: + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name: str) -> None: + """Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie: Cookie, *args: Any, **kwargs: Any) -> None: + if ( + (value := cookie.value) is not None + and value.startswith('"') + and value.endswith('"') + ): + cookie.value = value.replace('\\"', "") + return super().set_cookie(cookie, *args, **kwargs) + + def update( # type: ignore[override] + self, other: CookieJar | SupportsKeysAndGetItem[str, str] + ) -> None: + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super().update(other) + + def _find( + self, name: str, domain: str | None = None, path: str | None = None + ) -> str | None: + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def _find_no_duplicates( + self, name: str, domain: str | None = None, path: str | None = None + ) -> str: + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: + # if there are multiple cookies that meet passed in criteria + raise CookieConflictError( + f"There are multiple cookies with name, {name!r}" + ) + # we will eventually return this as long as no cookie conflict + toReturn = cookie.value + + if toReturn is not None: + return toReturn + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def __getstate__(self) -> dict[str, Any]: + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop("_cookies_lock") + return state + + def __setstate__(self, state: dict[str, Any]) -> None: + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if "_cookies_lock" not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self) -> RequestsCookieJar: + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self) -> CookiePolicy: + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar: CookieJar | None) -> CookieJar | None: # type: ignore[reportUnusedFunction] # cross-module usage in models.py + if jar is None: + return None + + if copy_method := getattr(jar, "copy", None): + # We're dealing with an instance of RequestsCookieJar + return copy_method() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name: str, value: str, **kwargs: Any) -> Cookie: + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result: dict[str, Any] = { + "version": 0, + "name": name, + "value": value, + "port": None, + "domain": "", + "path": "/", + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + + badargs = set(kwargs) - set(result) + if badargs: + raise TypeError( + f"create_cookie() got unexpected keyword arguments: {list(badargs)}" + ) + + result.update(kwargs) + result["port_specified"] = bool(result["port"]) + result["domain_specified"] = bool(result["domain"]) + result["domain_initial_dot"] = result["domain"].startswith(".") + result["path_specified"] = bool(result["path"]) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel: Morsel[Any]) -> Cookie: + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires: int | None = None + if morsel["max-age"]: + try: + expires = int(time.time() + int(morsel["max-age"])) + except ValueError: + raise TypeError(f"max-age: {morsel['max-age']} must be integer") + elif morsel["expires"]: + time_template = "%a, %d-%b-%Y %H:%M:%S GMT" + expires = calendar.timegm(time.strptime(morsel["expires"], time_template)) + return create_cookie( + comment=morsel["comment"], + comment_url=bool(morsel["comment"]), + discard=False, + domain=morsel["domain"], + expires=expires, + name=morsel.key, + path=morsel["path"], + port=None, + rest={"HttpOnly": morsel["httponly"]}, + rfc2109=False, + secure=bool(morsel["secure"]), + value=morsel.value, + version=morsel["version"] or 0, + ) + + +_CookieJarT = TypeVar("_CookieJarT", bound=CookieJar) + + +@overload +def cookiejar_from_dict( + cookie_dict: dict[str, str] | None, + cookiejar: None = None, + overwrite: bool = True, +) -> RequestsCookieJar: ... + + +@overload +def cookiejar_from_dict( + cookie_dict: dict[str, str] | None, + cookiejar: _CookieJarT, + overwrite: bool = True, +) -> _CookieJarT: ... + + +def cookiejar_from_dict( + cookie_dict: dict[str, str] | None, + cookiejar: CookieJar | None = None, + overwrite: bool = True, +) -> CookieJar: + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies( + cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None +) -> CookieJar: + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): # type: ignore[reportUnnecessaryIsInstance] # runtime guard + raise ValueError("You can only merge into CookieJar") + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + if update_method := getattr(cookiejar, "update", None): + update_method(cookies) + else: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/venv/Lib/site-packages/requests/exceptions.py b/venv/Lib/site-packages/requests/exceptions.py new file mode 100644 index 0000000..cb5e951 --- /dev/null +++ b/venv/Lib/site-packages/requests/exceptions.py @@ -0,0 +1,162 @@ +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + +if TYPE_CHECKING: + from .models import PreparedRequest, Request, Response + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + response: Response | None + request: Request | PreparedRequest | None + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Initialize RequestException with `request` and `response` objects.""" + response: Response | None = kwargs.pop("response", None) + self.response = response + self.request = kwargs.pop("request", None) + if response is not None and not self.request and hasattr(response, "request"): + self.request = response.request + super().__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """ + Construct the JSONDecodeError instance first with all + args. Then use it's args to construct the IOError so that + the json specific args aren't used as IOError specific args + and the error message from JSONDecodeError is preserved. + """ + CompatJSONDecodeError.__init__(self, *args) + InvalidJSONError.__init__(self, *self.args, **kwargs) + + def __reduce__(self) -> tuple[Any, ...] | str: + """ + The __reduce__ method called when pickling the object must + be the one from the JSONDecodeError (be it json/simplejson) + as it expects all the arguments for instantiation, not just + one like the IOError, and the MRO would by default call the + __reduce__ method from the IOError due to the inheritance order. + """ + return CompatJSONDecodeError.__reduce__(self) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/venv/Lib/site-packages/requests/help.py b/venv/Lib/site-packages/requests/help.py new file mode 100644 index 0000000..9269cc7 --- /dev/null +++ b/venv/Lib/site-packages/requests/help.py @@ -0,0 +1,134 @@ +"""Module containing bug report helper(s).""" + +# pyright: reportUnknownMemberType=false + +import json +import platform +import ssl +import sys +from typing import Any + +import idna +import urllib3 + +from . import __version__ as requests_version + +try: + import charset_normalizer +except ImportError: + charset_normalizer = None + +try: + import chardet # type: ignore[import-not-found] +except ImportError: + chardet = None + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import cryptography # type: ignore[import-not-found] + import OpenSSL # type: ignore[import-not-found] + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 3.10.3 it will return + {'name': 'CPython', 'version': '3.10.3'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == "CPython": + implementation_version = platform.python_version() + elif implementation == "PyPy": + pypy = sys.pypy_version_info # type: ignore[attr-defined] + implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}" + if sys.pypy_version_info.releaselevel != "final": # type: ignore[attr-defined] + implementation_version = "".join( + [implementation_version, sys.pypy_version_info.releaselevel] # type: ignore[attr-defined] + ) + elif implementation == "Jython": + implementation_version = platform.python_version() # Complete Guess + elif implementation == "IronPython": + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = "Unknown" + + return {"name": implementation, "version": implementation_version} + + +def info() -> dict[str, Any]: + """Generate information for a bug report.""" + try: + platform_info = { + "system": platform.system(), + "release": platform.release(), + } + except OSError: + platform_info = { + "system": "Unknown", + "release": "Unknown", + } + + implementation_info = _implementation() + urllib3_info = {"version": urllib3.__version__} # type: ignore[reportPrivateImportUsage] + charset_normalizer_info = {"version": None} + chardet_info: dict[str, str | None] = {"version": None} + if charset_normalizer: + charset_normalizer_info = {"version": charset_normalizer.__version__} + if chardet: + chardet_info = {"version": chardet.__version__} + + pyopenssl_info: dict[str, str | None] = { + "version": None, + "openssl_version": "", + } + if OpenSSL: + pyopenssl_info = { + "version": OpenSSL.__version__, + "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}", + } + cryptography_info = { + "version": getattr(cryptography, "__version__", ""), + } + idna_info = { + "version": getattr(idna, "__version__", ""), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""} # type: ignore[reportUnnecessaryComparison] + + return { + "platform": platform_info, + "implementation": implementation_info, + "system_ssl": system_ssl_info, + "using_pyopenssl": pyopenssl is not None, + "using_charset_normalizer": chardet is None, + "pyOpenSSL": pyopenssl_info, + "urllib3": urllib3_info, + "chardet": chardet_info, + "charset_normalizer": charset_normalizer_info, + "cryptography": cryptography_info, + "idna": idna_info, + "requests": { + "version": requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/venv/Lib/site-packages/requests/hooks.py b/venv/Lib/site-packages/requests/hooks.py new file mode 100644 index 0000000..11ff9e9 --- /dev/null +++ b/venv/Lib/site-packages/requests/hooks.py @@ -0,0 +1,48 @@ +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" + +from __future__ import annotations + +from collections.abc import Callable, Iterable +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from . import _types as _t + from .models import Response + +HOOKS: list[str] = ["response"] + + +def default_hooks() -> dict[str, list[_t.HookType]]: + return {event: [] for event in HOOKS} + + +# TODO: response is the only one + + +def dispatch_hook( + key: str, + hooks: _t.HooksInputType | None, + hook_data: Response, + **kwargs: Any, +) -> Response: + """Dispatches a hook dictionary on a given piece of data.""" + hooks_dict = hooks or {} + hook_list: Iterable[_t.HookType] | _t.HookType | None = hooks_dict.get(key) + if hook_list: + if isinstance(hook_list, Callable): + hook_list = [hook_list] + for hook in hook_list: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/venv/Lib/site-packages/requests/models.py b/venv/Lib/site-packages/requests/models.py new file mode 100644 index 0000000..4142f2a --- /dev/null +++ b/venv/Lib/site-packages/requests/models.py @@ -0,0 +1,1180 @@ +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +from __future__ import annotations + +import datetime + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna # noqa: F401 # type: ignore[reportUnusedImport] +from collections.abc import Callable, Generator, Iterable, Iterator, Mapping +from io import UnsupportedOperation +from typing import ( + TYPE_CHECKING, + Any, + Final, + Literal, + cast, + overload, +) + +from urllib3.exceptions import ( + DecodeError, + LocationParseError, + ProtocolError, + ReadTimeoutError, + SSLError, +) +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url + +from ._internal_utils import to_native_string, unicode_is_ascii +from ._types import SupportsRead as _SupportsRead +from .auth import HTTPBasicAuth +from .compat import ( + JSONDecodeError, + basestring, + builtin_str, + chardet, + cookielib, + urlencode, + urlsplit, + urlunparse, +) +from .compat import json as complexjson +from .cookies import ( + _copy_cookie_jar, # type: ignore[reportPrivateUsage] + cookiejar_from_dict, + get_cookie_header, +) +from .exceptions import ( + ChunkedEncodingError, + ConnectionError, + ContentDecodingError, + HTTPError, + InvalidJSONError, + InvalidURL, + MissingSchema, + StreamConsumedError, +) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from .exceptions import SSLError as RequestsSSLError +from .hooks import default_hooks +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( + check_header_validity, + get_auth_from_url, + guess_filename, + guess_json_utf, + iter_slices, + parse_header_links, + requote_uri, + stream_decode_response_unicode, + super_len, + to_key_val_list, +) + +if TYPE_CHECKING: + from http.cookiejar import CookieJar + + from typing_extensions import Self + + from . import _types as _t + from .adapters import HTTPAdapter + from .cookies import RequestsCookieJar + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI: Final[tuple[int, ...]] = ( # type: ignore[assignment] + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT: int = 30 +CONTENT_CHUNK_SIZE: int = 10 * 1024 +ITER_CHUNK_SIZE: int = 512 + + +class RequestEncodingMixin: + url: str | None + + @property + def path_url(self) -> str: + """Build the path URL to use.""" + + url: list[str] = [] + + p = urlsplit(cast(str, self.url)) + + path = p.path + if not path: + path = "/" + + url.append(path) + + query = p.query + if query: + url.append("?") + url.append(query) + + return "".join(url) + + @overload + @staticmethod + def _encode_params(data: str) -> str: ... + + @overload + @staticmethod + def _encode_params(data: bytes) -> bytes: ... + + @overload + @staticmethod + def _encode_params( + data: _t.SupportsRead[str | bytes], + ) -> _t.SupportsRead[str | bytes]: ... + + @overload + @staticmethod + def _encode_params(data: _t.KVDataType) -> str: ... + + @staticmethod + def _encode_params( + data: _t.EncodableDataType, + ) -> str | bytes | _t.SupportsRead[str | bytes]: + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif isinstance(data, _SupportsRead): + return data + elif hasattr(data, "__iter__"): + result: list[tuple[bytes, bytes]] = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, "__iter__"): + vs = [vs] + for v in vs: + if v is not None: + result.append( + ( + k.encode("utf-8") if isinstance(k, str) else k, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + return urlencode(result, doseq=True) + else: + return data # type: ignore[return-value] # unreachable for valid _t.DataType + + @staticmethod + def _encode_files( + files: _t.FilesType, data: _t.RawDataType | None + ) -> tuple[bytes, str]: + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if not files: + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields: list[RequestField | tuple[str, bytes]] = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, "__iter__"): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + ( + field.decode("utf-8") + if isinstance(field, bytes) + else field, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + + for k, v in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif isinstance(fp, _SupportsRead): # type: ignore[reportUnnecessaryIsInstance] # defensive check for untyped callers + fdata = fp.read() + elif fp is None: # type: ignore[reportUnnecessaryComparison] # defensive check for untyped callers + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin: + hooks: dict[str, list[_t.HookType]] + + def register_hook( + self, event: str, hook: Iterable[_t.HookType] | _t.HookType + ) -> None: + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError(f'Unsupported event specified, with event name "{event}"') + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, "__iter__"): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) # type: ignore[reportUnnecessaryIsInstance] # defensive runtime filter + + def deregister_hook(self, event: str, hook: _t.HookType) -> bool: + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + method: str | None + url: _t.UriType | None + headers: CaseInsensitiveDict[str] | Mapping[str, str | bytes] | None + files: _t.FilesType + data: _t.DataType + json: _t.JsonType + params: _t.ParamsType + auth: _t.AuthType + cookies: RequestsCookieJar | CookieJar | dict[str, str] | None + + def __init__( + self, + method: str | None = None, + url: _t.UriType | None = None, + headers: Mapping[str, str | bytes] | None = None, + files: _t.FilesType = None, + data: _t.DataType = None, + params: _t.ParamsType = None, + auth: _t.AuthType = None, + cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None, + hooks: _t.HooksInputType | None = None, + json: _t.JsonType = None, + ) -> None: + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for k, v in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self) -> str: + return f"" + + def prepare(self) -> PreparedRequest: + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + method: str | None + url: str | None + headers: CaseInsensitiveDict[str | bytes] + _cookies: RequestsCookieJar | CookieJar | None + body: _t.BodyType + hooks: dict[str, list[_t.HookType]] + _body_position: int | object | None + + def __init__(self) -> None: + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None # type: ignore[assignment] + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare( + self, + method: str | None = None, + url: _t.UriType | None = None, + headers: Mapping[str, str | bytes] | None = None, + files: _t.FilesType = None, + data: _t.DataType = None, + params: _t.ParamsType = None, + auth: _t.AuthType = None, + cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None, + hooks: _t.HooksInputType | None = None, + json: _t.JsonType = None, + ) -> None: + """Prepares the entire request with the given parameters.""" + + url = cast("_t.UriType", url) + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self) -> str: + return f"" + + def copy(self) -> PreparedRequest: + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None # type: ignore[assignment] + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method: str | None) -> None: + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host: str) -> str: + import idna + + try: + host = idna.encode(host, uts46=True).decode("utf-8") + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url( + self, + url: _t.UriType, + params: _t.ParamsType, + ) -> None: + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode("utf8") + else: + url = str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ":" in url and not url.lower().startswith("http"): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + raise MissingSchema( + f"Invalid URL {url!r}: No scheme supplied. " + f"Perhaps you meant https://{url}?" + ) + + if not host: + raise InvalidURL(f"Invalid URL {url!r}: No host supplied") + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL("URL has an invalid label.") + elif host.startswith(("*", ".")): + raise InvalidURL("URL has an invalid label.") + + # Carefully reconstruct the network location + netloc = auth or "" + if netloc: + netloc += "@" + netloc += host + if port: + netloc += f":{port}" + + # Bare domains aren't valid URLs. + if not path: + path = "/" + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + if params is not None: + enc_params = self._encode_params(params) + else: + enc_params = "" + + if enc_params: + if query: + query = f"{query}&{enc_params}" + else: + query = enc_params + + url = requote_uri(urlunparse((scheme, netloc, path, "", query, fragment))) + self.url = url + + def prepare_headers(self, headers: Mapping[str, str | bytes] | None) -> None: + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body( + self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None + ) -> None: + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = "application/json" + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode("utf-8") + + if isinstance(data, Iterable) and not isinstance( + data, (str, bytes, list, tuple, Mapping) + ): + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, "tell", None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() # type: ignore[union-attr] # guarded by getattr check + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError( + "Streamed bodies and files are mutually exclusive." + ) + + if length: + self.headers["Content-Length"] = builtin_str(length) + else: + self.headers["Transfer-Encoding"] = "chunked" + else: + # After is_stream filtering, remaining data is raw (not streamed) + raw_data = cast("_t.RawDataType | None", data) + + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, raw_data) + else: + if raw_data: + body = self._encode_params(raw_data) + if isinstance(data, basestring) or isinstance(data, _SupportsRead): + content_type = None + else: + content_type = "application/x-www-form-urlencoded" + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ("content-type" not in self.headers): + self.headers["Content-Type"] = content_type + + self.body = body # type: ignore[assignment] # body transforms from DataType to BodyType + + def prepare_content_length(self, body: _t.BodyType) -> None: + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers["Content-Length"] = builtin_str(length) + elif ( + self.method not in ("GET", "HEAD") + and self.headers.get("Content-Length") is None + ): + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers["Content-Length"] = "0" + + def prepare_auth( + self, + auth: _t.AuthType, + url: _t.UriType = "", + ) -> None: + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(cast(str, self.url)) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: # type: ignore[arg-type] # pyright widens tuple from Callable in AuthType + # special-case basic HTTP auth + auth_handler = HTTPBasicAuth(*auth) # type: ignore[arg-type] # pyright widens tuple from Callable in AuthType + else: + # TODO: can be fixed by flipping the conditionals + auth_handler = cast("Callable[..., PreparedRequest]", auth) + + # Allow auth to make its changes. + r = auth_handler(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies( + self, cookies: RequestsCookieJar | CookieJar | dict[str, str] | None + ) -> None: + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookies_jar = cast("CookieJar", self._cookies) + cookie_header = get_cookie_header(cookies_jar, self) + if cookie_header is not None: + self.headers["Cookie"] = cookie_header + + def prepare_hooks(self, hooks: _t.HooksInputType | None) -> None: + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or {} + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response: + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + _content: bytes | Literal[False] | None + _content_consumed: bool + _next: PreparedRequest | None + status_code: int + headers: CaseInsensitiveDict[str] + raw: Any + url: str + encoding: str | None + history: list[Response] + reason: str | None + cookies: RequestsCookieJar + elapsed: datetime.timedelta + request: PreparedRequest + connection: HTTPAdapter + + __attrs__: list[str] = [ + "_content", + "status_code", + "headers", + "url", + "history", + "encoding", + "reason", + "cookies", + "elapsed", + "request", + ] + + def __init__(self) -> None: + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None # type: ignore[assignment] + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None # type: ignore[assignment] + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None # type: ignore[assignment] + + def __enter__(self) -> Self: + return self + + def __exit__(self, *args: Any) -> None: + self.close() + + def __getstate__(self) -> dict[str, Any]: + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state: dict[str, Any]) -> None: + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, "_content_consumed", True) + setattr(self, "raw", None) + + def __repr__(self) -> str: + return f"" + + def __bool__(self) -> bool: + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self) -> bool: + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self) -> Iterator[bytes]: + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self) -> bool: + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self) -> bool: + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return "location" in self.headers and self.status_code in REDIRECT_STATI + + @property + def is_permanent_redirect(self) -> bool: + """True if this Response one of the permanent versions of redirect.""" + return "location" in self.headers and self.status_code in ( + codes.moved_permanently, + codes.permanent_redirect, + ) + + @property + def next(self) -> PreparedRequest | None: + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self) -> str | None: + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + if chardet is not None: + return chardet.detect(self.content)["encoding"] + else: + # If no character detection library is available, we'll fall back + # to a standard Python utf-8 str. + return "utf-8" + + @overload + def iter_content( + self, chunk_size: int | None = 1, decode_unicode: Literal[False] = False + ) -> Iterator[bytes]: ... + @overload + def iter_content( + self, chunk_size: int | None = 1, *, decode_unicode: Literal[True] + ) -> Iterator[str | bytes]: ... + def iter_content( + self, chunk_size: int | None = 1, decode_unicode: bool = False + ) -> Iterator[str | bytes]: + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using encoding + information from the response. If no encoding information is available, + bytes will be returned. This can be bypassed by manually setting + `encoding` on the response. + """ + + def generate() -> Generator[bytes, None, None]: + # Special case for urllib3. + if hasattr(self.raw, "stream"): + try: + yield from self.raw.stream(chunk_size, decode_content=True) + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + except SSLError as e: + raise RequestsSSLError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for untyped callers + raise TypeError( + f"chunk_size must be an int, it is instead a {type(chunk_size)}." + ) + + if self._content_consumed: + # simulate reading small chunks of the content + content = cast(bytes, self._content) + chunks = iter_slices(content, chunk_size) + else: + chunks = generate() + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + @overload + def iter_lines( + self, + chunk_size: int = ITER_CHUNK_SIZE, + decode_unicode: Literal[False] = False, + delimiter: bytes | None = None, + ) -> Iterator[bytes]: ... + @overload + def iter_lines( + self, + chunk_size: int = ITER_CHUNK_SIZE, + *, + decode_unicode: Literal[True], + delimiter: str | bytes | None = None, + ) -> Iterator[str | bytes]: ... + def iter_lines( + self, + chunk_size: int = ITER_CHUNK_SIZE, + decode_unicode: bool = False, + delimiter: str | bytes | None = None, + ) -> Iterator[str | bytes]: + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + The decode_unicode param works the same as in `iter_content`, with the + same caveats. + + .. note:: This method is not reentrant safe. + """ + + pending: str | bytes | None = None + + for chunk in self.iter_content( + chunk_size=chunk_size, decode_unicode=decode_unicode + ): + if pending is not None: + # TODO: remove cast after iter_lines rewrite + chunk = cast("str | bytes", pending + chunk) # type: ignore[operator] + + if delimiter: + lines = chunk.split(delimiter) # type: ignore[arg-type] + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + yield from lines + + if pending is not None: + yield pending + + @property + def content(self) -> bytes: + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError("The content for this response was already consumed") + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b"" + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content # type: ignore[return-value] + + @property + def text(self) -> str: + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return "" + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding or "utf-8", errors="replace") + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors="replace") + + return content + + def json(self, **kwargs: Any) -> Any: + r"""Decodes the JSON response body (if any) as a Python object. + + This may return a dictionary, list, etc. depending on what is in the response. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads(self.content.decode(encoding), **kwargs) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + except JSONDecodeError as e: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self) -> dict[str, dict[str, str]]: + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get("link") + + resolved_links: dict[str, dict[str, str]] = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get("rel") or link.get("url") + if key is not None: + resolved_links[key] = link + + return resolved_links + + def raise_for_status(self) -> None: + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = "" + if isinstance(self.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. (See PR #3538) + try: + reason = self.reason.decode("utf-8") + except UnicodeDecodeError: + reason = self.reason.decode("iso-8859-1") + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = ( + f"{self.status_code} Client Error: {reason} for url: {self.url}" + ) + + elif 500 <= self.status_code < 600: + http_error_msg = ( + f"{self.status_code} Server Error: {reason} for url: {self.url}" + ) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self) -> None: + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, "release_conn", None) + if release_conn is not None: + release_conn() diff --git a/venv/Lib/site-packages/requests/packages.py b/venv/Lib/site-packages/requests/packages.py new file mode 100644 index 0000000..5ab3d8e --- /dev/null +++ b/venv/Lib/site-packages/requests/packages.py @@ -0,0 +1,23 @@ +import sys + +from .compat import chardet + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ("urllib3", "idna"): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(f"{package}."): + sys.modules[f"requests.packages.{mod}"] = sys.modules[mod] + +if chardet is not None: + target = chardet.__name__ + for mod in list(sys.modules): + if mod == target or mod.startswith(f"{target}."): + imported_mod = sys.modules[mod] + sys.modules[f"requests.packages.{mod}"] = imported_mod + mod = mod.replace(target, "chardet") + sys.modules[f"requests.packages.{mod}"] = imported_mod diff --git a/venv/Lib/site-packages/requests/py.typed b/venv/Lib/site-packages/requests/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/requests/sessions.py b/venv/Lib/site-packages/requests/sessions.py new file mode 100644 index 0000000..8f13887 --- /dev/null +++ b/venv/Lib/site-packages/requests/sessions.py @@ -0,0 +1,920 @@ +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" + +from __future__ import annotations + +import os +import sys +import time +from collections import OrderedDict +from collections.abc import Generator, Mapping, MutableMapping +from datetime import timedelta +from typing import TYPE_CHECKING, Any, cast + +from ._internal_utils import to_native_string +from ._types import is_prepared as _is_prepared +from .adapters import HTTPAdapter +from .auth import _basic_auth_str # type: ignore[reportPrivateUsage] +from .compat import cookielib, urljoin, urlparse +from .cookies import ( + RequestsCookieJar, + cookiejar_from_dict, + extract_cookies_to_jar, + merge_cookies, +) +from .exceptions import ( + ChunkedEncodingError, + ContentDecodingError, + InvalidSchema, + TooManyRedirects, +) +from .hooks import default_hooks, dispatch_hook + +# formerly defined here, reexposed here for backward compatibility +from .models import ( # noqa: F401 + DEFAULT_REDIRECT_LIMIT, + REDIRECT_STATI, # type: ignore[reportUnusedImport] + PreparedRequest, + Request, + Response, +) +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( # noqa: F401 + DEFAULT_PORTS, + default_headers, + get_auth_from_url, + get_environ_proxies, + get_netrc_auth, + requote_uri, + resolve_proxies, + rewind_body, + should_bypass_proxies, # type: ignore[reportUnusedImport] # re-export for external consumers + to_key_val_list, +) + +if TYPE_CHECKING: + from http.cookiejar import CookieJar + + from typing_extensions import Self, Unpack + + from . import _types as _t + from .adapters import BaseAdapter + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == "win32": + preferred_clock = time.perf_counter +else: + preferred_clock = time.time + + +def merge_setting( + request_setting: Any, session_setting: Any, dict_class: type = OrderedDict +) -> Any: + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) # type: ignore[arg-type] # isinstance narrows Any to Mapping[Unknown] + merged_setting.update(to_key_val_list(request_setting)) # type: ignore[arg-type] + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks( + request_hooks: _t.HooksType, + session_hooks: _t.HooksType, + dict_class: type = OrderedDict, +) -> _t.HooksType: + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get("response") == []: + return request_hooks + + if request_hooks is None or request_hooks.get("response") == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin: + max_redirects: int + trust_env: bool + cookies: RequestsCookieJar + + def send(self, request: PreparedRequest, **kwargs: Any) -> Response: ... + + def get_redirect_target(self, resp: Response) -> str | None: + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers["location"] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + location = location.encode("latin1") + return to_native_string(location, "utf8") + return None + + def should_strip_auth(self, old_url: str, new_url: str) -> bool: + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if ( + old_parsed.scheme == "http" + and old_parsed.port in (80, None) + and new_parsed.scheme == "https" + and new_parsed.port in (443, None) + ): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if ( + not changed_scheme + and old_parsed.port in default_port + and new_parsed.port in default_port + ): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects( + self, + resp: Response, + req: PreparedRequest, + stream: bool = False, + timeout: _t.TimeoutType = None, + verify: _t.VerifyType = True, + cert: _t.CertType = None, + proxies: dict[str, str] | None = None, + yield_requests: bool = False, + **adapter_kwargs: Any, + ) -> Generator[Response, None, None]: + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist: list[Response] = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + resp.history = hist[:] + hist.append(resp) + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects( + f"Exceeded {self.max_redirects} redirects.", response=resp + ) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith("//"): + parsed_rurl = urlparse(resp.url) + url = ":".join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == "" and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in ( + codes.temporary_redirect, + codes.permanent_redirect, + ): + # https://github.com/psf/requests/issues/3490 + purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding") + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop("Cookie", None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + cookie_jar = cast("CookieJar", prepared_request._cookies) # type: ignore[reportPrivateUsage] + extract_cookies_to_jar(cookie_jar, req, resp.raw) + merge_cookies(cookie_jar, self.cookies) + prepared_request.prepare_cookies(cookie_jar) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = prepared_request._body_position is not None and ( # type: ignore[reportPrivateUsage] + "Content-Length" in headers or "Transfer-Encoding" in headers + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req # type: ignore[misc] # Internal use only, returns PreparedRequest + else: + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs, + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth( + self, prepared_request: PreparedRequest, response: Response + ) -> None: + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + original_request = response.request + assert _is_prepared(original_request) + assert _is_prepared(prepared_request) + + headers = prepared_request.headers + original_url = original_request.url + url = prepared_request.url + + if "Authorization" in headers and self.should_strip_auth(original_url, url): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers["Authorization"] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies( + self, + prepared_request: PreparedRequest, + proxies: dict[str, str] | None, + ) -> dict[str, str]: + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + assert _is_prepared(prepared_request) + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if "Proxy-Authorization" in headers: + del headers["Proxy-Authorization"] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + # urllib3 handles proxy authorization for us in the standard adapter. + # Avoid appending this to TLS tunneled requests where it may be leaked. + if not scheme.startswith("https") and username and password: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method( + self, prepared_request: PreparedRequest, response: Response + ) -> None: + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != "HEAD": + method = "GET" + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == "POST": + method = "GET" + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + headers: CaseInsensitiveDict[str] + auth: _t.AuthType + proxies: dict[str, str] + hooks: dict[str, list[_t.HookType]] + params: MutableMapping[str, Any] + stream: bool + verify: _t.VerifyType + cert: _t.CertType + max_redirects: int + trust_env: bool + cookies: RequestsCookieJar + adapters: MutableMapping[str, BaseAdapter] + + __attrs__: list[str] = [ + "headers", + "cookies", + "auth", + "proxies", + "hooks", + "params", + "verify", + "cert", + "adapters", + "stream", + "trust_env", + "max_redirects", + ] + + def __init__(self) -> None: + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + #: If verify is set to a string, it must be the path to a CA bundle file + #: that will be used to verify the TLS certificate. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount("https://", HTTPAdapter()) + self.mount("http://", HTTPAdapter()) + + def __enter__(self) -> Self: + return self + + def __exit__(self, *args: Any) -> None: + self.close() + + def prepare_request(self, request: Request) -> PreparedRequest: + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + url = cast("_t.UriType", request.url) + method = cast(str, request.method) + + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies + ) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(url) + + p = PreparedRequest() + p.prepare( + method=method.upper(), + url=url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting( + request.headers, self.headers, dict_class=CaseInsensitiveDict + ), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request( + self, + method: str, + url: _t.UriType, + params: _t.ParamsType = None, + data: _t.DataType = None, + headers: Mapping[str, str | bytes] | None = None, + cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None, + files: _t.FilesType = None, + auth: _t.AuthType = None, + timeout: _t.TimeoutType = None, + allow_redirects: bool = True, + proxies: dict[str, str] | None = None, + hooks: _t.HooksInputType | None = None, + stream: bool | None = None, + verify: _t.VerifyType | None = None, + cert: _t.CertType = None, + json: _t.JsonType = None, + ) -> Response: + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param hooks: (optional) Dictionary mapping hook name to one event or + list of events, event must be callable. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + if isinstance(url, bytes): + url = url.decode("utf-8") + + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + assert _is_prepared(prep) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + "timeout": timeout, + "allow_redirects": allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get( + self, + url: _t.UriType, + params: _t.ParamsType = None, + **kwargs: Unpack[_t.GetKwargs], + ) -> Response: + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("GET", url, params=params, **kwargs) + + def options(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response: + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("OPTIONS", url, **kwargs) + + def head(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response: + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return self.request("HEAD", url, **kwargs) + + def post( + self, + url: _t.UriType, + data: _t.DataType = None, + json: _t.JsonType = None, + **kwargs: Unpack[_t.PostKwargs], + ) -> Response: + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("POST", url, data=data, json=json, **kwargs) + + def put( + self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs] + ) -> Response: + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PUT", url, data=data, **kwargs) + + def patch( + self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs] + ) -> Response: + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PATCH", url, data=data, **kwargs) + + def delete(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response: + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("DELETE", url, **kwargs) + + def send(self, request: PreparedRequest, **kwargs: Any) -> Response: + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault("stream", self.stream) + kwargs.setdefault("verify", self.verify) + kwargs.setdefault("cert", self.cert) + if "proxies" not in kwargs: + kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError("You can only send PreparedRequests.") + + assert _is_prepared(request) + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop("allow_redirects", True) + stream = kwargs.get("stream") + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook("response", hooks, r, **kwargs) + + # Persist cookies + if r.history: + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next( # type: ignore[assignment] # yield_requests=True returns PreparedRequest + self.resolve_redirects(r, request, yield_requests=True, **kwargs) + ) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings( + self, + url: str, + proxies: dict[str, str] | None, + stream: bool | None, + verify: _t.VerifyType | None, + cert: _t.CertType, + ) -> dict[str, Any]: + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get("no_proxy") if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + if proxies is not None: + for k, v in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration + # and be compatible with cURL. + if verify is True or verify is None: + verify = ( + os.environ.get("REQUESTS_CA_BUNDLE") + or os.environ.get("CURL_CA_BUNDLE") + or verify + ) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert} + + def get_adapter(self, url: str) -> BaseAdapter: + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for prefix, adapter in self.adapters.items(): + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema(f"No connection adapters were found for {url!r}") + + def close(self) -> None: + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix: str, adapter: BaseAdapter) -> None: + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self) -> dict[str, Any]: + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state: dict[str, Any]) -> None: + for attr, value in state.items(): + setattr(self, attr, value) + + +def session() -> Session: + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/venv/Lib/site-packages/requests/status_codes.py b/venv/Lib/site-packages/requests/status_codes.py new file mode 100644 index 0000000..6c59d6b --- /dev/null +++ b/venv/Lib/site-packages/requests/status_codes.py @@ -0,0 +1,128 @@ +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + # Informational. + 100: ("continue",), + 101: ("switching_protocols",), + 102: ("processing", "early-hints"), + 103: ("checkpoint",), + 122: ("uri_too_long", "request_uri_too_long"), + 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"), + 201: ("created",), + 202: ("accepted",), + 203: ("non_authoritative_info", "non_authoritative_information"), + 204: ("no_content",), + 205: ("reset_content", "reset"), + 206: ("partial_content", "partial"), + 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"), + 208: ("already_reported",), + 226: ("im_used",), + # Redirection. + 300: ("multiple_choices",), + 301: ("moved_permanently", "moved", "\\o-"), + 302: ("found",), + 303: ("see_other", "other"), + 304: ("not_modified",), + 305: ("use_proxy",), + 306: ("switch_proxy",), + 307: ("temporary_redirect", "temporary_moved", "temporary"), + 308: ( + "permanent_redirect", + "resume_incomplete", + "resume", + ), # "resume" and "resume_incomplete" to be removed in 3.0 + # Client Error. + 400: ("bad_request", "bad"), + 401: ("unauthorized",), + 402: ("payment_required", "payment"), + 403: ("forbidden",), + 404: ("not_found", "-o-"), + 405: ("method_not_allowed", "not_allowed"), + 406: ("not_acceptable",), + 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"), + 408: ("request_timeout", "timeout"), + 409: ("conflict",), + 410: ("gone",), + 411: ("length_required",), + 412: ("precondition_failed", "precondition"), + 413: ("request_entity_too_large", "content_too_large"), + 414: ("request_uri_too_large", "uri_too_long"), + 415: ("unsupported_media_type", "unsupported_media", "media_type"), + 416: ( + "requested_range_not_satisfiable", + "requested_range", + "range_not_satisfiable", + ), + 417: ("expectation_failed",), + 418: ("im_a_teapot", "teapot", "i_am_a_teapot"), + 421: ("misdirected_request",), + 422: ("unprocessable_entity", "unprocessable", "unprocessable_content"), + 423: ("locked",), + 424: ("failed_dependency", "dependency"), + 425: ("unordered_collection", "unordered", "too_early"), + 426: ("upgrade_required", "upgrade"), + 428: ("precondition_required", "precondition"), + 429: ("too_many_requests", "too_many"), + 431: ("header_fields_too_large", "fields_too_large"), + 444: ("no_response", "none"), + 449: ("retry_with", "retry"), + 450: ("blocked_by_windows_parental_controls", "parental_controls"), + 451: ("unavailable_for_legal_reasons", "legal_reasons"), + 499: ("client_closed_request",), + # Server Error. + 500: ("internal_server_error", "server_error", "/o\\", "✗"), + 501: ("not_implemented",), + 502: ("bad_gateway",), + 503: ("service_unavailable", "unavailable"), + 504: ("gateway_timeout",), + 505: ("http_version_not_supported", "http_version"), + 506: ("variant_also_negotiates",), + 507: ("insufficient_storage",), + 509: ("bandwidth_limit_exceeded", "bandwidth"), + 510: ("not_extended",), + 511: ("network_authentication_required", "network_auth", "network_authentication"), +} + +codes: LookupDict[int] = LookupDict(name="status_codes") + + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(("\\", "/")): + setattr(codes, title.upper(), code) + + def doc(code: int) -> str: + names = ", ".join(f"``{n}``" for n in _codes[code]) + return "* %d: %s" % (code, names) + + global __doc__ + __doc__ = ( + __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes)) + if __doc__ is not None + else None + ) + + +_init() diff --git a/venv/Lib/site-packages/requests/structures.py b/venv/Lib/site-packages/requests/structures.py new file mode 100644 index 0000000..7675eaf --- /dev/null +++ b/venv/Lib/site-packages/requests/structures.py @@ -0,0 +1,130 @@ +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from __future__ import annotations + +from collections import OrderedDict +from collections.abc import Iterable, Iterator, Mapping +from typing import Any, Generic, TypeVar, overload + +from .compat import MutableMapping + +_VT = TypeVar("_VT") +_D = TypeVar("_D") + + +class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + _store: OrderedDict[str, tuple[str, _VT]] + + def __init__( + self, + data: Mapping[str, _VT] | Iterable[tuple[str, _VT]] | None = None, + **kwargs: _VT, + ) -> None: + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key: str, value: _VT) -> None: + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key: str) -> _VT: + return self._store[key.lower()][1] + + def __delitem__(self, key: str) -> None: + del self._store[key.lower()] + + def __iter__(self) -> Iterator[str]: + return (casedkey for casedkey, _ in self._store.values()) + + def __len__(self) -> int: + return len(self._store) + + def lower_items(self) -> Iterator[tuple[str, _VT]]: + """Like iteritems(), but with all lowercase keys.""" + return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()) + + def __eq__(self, other: object) -> bool: + if isinstance(other, Mapping): + other_dict: CaseInsensitiveDict[Any] = CaseInsensitiveDict(other) # type: ignore[reportUnknownArgumentType] + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other_dict.lower_items()) + + # Copy is required + def copy(self) -> CaseInsensitiveDict[_VT]: + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self) -> str: + return str(dict(self.items())) + + +class LookupDict(dict[str, _VT]): + """Dictionary lookup object.""" + + name: Any + + def __init__(self, name: Any = None) -> None: + self.name = name + super().__init__() + + def __repr__(self) -> str: + return f"" + + def __getattr__(self, key: str) -> _VT | None: + # We need this for type checkers to infer typing + # on attribute access with status_codes.py + if key in self.__dict__: + return self.__dict__[key] + else: + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{key}'" + ) + + def __getitem__(self, key: str) -> _VT | None: # type: ignore[override] + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + @overload + def get(self, key: str, default: None = None) -> _VT | None: ... + + @overload + def get(self, key: str, default: _D | _VT) -> _D | _VT: ... + + def get(self, key: str, default: _D | None = None) -> _VT | _D | None: + return self.__dict__.get(key, default) diff --git a/venv/Lib/site-packages/requests/utils.py b/venv/Lib/site-packages/requests/utils.py new file mode 100644 index 0000000..120336d --- /dev/null +++ b/venv/Lib/site-packages/requests/utils.py @@ -0,0 +1,1155 @@ +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +from __future__ import annotations + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict +from collections.abc import Generator, Iterable +from typing import ( + TYPE_CHECKING, + Any, + Final, + TypeVar, + cast, + overload, +) + +from urllib3.util import make_headers, parse_url + +from . import certs +from .__version__ import __version__ + +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import ( # noqa: F401 + _HEADER_VALIDATORS_BYTE, # type: ignore[reportPrivateUsage] + _HEADER_VALIDATORS_STR, # type: ignore[reportPrivateUsage] + HEADER_VALIDATORS, # type: ignore[reportUnusedImport] + to_native_string, # type: ignore[reportUnusedImport] +) +from ._types import SupportsItems as _SupportsItems +from .compat import ( + Mapping, + bytes, + getproxies, + getproxies_environment, + integer_types, + is_urllib3_1, + proxy_bypass, + proxy_bypass_environment, # type: ignore[attr-defined] # https://github.com/python/cpython/issues/145331 + quote, + str, + unquote, + urlparse, + urlunparse, +) +from .compat import parse_http_list as _parse_list_header +from .cookies import cookiejar_from_dict +from .exceptions import ( + FileModeWarning, + InvalidHeader, + InvalidURL, + UnrewindableBodyError, +) +from .structures import CaseInsensitiveDict + +if TYPE_CHECKING: + from http.cookiejar import CookieJar + from io import BufferedWriter + + from . import _types as _t + from .models import PreparedRequest, Request, Response + +NETRC_FILES: Final = (".netrc", "_netrc") + + +# Certificate is extracted by certifi when needed. +DEFAULT_CA_BUNDLE_PATH: str = certs.where() + + +DEFAULT_PORTS: Final = {"http": 80, "https": 443} + +_KT = TypeVar("_KT") +_VT = TypeVar("_VT") + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING: Final = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == "win32": + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host: str) -> bool: + try: + import winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", + ) + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0] + except (OSError, ValueError): + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(";") + # filter out empty strings to avoid re.match return true in the following code. + proxyOverride = filter(None, proxyOverride) + # now check if we match one of the registry values. + for test in proxyOverride: + if test == "": + if "." not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host: str) -> bool: # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence( + d: _t.SupportsItems[Any, Any] | Iterable[tuple[Any, Any]], +) -> Iterable[tuple[Any, Any]]: + """Returns an internal sequence dictionary update.""" + + if isinstance(d, _SupportsItems): + return d.items() + + return d + + +def super_len(o: Any) -> int: + total_length = None + current_position = 0 + + if not is_urllib3_1 and isinstance(o, str): + # urllib3 2.x+ treats all strings as utf-8 instead + # of latin-1 (iso-8859-1) like http.client. + o = o.encode("utf-8") + + if hasattr(o, "__len__"): + total_length = len(o) + + elif hasattr(o, "len"): + total_length = o.len + + elif hasattr(o, "fileno"): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if "b" not in o.mode: + warnings.warn( + ( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode." + ), + FileModeWarning, + ) + + if hasattr(o, "tell"): + try: + current_position = o.tell() + except OSError: + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, "seek") and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except OSError: + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth( + url: _t.UriType, raise_errors: bool = False +) -> tuple[str, str] | None: + """Returns the Requests tuple auth for a given url from netrc.""" + + if isinstance(url, bytes): + url = url.decode("utf-8") + + netrc_file = os.environ.get("NETRC") + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = (f"~/{f}" for f in NETRC_FILES) + + try: + from netrc import NetrcParseError, netrc + + netrc_path = None + + for f in netrc_locations: + loc = os.path.expanduser(f) + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + host = ri.hostname + + if host is None: + return + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc and any(_netrc): + # Return with login / password + login_i = 0 if _netrc[0] else 1 + return (_netrc[login_i] or "", _netrc[2] or "") + except (NetrcParseError, OSError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj: Any) -> str | None: + """Tries to guess the filename of the given object.""" + name = getattr(obj, "name", None) + if name and isinstance(name, (str, bytes)) and name[0] != "<" and name[-1] != ">": + return os.path.basename(name) # type: ignore[return-value] # urllib3 accepts bytes but types str only + + +def extract_zipped_paths(path: str) -> str: + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = "/".join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + suffix = os.path.splitext(member.split("/")[-1])[-1] + fd, extracted_path = tempfile.mkstemp(suffix=suffix) + try: + os.write(fd, zip_file.read(member)) + finally: + os.close(fd) + + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename: str) -> Generator[BufferedWriter, None, None]: + """Write a file to the disk in an atomic fashion""" + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, "wb") as tmp_handler: + yield tmp_handler + os.replace(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list( + value: Mapping[Any, Any] | Iterable[tuple[Any, Any]] | None, +) -> dict[Any, Any] | None: + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + return OrderedDict(value) + + +@overload +def to_key_val_list(value: None) -> None: ... +@overload +def to_key_val_list( + value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]], +) -> list[tuple[_KT, _VT]]: ... +def to_key_val_list( + value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]] | None, +) -> list[tuple[_KT, _VT]] | None: + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + if isinstance(value, _SupportsItems): + return list(value.items()) + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value: str) -> list[str]: + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result: list[str] = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value: str) -> dict[str, str | None]: + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result: dict[str, str | None] = {} + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value: str, is_filename: bool = False) -> str: + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj: CookieJar) -> dict[str, str | None]: + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {cookie.name: cookie.value for cookie in cj} + return cookie_dict + + +def add_dict_to_cookiejar(cj: CookieJar, cookie_dict: dict[str, str]) -> CookieJar: + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content: str) -> list[str]: + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn( + ( + "In requests 3.0, get_encodings_from_content will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return ( + charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content) + ) + + +def _parse_content_type_header(header: str) -> tuple[str, dict[str, Any]]: + """Returns content type and parameters from given header. + + :param header: string + :return: tuple containing content type and dictionary of + parameters. + """ + + tokens = header.split(";") + content_type, params = tokens[0].strip(), tokens[1:] + params_dict: dict[str, str | bool] = {} + strip_chars = "\"' " + + for param in params: + param = param.strip() + if param and (idx := param.find("=")) != -1: + key = param[:idx].strip(strip_chars) + value = param[idx + 1 :].strip(strip_chars) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers: CaseInsensitiveDict[str]) -> str | None: + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get("content-type") + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if "charset" in params: + return params["charset"].strip("'\"") + + if "text" in content_type: + return "ISO-8859-1" + + if "application/json" in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return "utf-8" + + +def stream_decode_response_unicode( + iterator: Iterable[bytes], r: Response +) -> Generator[str | bytes, None, None]: + """Stream decodes an iterator.""" + + if r.encoding is None: + yield from iterator + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace") + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b"", final=True) + if rv: + yield rv + + +@overload +def iter_slices( + string: bytes, slice_length: int | None +) -> Generator[bytes, None, None]: ... +@overload +def iter_slices( + string: str, slice_length: int | None +) -> Generator[str, None, None]: ... +def iter_slices( + string: bytes | str, slice_length: int | None +) -> Generator[bytes | str, None, None]: + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos : pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r: Response) -> str | bytes | None: + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn( + ( + "In requests 3.0, get_unicode_from_response will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + if r.content is None: # type: ignore[reportUnnecessaryComparison] + return None + + tried_encodings: list[str] = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding or "utf-8", errors="replace") + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET: Final = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~" +) + + +def unquote_unreserved(uri: str) -> str: + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split("%") + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL(f"Invalid percent-escape sequence: '{h}'") + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = f"%{parts[i]}" + else: + parts[i] = f"%{parts[i]}" + return "".join(parts) + + +def requote_uri(uri: str) -> str: + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip: str, net: str) -> bool: + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0] + netaddr, bits = net.split("/") + netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask: int) -> str: + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack(">I", bits)) + + +def is_ipv4_address(string_ip: str) -> bool: + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except OSError: + return False + return True + + +def is_valid_cidr(string_network: str) -> bool: + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count("/") == 1: + try: + mask = int(string_network.split("/")[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split("/")[0]) + except OSError: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name: str, value: str | None) -> Generator[None, None, None]: + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + old_value: str | None = None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url: str, no_proxy: str | None) -> bool: + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + def get_proxy(key: str) -> str | None: + return os.environ.get(key) or os.environ.get(key.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy("no_proxy") + parsed = urlparse(url) + hostname = parsed.hostname + + if hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy_hosts = (host for host in no_proxy.replace(" ", "").split(",") if host) + + if is_ipv4_address(hostname): + for proxy_ip in no_proxy_hosts: + if is_valid_cidr(proxy_ip): + if address_in_network(hostname, proxy_ip): + return True + elif hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = hostname + if parsed.port: + host_with_port += f":{parsed.port}" + + for host in no_proxy_hosts: + host = host.lstrip(".") + if hostname == host or host_with_port == host: + return True + host = "." + host + if hostname.endswith(host) or host_with_port.endswith(host): + return True + + with set_environ("no_proxy", no_proxy_arg): + try: + bypass = proxy_bypass(hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url: str, no_proxy: str | None = None) -> dict[str, str]: + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url: str, proxies: dict[str, str] | None) -> str | None: + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get("all")) + + proxy_keys = [ + urlparts.scheme + "://" + urlparts.hostname, + urlparts.scheme, + "all://" + urlparts.hostname, + "all", + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies( + request: Request | PreparedRequest, + proxies: dict[str, str] | None, + trust_env: bool = True, +) -> dict[str, str]: + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such as NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = cast(str, request.url) + scheme = urlparse(url).scheme + no_proxy = proxies.get("no_proxy") + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get("all")) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name: str = "python-requests") -> str: + """ + Return a string representing the default user agent. + + :rtype: str + """ + return f"{name}/{__version__}" + + +def default_headers() -> CaseInsensitiveDict[str]: + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict( + { + "User-Agent": default_user_agent(), + "Accept-Encoding": DEFAULT_ACCEPT_ENCODING, + "Accept": "*/*", + "Connection": "keep-alive", + } + ) + + +def parse_header_links(value: str) -> list[dict[str, str]]: + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links: list[dict[str, str]] = [] + + replace_chars = " '\"" + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + + link: dict[str, str] = {"url": url.strip("<> '\"")} + + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = "\x00".encode("ascii") # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data: bytes) -> str | None: + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return "utf-32" # BOM included + if sample[:3] == codecs.BOM_UTF8: + return "utf-8-sig" # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return "utf-16" # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return "utf-8" + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return "utf-16-be" + if sample[1::2] == _null2: # 2nd and 4th are null + return "utf-16-le" + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return "utf-32-be" + if sample[1:] == _null3: + return "utf-32-le" + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url: str, new_scheme: str) -> str: + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, _host, _port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = cast(str, netloc) + netloc = "@".join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = "" + + return urlunparse((scheme, netloc, path, "", query, fragment)) + + +def get_auth_from_url(url: str) -> tuple[str, str]: + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + # except handles parsed.username/password being None + auth = (unquote(parsed.username), unquote(parsed.password)) # type: ignore[arg-type] + except (AttributeError, TypeError): + auth = ("", "") + + return auth + + +def check_header_validity(header: tuple[str | bytes, str | bytes]) -> None: + """Verifies that header parts don't contain leading whitespace + reserved characters, or return characters. + + :param header: tuple, in the format (name, value). + """ + name, value = header + _validate_header_part(header, name, 0) + _validate_header_part(header, value, 1) + + +def _validate_header_part( + header: tuple[str | bytes, str | bytes], + header_part: str | bytes, + header_validator_index: int, +) -> None: + if isinstance(header_part, str): + validator = _HEADER_VALIDATORS_STR[header_validator_index] + elif isinstance(header_part, bytes): # type: ignore[reportUnnecessaryIsInstance] + # runtime guard for non-str/bytes input + validator = _HEADER_VALIDATORS_BYTE[header_validator_index] + else: + raise InvalidHeader( + f"Header part ({header_part!r}) from {header} " + f"must be of type str or bytes, not {type(header_part)}" + ) + + if not validator.match(header_part): # type: ignore[arg-type] + header_kind = "name" if header_validator_index == 0 else "value" + raise InvalidHeader( + f"Invalid leading whitespace, reserved character(s), or return " + f"character(s) in header {header_kind}: {header_part!r}" + ) + + +def urldefragauth(url: str) -> str: + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, _fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit("@", 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, "")) + + +def rewind_body(prepared_request: PreparedRequest) -> None: + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, "seek", None) + if body_seek is not None and isinstance( + prepared_request._body_position, # type: ignore[reportPrivateUsage] + integer_types, + ): + try: + body_seek(prepared_request._body_position) # type: ignore[reportPrivateUsage] + except OSError: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect." + ) + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.")