Skip to content

Commit 62371c9

Browse files
committed
release
1 parent 0badeb3 commit 62371c9

File tree

2 files changed

+202
-5
lines changed

2 files changed

+202
-5
lines changed

00_core.ipynb

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,204 @@
13391339
"test_eq(rel.name, 'v0.0.1')"
13401340
]
13411341
},
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+
},
13421540
{
13431541
"cell_type": "code",
13441542
"execution_count": null,
@@ -2305,7 +2503,10 @@
23052503
"source": []
23062504
}
23072505
],
2308-
"metadata": {},
2506+
"metadata": {
2507+
"solveit_dialog_mode": "concise",
2508+
"solveit_ver": 2
2509+
},
23092510
"nbformat": 4,
23102511
"nbformat_minor": 5
23112512
}

CHANGELOG.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
<!-- do not remove -->
44

5-
6-
7-
8-
95
## 1.0.11
106

117
### New Features

0 commit comments

Comments
 (0)