|
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 | + }, |
1342 | 1540 | { |
1343 | 1541 | "cell_type": "code", |
1344 | 1542 | "execution_count": null, |
|
2305 | 2503 | "source": [] |
2306 | 2504 | } |
2307 | 2505 | ], |
2308 | | - "metadata": {}, |
| 2506 | + "metadata": { |
| 2507 | + "solveit_dialog_mode": "concise", |
| 2508 | + "solveit_ver": 2 |
| 2509 | + }, |
2309 | 2510 | "nbformat": 4, |
2310 | 2511 | "nbformat_minor": 5 |
2311 | 2512 | } |
0 commit comments