-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements Response.from_response and support for cf opts in Py fetch. #3229
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,7 +5,7 @@ | |||||||||||
from contextlib import ExitStack, contextmanager | ||||||||||||
from enum import StrEnum | ||||||||||||
from http import HTTPMethod, HTTPStatus | ||||||||||||
from typing import TypedDict, Unpack | ||||||||||||
from typing import Any, TypedDict, Unpack | ||||||||||||
|
||||||||||||
import js | ||||||||||||
|
||||||||||||
|
@@ -21,10 +21,32 @@ | |||||||||||
Headers = dict[str, str] | list[tuple[str, str]] | ||||||||||||
|
||||||||||||
|
||||||||||||
# https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties | ||||||||||||
class RequestInitCfProperties(TypedDict, total=False): | ||||||||||||
apps: bool | None | ||||||||||||
cacheEverything: bool | None | ||||||||||||
cacheKey: str | None | ||||||||||||
cacheTags: list[str] | None | ||||||||||||
cacheTtl: int | ||||||||||||
cacheTtlByStatus: dict[str, int] | ||||||||||||
image: ( | ||||||||||||
Any | None | ||||||||||||
) # TODO: https://developers.cloudflare.com/images/transform-images/transform-via-workers/ | ||||||||||||
mirage: bool | None | ||||||||||||
polish: str | None | ||||||||||||
resolveOverride: str | None | ||||||||||||
scrapeShield: bool | None | ||||||||||||
webp: bool | None | ||||||||||||
|
||||||||||||
|
||||||||||||
# This matches the Request options: | ||||||||||||
# https://developers.cloudflare.com/workers/runtime-apis/request/#options | ||||||||||||
class FetchKwargs(TypedDict, total=False): | ||||||||||||
headers: Headers | None | ||||||||||||
body: "Body | None" | ||||||||||||
method: HTTPMethod = HTTPMethod.GET | ||||||||||||
redirect: str | None | ||||||||||||
cf: RequestInitCfProperties | None | ||||||||||||
|
||||||||||||
|
||||||||||||
# TODO: Pyodide's FetchResponse.headers returns a dict[str, str] which means | ||||||||||||
|
@@ -99,7 +121,7 @@ def __init__( | |||||||||||
self, | ||||||||||||
body: Body, | ||||||||||||
status: HTTPStatus | int = HTTPStatus.OK, | ||||||||||||
statusText="", | ||||||||||||
status_text="", | ||||||||||||
headers: Headers = None, | ||||||||||||
): | ||||||||||||
""" | ||||||||||||
|
@@ -108,7 +130,7 @@ def __init__( | |||||||||||
Based on the JS API of the same name: | ||||||||||||
https://developer.mozilla.org/en-US/docs/Web/API/Response/Response. | ||||||||||||
""" | ||||||||||||
options = self._create_options(status, statusText, headers) | ||||||||||||
options = self._create_options(status, status_text, headers) | ||||||||||||
|
||||||||||||
# Initialise via the FetchResponse super-class which gives us access to | ||||||||||||
# methods that we would ordinarily have to redeclare. | ||||||||||||
|
@@ -117,15 +139,25 @@ def __init__( | |||||||||||
) | ||||||||||||
super().__init__(js_resp.url, js_resp) | ||||||||||||
|
||||||||||||
@classmethod | ||||||||||||
def from_response(cls, body: Body, response: "Response") -> "Response": | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||
b = body.js_object if isinstance(body, FormData) else body | ||||||||||||
result = cls.__new__(cls) | ||||||||||||
js_resp = js.Response.new(b, response.js_object) | ||||||||||||
super(Response, result).__init__(js_resp.url, js_resp) | ||||||||||||
Comment on lines
+145
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain the reasoning for this? Ideally we should switch to:
Suggested change
or |
||||||||||||
return result | ||||||||||||
|
||||||||||||
@staticmethod | ||||||||||||
def _create_options( | ||||||||||||
status: HTTPStatus | int = HTTPStatus.OK, statusText="", headers: Headers = None | ||||||||||||
status: HTTPStatus | int = HTTPStatus.OK, | ||||||||||||
status_text="", | ||||||||||||
headers: Headers = None, | ||||||||||||
): | ||||||||||||
options = { | ||||||||||||
"status": status.value if isinstance(status, HTTPStatus) else status, | ||||||||||||
} | ||||||||||||
if len(statusText) > 0: | ||||||||||||
options["statusText"] = statusText | ||||||||||||
if len(status_text) > 0: | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does the same thing afaict:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Explicit is always better than implicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well it's most common in python code to check if a container is non empty with |
||||||||||||
options["statusText"] = status_text | ||||||||||||
if headers: | ||||||||||||
if isinstance(headers, list): | ||||||||||||
# We should have a list[tuple[str, str]] | ||||||||||||
|
@@ -154,10 +186,10 @@ def redirect(url: str, status: HTTPStatus | int = HTTPStatus.FOUND): | |||||||||||
def json( | ||||||||||||
data: str | dict[str, str], | ||||||||||||
status: HTTPStatus | int = HTTPStatus.OK, | ||||||||||||
statusText="", | ||||||||||||
status_text="", | ||||||||||||
headers: Headers = None, | ||||||||||||
): | ||||||||||||
options = Response._create_options(status, statusText, headers) | ||||||||||||
options = Response._create_options(status, status_text, headers) | ||||||||||||
with _manage_pyproxies() as pyproxies: | ||||||||||||
try: | ||||||||||||
return js.Response.json( | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,8 +51,11 @@ async def on_fetch(request): | |
elif request.url.endswith("/undefined_opts"): | ||
# This tests two things: | ||
# * `Response.redirect` static method | ||
# * that other options can be passed into `fetch` | ||
resp = await fetch("https://example.com/redirect", redirect="manual") | ||
# * that other options can be passed into `fetch` (so that we can support | ||
# new options without updating this code) | ||
resp = await fetch( | ||
"https://example.com/redirect", redirect="manual", foobarbaz=42 | ||
) | ||
Comment on lines
+54
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we could test that we receive |
||
return resp | ||
elif request.url.endswith("/response_inherited"): | ||
expected = "test123" | ||
|
@@ -89,6 +92,14 @@ async def on_fetch(request): | |
assert data["blob.py"].content_type == "text/python" | ||
assert data["metadata"].name == "metadata.json" | ||
|
||
return Response("success") | ||
elif request.url.endswith("/cf_opts"): | ||
resp = await fetch( | ||
"http://example.com/redirect", | ||
redirect="manual", | ||
cf={"cacheTtl": 5, "cacheEverything": True, "cacheKey": "someCustomKey"}, | ||
) | ||
assert resp.status == 301 | ||
return Response("success") | ||
else: | ||
resp = await fetch("https://example.com/sub") | ||
|
@@ -273,6 +284,23 @@ async def can_request_form_data_blob(env): | |
assert text == "success" | ||
|
||
|
||
async def response_from_unit_tests(env): | ||
response = Response("test", status=201, status_text="Created") | ||
cloned = Response.from_response("other", response) | ||
assert cloned.status == 201 | ||
assert cloned.status_text == "Created" | ||
t = await cloned.text() | ||
assert t == "other" | ||
|
||
|
||
async def can_use_cf_fetch_opts(env): | ||
response = await env.SELF.fetch( | ||
"http://example.com/cf_opts", | ||
) | ||
text = await response.text() | ||
assert text == "success" | ||
|
||
|
||
async def test(ctrl, env): | ||
await can_return_custom_fetch_response(env) | ||
await can_modify_response(env) | ||
|
@@ -287,3 +315,5 @@ async def test(ctrl, env): | |
await form_data_unit_tests(env) | ||
await blob_unit_tests(env) | ||
await can_request_form_data_blob(env) | ||
await response_from_unit_tests(env) | ||
await can_use_cf_fetch_opts(env) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting is inconsistent here, does our lint check not check formatting?