|
1339 | 1339 | "test_eq(rel.name, 'v0.0.1')" |
1340 | 1340 | ] |
1341 | 1341 | }, |
1342 | | - { |
1343 | | - "cell_type": "markdown", |
1344 | | - "id": "c49a4347", |
1345 | | - "metadata": { |
1346 | | - "solveit_ai": true |
1347 | | - }, |
1348 | | - "source": [ |
1349 | | - "```\n", |
1350 | | - " File \"/Users/jhoward/aai-ws/ghapi/ghapi/core.py\", line 63, in __call__\n", |
1351 | | - " return self.client(self.path, self.verb, headers=headers, route=route_p, query=query_p, data=data_p)\n", |
1352 | | - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", |
1353 | | - " File \"/Users/jhoward/aai-ws/ghapi/ghapi/core.py\", line 121, in __call__\n", |
1354 | | - " res,self.recv_hdrs = urlsend(path, verb, headers=headers or None, decode=False, debug=debug, return_headers=True,\n", |
1355 | | - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", |
1356 | | - " File \"/Users/jhoward/aai-ws/fastcore/fastcore/net.py\", line 221, in urlsend\n", |
1357 | | - " return urlread(req, decode=decode, return_json=return_json, return_headers=return_headers, timeout=timeout)\n", |
1358 | | - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", |
1359 | | - " File \"/Users/jhoward/aai-ws/fastcore/fastcore/net.py\", line 120, in urlread\n", |
1360 | | - " with urlopen(url, data=data, headers=headers, timeout=timeout, **kwargs) as u: res,hdrs = u.read(),u.headers\n", |
1361 | | - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", |
1362 | | - " File \"/Users/jhoward/aai-ws/fastcore/fastcore/net.py\", line 111, in urlopen\n", |
1363 | | - " try: return urlopener().open(urlwrap(url, data=data, headers=headers), timeout=timeout)\n", |
1364 | | - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", |
1365 | | - " File \"/Users/jhoward/.local/share/uv/python/cpython-3.12.0-macos-aarch64-none/lib/python3.12/urllib/request.py\", line 521, in open\n", |
1366 | | - " response = meth(req, response)\n", |
1367 | | - " ^^^^^^^^^^^^^^^^^^^\n", |
1368 | | - " File \"/Users/jhoward/.local/share/uv/python/cpython-3.12.0-macos-aarch64-none/lib/python3.12/urllib/request.py\", line 630, in http_response\n", |
1369 | | - " response = self.parent.error(\n", |
1370 | | - " ^^^^^^^^^^^^^^^^^^\n", |
1371 | | - " File \"/Users/jhoward/.local/share/uv/python/cpython-3.12.0-macos-aarch64-none/lib/python3.12/urllib/request.py\", line 553, in error\n", |
1372 | | - " result = self._call_chain(*args)\n", |
1373 | | - " ^^^^^^^^^^^^^^^^^^^^^^^\n", |
1374 | | - " File \"/Users/jhoward/.local/share/uv/python/cpython-3.12.0-macos-aarch64-none/lib/python3.12/urllib/request.py\", line 492, in _call_chain\n", |
1375 | | - " result = func(*args)\n", |
1376 | | - " ^^^^^^^^^^^\n", |
1377 | | - " File \"/Users/jhoward/.local/share/uv/python/cpython-3.12.0-macos-aarch64-none/lib/python3.12/urllib/request.py\", line 724, in http_error_302\n", |
1378 | | - " new = self.redirect_request(req, fp, code, msg, headers, newurl)\n", |
1379 | | - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", |
1380 | | - " File \"/Users/jhoward/.local/share/uv/python/cpython-3.12.0-macos-aarch64-none/lib/python3.12/urllib/request.py\", line 662, in redirect_request\n", |
1381 | | - " raise HTTPError(req.full_url, code, msg, headers, fp)\n", |
1382 | | - "urllib.error.HTTPError: HTTP Error 307: Temporary Redirect\n", |
1383 | | - "====Error Body====\n", |
1384 | | - "{\n", |
1385 | | - " \"message\": \"Moved Permanently\",\n", |
1386 | | - " \"url\": \"https://api.github.com/repositories/314706418/releases\",\n", |
1387 | | - " \"documentation_url\": \"https://docs.github.com/rest/guides/best-practices-for-using-the-rest-api#follow-redirects\"\n", |
1388 | | - "}\n", |
1389 | | - "```\n", |
1390 | | - "Sometimes I get this error. Can you look through fastcore to see what we need to change there or here to enable redirects? I think we should have them on by default.\n", |
1391 | | - "\n", |
1392 | | - "##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->\n", |
1393 | | - "\n", |
1394 | | - "🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠\n", |
1395 | | - "\n", |
1396 | | - "Let me look at the fastcore networking code to understand how redirects are handled:\n", |
1397 | | - "<details class='token-usage-details'><summary>Cache hit: 0.0% | Tokens: total=55,463 input=55,250 (+0 cached, 43,847 new) output=213</summary>\n", |
1398 | | - "\n", |
1399 | | - "`Usage(completion_tokens=213, prompt_tokens=55250, total_tokens=55463, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=213, image_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=0, text_tokens=None, image_tokens=None, cache_creation_tokens=43847), cache_creation_input_tokens=43847, cache_read_input_tokens=0)`\n", |
1400 | | - "\n", |
1401 | | - "</details>\n", |
1402 | | - "\n", |
1403 | | - "\n", |
1404 | | - "<details class='tool-usage-details'>\n", |
1405 | | - "<summary>view(path='/Users/jhoward/aai-ws/fastcore/fastcore/net.py', nums=True)</summary>\n", |
1406 | | - "\n", |
1407 | | - "```json\n", |
1408 | | - "{\n", |
1409 | | - " \"id\": \"toolu_017R1tVfTRvatQ3c6ECSeyXV\",\n", |
1410 | | - " \"call\": {\n", |
1411 | | - " \"function\": \"view\",\n", |
1412 | | - " \"arguments\": {\n", |
1413 | | - " \"path\": \"/Users/jhoward/aai-ws/fastcore/fastcore/net.py\",\n", |
1414 | | - " \"nums\": \"True\"\n", |
1415 | | - " }\n", |
1416 | | - " },\n", |
1417 | | - " \"result\": \"<TRUNCATED>\\u2026work, HTTP, and URL functions\\\"\\\"\\\"\\n 2 \\u2502 \\n 3 \\u2502 # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03b_net.ipynb.\\n 4 \\u2502 \\n 5 \\u2502 # %% auto #0\\n 6 \\u2502 __all__ = ['url_default_headers', 'ExceptionsHTTP', 'urlquote', 'urlwrap', 'HTTP4xxClientError', 'HTTP5xxServerError',\\n 7 \\u2502 'urlopener', 'urlopen', 'urlread', 'urljson', 'urlcheck', 'urlclean', 'urlretrieve', 'urldest', 'urlsave',\\n 8 \\u2502 'urlvalid', 'urlrequest', 'urlsend', 'do_request', 'start_server', 'start_client', 'tobytes',\\n 9 \\u2502 'http_response', 'recv_once', 'HTTP400BadRequestError', 'HTTP401UnauthorizedError',\\n 10 \\u2502 'HTTP402PaymentRequiredError', 'HTTP403ForbiddenError', 'HTTP404NotFoundError',\\n 11 \\u2502 'HTTP405MethodNotAllowedError', 'HTTP406NotAcceptableError', 'HTTP407ProxyAuthRequiredError',\\n 12 \\u2502 'HTTP408RequestTimeoutError', 'HTTP409ConflictError', 'HTTP410GoneError', 'HTTP411LengthRequiredError',\\n 13 \\u2502 \\u2026</TRUNCATED>\"\n", |
1418 | | - "}\n", |
1419 | | - "```\n", |
1420 | | - "\n", |
1421 | | - "</details>\n", |
1422 | | - "\n", |
1423 | | - "\n", |
1424 | | - "\n", |
1425 | | - "🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠\n", |
1426 | | - "\n", |
1427 | | - "I can see the issue. On line 77-82, `_SafeRedirectHandler.redirect_request` calls `super()`, which is Python's stdlib `HTTPRedirectHandler`. The stdlib raises `HTTPError` for 307/308 with anything other than GET/HEAD — it doesn't preserve the method and body as 307/308 require.\n", |
1428 | | - "\n", |
1429 | | - "The fix is to handle 307/308 directly in `_SafeRedirectHandler` before calling `super()`. Let me check the source notebook:\n", |
1430 | | - "<details class='token-usage-details'><summary>Cache hit: 70.9% | Tokens: total=64,472 input=61,812 (+43,847 cached, 11,400 new) output=2,660</summary>\n", |
1431 | | - "\n", |
1432 | | - "`Usage(completion_tokens=2660, prompt_tokens=61812, total_tokens=64472, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=2660, image_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=43847, text_tokens=None, image_tokens=None, cache_creation_tokens=11400), cache_creation_input_tokens=11400, cache_read_input_tokens=43847)`\n", |
1433 | | - "\n", |
1434 | | - "</details>\n", |
1435 | | - "\n", |
1436 | | - "\n", |
1437 | | - "<details class='tool-usage-details'>\n", |
1438 | | - "<summary>find_msgs(dname='/aai-ws/fastcore/nbs/03b_net', re_pattern='_SafeRedirectHandler')</summary>\n", |
1439 | | - "\n", |
1440 | | - "```json\n", |
1441 | | - "{\n", |
1442 | | - " \"id\": \"toolu_017jfsewPEFWG6nirtVVzw2V\",\n", |
1443 | | - " \"call\": {\n", |
1444 | | - " \"function\": \"find_msgs\",\n", |
1445 | | - " \"arguments\": {\n", |
1446 | | - " \"dname\": \"/aai-ws/fastcore/nbs/03b_net\",\n", |
1447 | | - " \"re_pattern\": \"_SafeRedirectHandler\"\n", |
1448 | | - " }\n", |
1449 | | - " },\n", |
1450 | | - " \"result\": \"[{'id': '_128b5f4a', 'time_run': '', 'is_exported': 1, 'skipped': 0, 'hidden': False, 'bookmark': {}, 'i_collapsed': 0, 'o_collapsed': 0, 'heading_collapsed': False, 'i_clamp': False, 'o_clamp': False, 'pinned': 0, 'run': {}, 'content': \\\"class _SafeRedirectHandler(urllib.request.HTTPRedirectHandler):\\\\n def redirect_request(self, req, fp, code, msg, headers, newurl):\\\\n new_req = super().redirect_request(req, fp, code, msg, headers, newurl)\\\\n if new_req and urlparse(newurl).netloc != urlparse(req.full_url).netloc:\\\\n new_req.remove_header('Authorization')\\\\n return new_req\\\\n\\\\ndef urlopener():\\\\n _opener = urllib.request.build_opener(_SafeRedirectHandler)\\\\n _opener.addheaders = list(url_default_headers.items())\\\\n return _opener\\\", 'output': '', 'msg_type': 'code', 'input_tokens': 211, 'output_tokens': {}}]\"\n", |
1451 | | - "}\n", |
1452 | | - "```\n", |
1453 | | - "\n", |
1454 | | - "</details>\n", |
1455 | | - "\n", |
1456 | | - "\n", |
1457 | | - "<details class='token-usage-details'><summary>Cache hit: 85.3% | Tokens: total=64,851 input=64,759 (+55,247 cached, 215 new) output=92</summary>\n", |
1458 | | - "\n", |
1459 | | - "`Usage(completion_tokens=92, prompt_tokens=64759, total_tokens=64851, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=92, image_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=55247, text_tokens=None, image_tokens=None, cache_creation_tokens=215), cache_creation_input_tokens=215, cache_read_input_tokens=55247)`\n", |
1460 | | - "\n", |
1461 | | - "</details>\n", |
1462 | | - "\n", |
1463 | | - "\n", |
1464 | | - "<details class='tool-usage-details'>\n", |
1465 | | - "<summary>view_msg(id='_128b5f4a', dname='/aai-ws/fastcore/nbs/03b_net')</summary>\n", |
1466 | | - "\n", |
1467 | | - "```json\n", |
1468 | | - "{\n", |
1469 | | - " \"id\": \"toolu_0176XwAtruq7tFxjbvwS62S7\",\n", |
1470 | | - " \"call\": {\n", |
1471 | | - " \"function\": \"view_msg\",\n", |
1472 | | - " \"arguments\": {\n", |
1473 | | - " \"id\": \"_128b5f4a\",\n", |
1474 | | - " \"dname\": \"/aai-ws/fastcore/nbs/03b_net\"\n", |
1475 | | - " }\n", |
1476 | | - " },\n", |
1477 | | - " \"result\": \"1 \\u2502 class _SafeRedirectHandler(urllib.request.HTTPRedirectHandler):\\n 2 \\u2502 def redirect_request(self, req, fp, code, msg, headers, newurl):\\n 3 \\u2502 new_req = super().redirect_request(req, fp, code, msg, headers, newurl)\\n 4 \\u2502 if new_req and urlparse(newurl).netloc != urlparse(req.full_url).netloc:\\n 5 \\u2502 new_req.remove_header('Authorization')\\n 6 \\u2502 return new_req\\n 7 \\u2502 \\n 8 \\u2502 def urlopener():\\n 9 \\u2502 _opener = urllib.request.build_opener(_SafeRedirectHandler)\\n 10 \\u2502 _opener.addheaders = list(url_default_headers.items())\\n 11 \\u2502 return _opener\"\n", |
1478 | | - "}\n", |
1479 | | - "```\n", |
1480 | | - "\n", |
1481 | | - "</details>\n", |
1482 | | - "\n", |
1483 | | - "The problem: `super().redirect_request()` on line 3 calls Python's `HTTPRedirectHandler.redirect_request`, which raises `HTTPError` for 307/308 with any method other than GET/HEAD. Per the HTTP spec, 307/308 must preserve the original method and body — so we need to handle them before calling `super()`.\n", |
1484 | | - "\n", |
1485 | | - "Here's the fix:\n", |
1486 | | - "\n", |
1487 | | - "```python\n", |
1488 | | - "class _SafeRedirectHandler(urllib.request.HTTPRedirectHandler):\n", |
1489 | | - " def redirect_request(self, req, fp, code, msg, headers, newurl):\n", |
1490 | | - " if code in (307, 308):\n", |
1491 | | - " new_req = Request(newurl, data=req.data, method=req.get_method())\n", |
1492 | | - " for k,v in req.headers.items(): new_req.add_header(k, v)\n", |
1493 | | - " else:\n", |
1494 | | - " new_req = super().redirect_request(req, fp, code, msg, headers, newurl)\n", |
1495 | | - " if new_req and urlparse(newurl).netloc != urlparse(req.full_url).netloc:\n", |
1496 | | - " new_req.remove_header('Authorization')\n", |
1497 | | - " return new_req\n", |
1498 | | - "```\n", |
1499 | | - "\n", |
1500 | | - "The key changes:\n", |
1501 | | - "- For **307/308**: build a new `Request` manually, preserving `req.data` and `req.get_method()` (plus headers) — this is what the spec requires\n", |
1502 | | - "- For **301/302/303**: fall through to `super()` as before (these convert to GET, which is the standard behavior)\n", |
1503 | | - "- The **Authorization stripping** on cross-origin redirects still applies to both paths\n", |
1504 | | - "<details class='token-usage-details'><summary>Cache hit: 85.2% | Tokens: total=65,439 input=65,071 (+55,462 cached, 8,974 new) output=368</summary>\n", |
1505 | | - "\n", |
1506 | | - "`Usage(completion_tokens=368, prompt_tokens=65071, total_tokens=65439, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=368, image_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=55462, text_tokens=None, image_tokens=None, cache_creation_tokens=8974), cache_creation_input_tokens=8974, cache_read_input_tokens=55462)`\n", |
1507 | | - "\n", |
1508 | | - "</details>\n" |
1509 | | - ] |
1510 | | - }, |
1511 | | - { |
1512 | | - "cell_type": "markdown", |
1513 | | - "id": "a43c002e", |
1514 | | - "metadata": { |
1515 | | - "solveit_ai": true |
1516 | | - }, |
1517 | | - "source": [ |
1518 | | - "We can't use `super().redirect_request` for 307/8 and pass in the data/method?\n", |
1519 | | - "\n", |
1520 | | - "##### 🤖Reply🤖<!-- SOLVEIT_SEPARATOR_7f3a9b2c -->\n", |
1521 | | - "\n", |
1522 | | - "🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠\n", |
1523 | | - "\n", |
1524 | | - "No — the stdlib's `HTTPRedirectHandler.redirect_request` explicitly raises `HTTPError` for 307/308 when the method isn't GET or HEAD:\n", |
1525 | | - "\n", |
1526 | | - "```python\n", |
1527 | | - "if (not (code in (301, 302, 303, 307, 308) and m in (\"GET\", \"HEAD\")\n", |
1528 | | - " or code in (301, 302, 303) and m == \"POST\")):\n", |
1529 | | - " raise HTTPError(req.full_url, code, msg, headers, fp)\n", |
1530 | | - "```\n", |
1531 | | - "\n", |
1532 | | - "So for a POST to a GitHub endpoint that returns 307, `super()` will always raise before it gets a chance to build the redirect request. That's why we have to handle 307/308 ourselves before calling `super()`.\n", |
1533 | | - "<details class='token-usage-details'><summary>Cache hit: 73.2% | Tokens: total=57,585 input=57,043 (+41,777 cached, 15,058 new) output=542</summary>\n", |
1534 | | - "\n", |
1535 | | - "`Usage(completion_tokens=542, prompt_tokens=57043, total_tokens=57585, completion_tokens_details=CompletionTokensDetailsWrapper(accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=0, rejected_prediction_tokens=None, text_tokens=542, image_tokens=None), prompt_tokens_details=PromptTokensDetailsWrapper(audio_tokens=None, cached_tokens=41777, text_tokens=None, image_tokens=None, cache_creation_tokens=15058), cache_creation_input_tokens=15058, cache_read_input_tokens=41777)`\n", |
1536 | | - "\n", |
1537 | | - "</details>\n" |
1538 | | - ] |
1539 | | - }, |
1540 | 1342 | { |
1541 | 1343 | "cell_type": "code", |
1542 | 1344 | "execution_count": null, |
|
0 commit comments