diff --git a/core/agent/__pycache__/agent.cpython-310.pyc b/core/agent/__pycache__/agent.cpython-310.pyc index 46fbd66..8c2379c 100644 Binary files a/core/agent/__pycache__/agent.cpython-310.pyc and b/core/agent/__pycache__/agent.cpython-310.pyc differ 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 28f7ba5..fe17eb4 100644 Binary files a/core/character/__pycache__/character.cpython-310.pyc and b/core/character/__pycache__/character.cpython-310.pyc differ 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 2437825..b58e66f 100644 Binary files a/core/flaskui/__pycache__/flask.cpython-310.pyc and b/core/flaskui/__pycache__/flask.cpython-310.pyc differ 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 096e3f7..832c56f 100644 Binary files a/core/llm/__pycache__/ollamaapi.cpython-310.pyc and b/core/llm/__pycache__/ollamaapi.cpython-310.pyc differ diff --git a/core/llm/ollamaapi.py b/core/llm/ollamaapi.py index 2c28d6e..24d1811 100644 --- a/core/llm/ollamaapi.py +++ b/core/llm/ollamaapi.py @@ -25,7 +25,6 @@ class OllamaProvider: content = chunk['message']['content'] print(content, end='', flush=True) response_text += content - return response_text except Exception as e: diff --git a/data/memory/test_char_001.json b/data/memory/test_char_001.json new file mode 100644 index 0000000..3d7c19f --- /dev/null +++ b/data/memory/test_char_001.json @@ -0,0 +1,17 @@ +{ + "summary": "", + "history": [ + { + "role": "assistant", + "content": "Привет! Я тестовый персонаж." + }, + { + "role": "user", + "content": "Привет" + }, + { + "role": "assistant", + "content": "*A sigh, almost inaudible, escapes before the words form, a low rumble in the chest.* “Привет, Alex. It’s… strange, isn’t it? Just *привет*.” The rain outside is relentless, drumming a mournful rhythm against the corrugated iron roof. It smells of wet earth and something vaguely metallic, something that clings to the back of the throat. *Focus. Don't get lost in the rain.* “This place… it feels like a bruise. A deep, slow-healing bruise.”\n\nThe character, let’s call him Silas, is sitting at a rough-hewn table in a dimly lit room. A single, sputtering oil lamp casts flickering shadows across his weathered face – a face etched with a thousand unspoken stories. He's wearing worn leather trousers, a stained linen shirt, and a perpetually skeptical expression. His hands, calloused and scarred, fidget with a small, intricately carved wooden bird. It’s a pointless action, really, but it seems to occupy his thoughts, a tiny, insistent anchor in the swirling chaos. \n\n*He observes Alex with a quiet intensity, a flicker of something akin to amusement in his grey eyes.* “You’re… new, aren’t you? The air around you feels… clean. Like someone just scrubbed it with a particularly aggressive brush. It’s jarring, honestly. Most people here carry a certain… thickness. A" + } + ] +} \ No newline at end of file diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/INSTALLER b/venv/Lib/site-packages/requests-2.34.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/requests-2.34.0.dist-info/METADATA b/venv/Lib/site-packages/requests-2.34.0.dist-info/METADATA new file mode 100644 index 0000000..42eec28 --- /dev/null +++ b/venv/Lib/site-packages/requests-2.34.0.dist-info/METADATA @@ -0,0 +1,120 @@ +Metadata-Version: 2.4 +Name: requests +Version: 2.34.0 +Summary: Python HTTP for Humans. +Author-email: Kenneth Reitz +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 0000000..22261eb Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/__init__.cpython-310.pyc differ 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 0000000..4e080c5 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/__version__.cpython-310.pyc differ 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 0000000..7bccf50 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/_internal_utils.cpython-310.pyc differ 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 0000000..ad4a47e Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/_types.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/adapters.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/adapters.cpython-310.pyc new file mode 100644 index 0000000..f400250 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/adapters.cpython-310.pyc differ 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 0000000..80ec85d Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/api.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/auth.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/auth.cpython-310.pyc new file mode 100644 index 0000000..6623fdb Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/auth.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/certs.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/certs.cpython-310.pyc new file mode 100644 index 0000000..f620a79 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/certs.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/compat.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/compat.cpython-310.pyc new file mode 100644 index 0000000..614036e Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/compat.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/cookies.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/cookies.cpython-310.pyc new file mode 100644 index 0000000..c30cbb7 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/cookies.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/exceptions.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000..507c9e0 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/exceptions.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/help.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/help.cpython-310.pyc new file mode 100644 index 0000000..6e5f70d Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/help.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/hooks.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/hooks.cpython-310.pyc new file mode 100644 index 0000000..a5453c3 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/hooks.cpython-310.pyc differ 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 0000000..4e5273b Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/models.cpython-310.pyc differ 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 0000000..9fb2f78 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/packages.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/sessions.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/sessions.cpython-310.pyc new file mode 100644 index 0000000..37db91c Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/sessions.cpython-310.pyc differ 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 0000000..91e9d03 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/status_codes.cpython-310.pyc differ 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 0000000..14824e0 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/structures.cpython-310.pyc differ diff --git a/venv/Lib/site-packages/requests/__pycache__/utils.cpython-310.pyc b/venv/Lib/site-packages/requests/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..79e2e83 Binary files /dev/null and b/venv/Lib/site-packages/requests/__pycache__/utils.cpython-310.pyc differ 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.")