decentralized and customizable links page on top of atproto

replace requests with httpx

+1 -2
pyproject.toml
··· 9 9 "dnspython>=2.8.0", 10 10 "flask[dotenv]>=3.1.2", 11 11 "gunicorn>=23.0.0", 12 - "requests>=2.32", 13 - "requests-hardened>=1.2.0", 12 + "httpx>=0.28.1", 14 13 ]
+8 -8
src/atproto/__init__.py
··· 1 1 from dns.resolver import resolve as resolve_dns 2 2 from re import match as regex_match 3 3 from typing import Any 4 - import requests 4 + import httpx 5 5 6 6 from .validator import is_valid_authserver_meta 7 7 from ..security import is_safe_url ··· 125 125 directory: str = PLC_DIRECTORY, 126 126 ) -> dict[str, Any] | None: 127 127 if did.startswith("did:plc:"): 128 - response = requests.get(f"{directory}/{did}") 129 - if response.ok: 128 + response = httpx.get(f"{directory}/{did}") 129 + if response.is_success: 130 130 return response.json() 131 131 return None 132 132 ··· 149 149 150 150 assert is_safe_url(pds_url) 151 151 endpoint = f"{pds_url}/.well-known/oauth-protected-resource" 152 - response = requests.get(endpoint) 152 + response = httpx.get(endpoint) 153 153 if response.status_code != 200: 154 154 return None 155 155 parsed: dict[str, list[str]] = response.json() ··· 176 176 177 177 178 178 def http_get_json(url: str) -> Any | None: 179 - response = requests.get(url) 180 - if response.ok: 179 + response = httpx.get(url) 180 + if response.is_success: 181 181 return response.json() 182 182 return None 183 183 184 184 185 185 def http_get(url: str) -> str | None: 186 - response = requests.get(url) 187 - if response.ok: 186 + response = httpx.get(url) 187 + if response.is_success: 188 188 return response.text 189 189 return None
+1 -1
src/atproto/oauth.py
··· 5 5 from authlib.common.security import generate_token 6 6 from authlib.jose import jwt 7 7 from authlib.oauth2.rfc7636 import create_s256_code_challenge 8 - from requests import Response 8 + from httpx import Response 9 9 10 10 from . import fetch_authserver_meta 11 11
+22 -4
src/main.py
··· 2 2 from typing import Any 3 3 import json 4 4 5 - from .atproto import PdsUrl, get_record, resolve_did_from_handle, resolve_pds_from_did 5 + from .atproto import ( 6 + PdsUrl, 7 + get_record, 8 + is_valid_did, 9 + resolve_did_from_handle, 10 + resolve_pds_from_did, 11 + ) 6 12 from .atproto.oauth import pds_authed_req 7 13 from .db import close_db_connection, init_db 8 14 from .oauth import get_auth_session, oauth, save_auth_session ··· 16 22 links: dict[str, list[dict[str, str]]] = {} 17 23 profiles: dict[str, tuple[str, str]] = {} 18 24 19 - SCHEMA = "one.nauta" 25 + SCHEMA = "at.ligo" 20 26 21 27 22 28 @app.before_request ··· 38 44 return render_template("index.html") 39 45 40 46 47 + @app.get("/did:<string:did>") 48 + def page_profile_with_did(did: str): 49 + did = f"did:{did}" 50 + if not is_valid_did(did): 51 + return "invalid did", 400 52 + return page_profile(did) 53 + 54 + 41 55 @app.get("/@<string:handle>") 42 - def page_profile(handle: str): 56 + def page_profile_with_handle(handle: str): 43 57 reload = request.args.get("reload") is not None 44 58 45 59 did = resolve_did_from_handle(handle, reload=reload) 46 60 if did is None: 47 61 return "did not found", 404 62 + return page_profile(did, reload=reload) 63 + 64 + 65 + def page_profile(did: str, reload: bool = False): 48 66 pds = resolve_pds_from_did(did, reload=reload) 49 67 if pds is None: 50 68 return "pds not found", 404 ··· 242 260 user=user, 243 261 update_dpop_pds_nonce=update_dpop_pds_nonce, 244 262 ) 245 - if not response or not response.ok: 263 + if not response or not response.is_success: 246 264 app.logger.warning("PDS HTTP ERROR")
+13 -11
src/security.py
··· 1 1 from urllib.parse import urlparse 2 - import requests_hardened 2 + import httpx 3 3 4 4 5 5 # this is a crude/partial filter that looks at HTTPS URLs and checks if they seem "safe" for server-side requests (SSRF). This is only a partial mitigation, the actual HTTP client also needs to prevent other attacks and behaviors. ··· 29 29 return True 30 30 31 31 32 - # configures a "hardened" requests wrapper 33 - hardened_http = requests_hardened.Manager( 34 - requests_hardened.Config( 35 - default_timeout=(2, 10), 36 - never_redirect=True, 37 - ip_filter_enable=True, 38 - ip_filter_allow_loopback_ips=False, 39 - user_agent_override="AtprotoCookbookOAuthFlaskDemo", 40 - ) 41 - ) 32 + class HardenedHttp: 33 + def get_session(self) -> httpx.Client: 34 + return httpx.Client( 35 + timeout=httpx.Timeout(20, connect=5), 36 + follow_redirects=False, 37 + headers={ 38 + "User-Agent": "ligo.at/0", 39 + }, 40 + ) 41 + 42 + 43 + hardened_http = HardenedHttp()
+56 -66
uv.lock
··· 3 3 requires-python = ">=3.13" 4 4 5 5 [[package]] 6 + name = "anyio" 7 + version = "4.11.0" 8 + source = { registry = "https://pypi.org/simple" } 9 + dependencies = [ 10 + { name = "idna" }, 11 + { name = "sniffio" }, 12 + ] 13 + sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } 14 + wheels = [ 15 + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, 16 + ] 17 + 18 + [[package]] 6 19 name = "authlib" 7 20 version = "1.6.5" 8 21 source = { registry = "https://pypi.org/simple" } ··· 78 91 ] 79 92 80 93 [[package]] 81 - name = "charset-normalizer" 82 - version = "3.4.3" 83 - source = { registry = "https://pypi.org/simple" } 84 - sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } 85 - wheels = [ 86 - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, 87 - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, 88 - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, 89 - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, 90 - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, 91 - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, 92 - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, 93 - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, 94 - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, 95 - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, 96 - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, 97 - { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, 98 - { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, 99 - { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, 100 - { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, 101 - { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, 102 - { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, 103 - { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, 104 - { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, 105 - { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, 106 - { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, 107 - { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, 108 - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, 109 - ] 110 - 111 - [[package]] 112 94 name = "click" 113 95 version = "8.3.0" 114 96 source = { registry = "https://pypi.org/simple" } ··· 208 190 ] 209 191 210 192 [[package]] 193 + name = "h11" 194 + version = "0.16.0" 195 + source = { registry = "https://pypi.org/simple" } 196 + sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 197 + wheels = [ 198 + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 199 + ] 200 + 201 + [[package]] 202 + name = "httpcore" 203 + version = "1.0.9" 204 + source = { registry = "https://pypi.org/simple" } 205 + dependencies = [ 206 + { name = "certifi" }, 207 + { name = "h11" }, 208 + ] 209 + sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } 210 + wheels = [ 211 + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, 212 + ] 213 + 214 + [[package]] 215 + name = "httpx" 216 + version = "0.28.1" 217 + source = { registry = "https://pypi.org/simple" } 218 + dependencies = [ 219 + { name = "anyio" }, 220 + { name = "certifi" }, 221 + { name = "httpcore" }, 222 + { name = "idna" }, 223 + ] 224 + sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 225 + wheels = [ 226 + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 227 + ] 228 + 229 + [[package]] 211 230 name = "idna" 212 231 version = "3.10" 213 232 source = { registry = "https://pypi.org/simple" } ··· 246 265 { name = "dnspython" }, 247 266 { name = "flask", extra = ["dotenv"] }, 248 267 { name = "gunicorn" }, 249 - { name = "requests" }, 250 - { name = "requests-hardened" }, 268 + { name = "httpx" }, 251 269 ] 252 270 253 271 [package.metadata] ··· 256 274 { name = "dnspython", specifier = ">=2.8.0" }, 257 275 { name = "flask", extras = ["dotenv"], specifier = ">=3.1.2" }, 258 276 { name = "gunicorn", specifier = ">=23.0.0" }, 259 - { name = "requests", specifier = ">=2.32" }, 260 - { name = "requests-hardened", specifier = ">=1.2.0" }, 277 + { name = "httpx", specifier = ">=0.28.1" }, 261 278 ] 262 279 263 280 [[package]] ··· 340 357 ] 341 358 342 359 [[package]] 343 - name = "requests" 344 - version = "2.32.5" 360 + name = "sniffio" 361 + version = "1.3.1" 345 362 source = { registry = "https://pypi.org/simple" } 346 - dependencies = [ 347 - { name = "certifi" }, 348 - { name = "charset-normalizer" }, 349 - { name = "idna" }, 350 - { name = "urllib3" }, 351 - ] 352 - sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } 363 + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } 353 364 wheels = [ 354 - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, 355 - ] 356 - 357 - [[package]] 358 - name = "requests-hardened" 359 - version = "1.2.0" 360 - source = { registry = "https://pypi.org/simple" } 361 - dependencies = [ 362 - { name = "requests" }, 363 - ] 364 - sdist = { url = "https://files.pythonhosted.org/packages/0d/ab/3206848b4657be7902bb10af5686f71da450d9135340ecd6ee80da718557/requests_hardened-1.2.0.tar.gz", hash = "sha256:24ff13c798a22afc3465c24ff955b003c81f605e2ec30cbdbd40f28389cfca72", size = 7254, upload-time = "2025-09-26T12:20:20.518Z" } 365 - wheels = [ 366 - { url = "https://files.pythonhosted.org/packages/8d/0e/b521e2034f0984b3a446009223e8ec67bfae5e3d4a11b0066951d2df6515/requests_hardened-1.2.0-py3-none-any.whl", hash = "sha256:7d70b38bbfdea3f1d27d9149a5967f8c350b3496d232b1d4b031b7d0f2590ba9", size = 9197, upload-time = "2025-09-26T12:20:19.126Z" }, 367 - ] 368 - 369 - [[package]] 370 - name = "urllib3" 371 - version = "2.5.0" 372 - source = { registry = "https://pypi.org/simple" } 373 - sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } 374 - wheels = [ 375 - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, 365 + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, 376 366 ] 377 367 378 368 [[package]]