crosswind-workers 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _crosswind_patches/__init__.py +13 -0
- _crosswind_patches/entropy.py +259 -0
- _crosswind_patches/runtime.py +45 -0
- asgi/__init__.py +7 -0
- crosswind_workers-0.1.0.data/purelib/crosswind_workers_patches.pth +1 -0
- crosswind_workers-0.1.0.dist-info/METADATA +9 -0
- crosswind_workers-0.1.0.dist-info/RECORD +35 -0
- crosswind_workers-0.1.0.dist-info/WHEEL +4 -0
- workers/__init__.py +88 -0
- workers/_adapter.py +24 -0
- workers/_body.py +34 -0
- workers/_js.py +121 -0
- workers/asgi.py +523 -0
- workers/bindings/__init__.py +36 -0
- workers/bindings/base.py +135 -0
- workers/bindings/d1.py +47 -0
- workers/bindings/env.py +50 -0
- workers/bindings/kv.py +121 -0
- workers/bindings/queues.py +36 -0
- workers/bindings/r2.py +130 -0
- workers/bindings/services.py +55 -0
- workers/bindings/workflow.py +64 -0
- workers/blob.py +158 -0
- workers/context.py +230 -0
- workers/entrypoints.py +183 -0
- workers/fetch.py +51 -0
- workers/formdata.py +92 -0
- workers/py.typed +0 -0
- workers/request.py +138 -0
- workers/response.py +181 -0
- workers/rpc.py +154 -0
- workers/types.py +57 -0
- workers/utils.py +55 -0
- workers/workflows.py +265 -0
- workers/wsgi.py +426 -0
workers/response.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Callable, Coroutine
|
|
3
|
+
from http import HTTPStatus
|
|
4
|
+
from typing import TYPE_CHECKING, Any, overload
|
|
5
|
+
|
|
6
|
+
import js
|
|
7
|
+
import pyodide.http
|
|
8
|
+
from pyodide.ffi import JsProxy
|
|
9
|
+
|
|
10
|
+
from ._body import BodyMixin
|
|
11
|
+
from ._js import js_body, js_name, none_if_null, python_errors, to_js_headers
|
|
12
|
+
from .blob import Blob
|
|
13
|
+
from .formdata import FormData
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .types import Body, Headers
|
|
17
|
+
|
|
18
|
+
__all__ = ["RESPONSE_ACCEPTED_TYPES", "FetchResponse", "Response"]
|
|
19
|
+
|
|
20
|
+
# The JS body types js.Response accepts.
|
|
21
|
+
RESPONSE_ACCEPTED_TYPES = {
|
|
22
|
+
"Blob",
|
|
23
|
+
"ArrayBuffer",
|
|
24
|
+
"TypedArray",
|
|
25
|
+
"DataView",
|
|
26
|
+
"Uint8Array",
|
|
27
|
+
"Uint8ClampedArray",
|
|
28
|
+
"Int8Array",
|
|
29
|
+
"Uint16Array",
|
|
30
|
+
"Int16Array",
|
|
31
|
+
"Uint32Array",
|
|
32
|
+
"Int32Array",
|
|
33
|
+
"Float16Array",
|
|
34
|
+
"Float32Array",
|
|
35
|
+
"Float64Array",
|
|
36
|
+
"BigInt64Array",
|
|
37
|
+
"BigUint64Array",
|
|
38
|
+
"FormData",
|
|
39
|
+
"ReadableStream",
|
|
40
|
+
"URLSearchParams",
|
|
41
|
+
"Response",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _Json:
|
|
46
|
+
"""Response.json(data) is from_json; response.json() parses the body.
|
|
47
|
+
|
|
48
|
+
The stock SDK does this inside one method, which a type checker can't
|
|
49
|
+
follow. A descriptor gives each use its own signature.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
def __get__(self, instance: None, owner: type) -> Callable[..., "Response"]: ...
|
|
54
|
+
@overload
|
|
55
|
+
def __get__(
|
|
56
|
+
self, instance: "FetchResponse", owner: type
|
|
57
|
+
) -> Callable[..., Coroutine[Any, Any, Any]]: ...
|
|
58
|
+
def __get__(self, instance: "FetchResponse | None", owner: type) -> Any:
|
|
59
|
+
if instance is None:
|
|
60
|
+
return _stock_json
|
|
61
|
+
return lambda *args, **kwargs: _stock_json(instance, *args, **kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _stock_json(data: Any, *args: Any, **kwargs: Any) -> Any:
|
|
65
|
+
# The stock SDK's one method: a Response's body parse, else from_json.
|
|
66
|
+
if isinstance(data, Response):
|
|
67
|
+
return pyodide.http.FetchResponse.json(data)
|
|
68
|
+
return FetchResponse.from_json(data, *args, **kwargs)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class FetchResponse(BodyMixin, pyodide.http.FetchResponse):
|
|
72
|
+
@property
|
|
73
|
+
def body(self) -> "js.ReadableStream | None":
|
|
74
|
+
return none_if_null(self.js_object.body)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def js_object(self) -> "js.Response":
|
|
78
|
+
return self.js_response
|
|
79
|
+
|
|
80
|
+
async def formData(self) -> FormData: # noqa: N802 (the stock SDK's alias)
|
|
81
|
+
return await self.form_data()
|
|
82
|
+
|
|
83
|
+
def replace_body(self, body: "Body") -> "Response":
|
|
84
|
+
"""A new Response with this one's status and headers and a new body."""
|
|
85
|
+
with js_body(body) as converted:
|
|
86
|
+
return Response(js.Response.new(converted, self.js_response))
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def redirect(url: str, status: HTTPStatus | int = HTTPStatus.FOUND) -> "Response":
|
|
90
|
+
# The stock SDK returns the raw JS Response here.
|
|
91
|
+
with python_errors():
|
|
92
|
+
return Response(js.Response.redirect(url, int(status)))
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def from_json(
|
|
96
|
+
data: Any,
|
|
97
|
+
status: HTTPStatus | int = HTTPStatus.OK,
|
|
98
|
+
status_text: str = "",
|
|
99
|
+
headers: "Headers | None" = None,
|
|
100
|
+
) -> "Response":
|
|
101
|
+
options = Response._create_options(status, status_text, headers)
|
|
102
|
+
|
|
103
|
+
with python_errors():
|
|
104
|
+
if isinstance(data, JsProxy):
|
|
105
|
+
return Response(js.Response.json(data, **options))
|
|
106
|
+
|
|
107
|
+
if "headers" not in options:
|
|
108
|
+
options["headers"] = to_js_headers({"content-type": "application/json"})
|
|
109
|
+
elif not options["headers"].has("content-type"):
|
|
110
|
+
options["headers"].set("content-type", "application/json")
|
|
111
|
+
|
|
112
|
+
return Response(js.Response.new(json.dumps(data), **options))
|
|
113
|
+
|
|
114
|
+
# A descriptor where Pyodide has a method, so ty reports the override.
|
|
115
|
+
json = _Json() # ty: ignore[invalid-attribute-override]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Response(FetchResponse):
|
|
119
|
+
"""An HTTP response, with an API like the web's Response."""
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
body: "Body" = None,
|
|
124
|
+
status: HTTPStatus | int | None = None,
|
|
125
|
+
status_text: str = "",
|
|
126
|
+
headers: "Headers | None" = None,
|
|
127
|
+
web_socket: "js.WebSocket | None" = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
name = js_name(body)
|
|
130
|
+
if name is not None:
|
|
131
|
+
if name not in RESPONSE_ACCEPTED_TYPES:
|
|
132
|
+
raise TypeError(f"Unsupported type in Response: {name}")
|
|
133
|
+
elif not isinstance(body, (str, bytes, FormData, Blob)) and body is not None:
|
|
134
|
+
# The stock SDK also rejects a Blob.
|
|
135
|
+
raise TypeError(f"Unsupported type in Response: {type(body).__name__}")
|
|
136
|
+
|
|
137
|
+
if name == "Response":
|
|
138
|
+
if status is not None or status_text or headers is not None:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
"Expected no options when constructing Response from a js.Response"
|
|
141
|
+
)
|
|
142
|
+
js_response: Any = body
|
|
143
|
+
super().__init__(js_response.url, js_response)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
options = self._create_options(status, status_text, headers, web_socket)
|
|
147
|
+
with js_body(body) as converted:
|
|
148
|
+
js_response = js.Response.new(converted, **options)
|
|
149
|
+
super().__init__(js_response.url, js_response)
|
|
150
|
+
|
|
151
|
+
def __repr__(self) -> str:
|
|
152
|
+
parts = [f"status={self.status}"]
|
|
153
|
+
if self.js_object.statusText:
|
|
154
|
+
parts.append(f"status_text={self.status_text!r}")
|
|
155
|
+
if "content-type" in self.headers:
|
|
156
|
+
parts.append(f"content_type={self.headers['content-type']!r}")
|
|
157
|
+
if self.js_object.url:
|
|
158
|
+
parts.append(f"url={self.js_object.url!r}")
|
|
159
|
+
if self.js_object.type != "default":
|
|
160
|
+
parts.append(f"type={self.js_object.type!r}")
|
|
161
|
+
|
|
162
|
+
return f"Response({', '.join(parts)})"
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def _create_options(
|
|
166
|
+
status: HTTPStatus | int | None = HTTPStatus.OK,
|
|
167
|
+
status_text: str = "",
|
|
168
|
+
headers: "Headers | None" = None,
|
|
169
|
+
web_socket: "js.WebSocket | None" = None,
|
|
170
|
+
) -> dict[str, Any]:
|
|
171
|
+
options: dict[str, Any] = {}
|
|
172
|
+
if status:
|
|
173
|
+
options["status"] = int(status)
|
|
174
|
+
if status_text:
|
|
175
|
+
options["statusText"] = status_text
|
|
176
|
+
if headers:
|
|
177
|
+
options["headers"] = to_js_headers(headers)
|
|
178
|
+
if web_socket:
|
|
179
|
+
options["webSocket"] = web_socket
|
|
180
|
+
|
|
181
|
+
return options
|
workers/rpc.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import inspect
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import js
|
|
6
|
+
from pyodide.ffi import create_proxy, to_js
|
|
7
|
+
|
|
8
|
+
from ._js import js_name, jsnull
|
|
9
|
+
from .blob import Blob, File
|
|
10
|
+
from .formdata import FormData
|
|
11
|
+
from .request import Request
|
|
12
|
+
from .response import Response
|
|
13
|
+
|
|
14
|
+
__all__ = ["JsDict", "python_from_rpc", "python_to_rpc"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def python_from_rpc(obj: Any) -> Any:
|
|
18
|
+
"""Convert a value workerd received over RPC to Python, as the stock SDK does."""
|
|
19
|
+
if obj is jsnull:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
name = js_name(obj)
|
|
23
|
+
if name is None:
|
|
24
|
+
return obj
|
|
25
|
+
if name == "TestController":
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
return _null_to_none(obj.to_py(default_converter=_default_from_js))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def python_to_rpc(value: Any) -> Any:
|
|
32
|
+
"""Convert a Python value to send over RPC, as the stock SDK does."""
|
|
33
|
+
value = _none_to_null(value)
|
|
34
|
+
if value is jsnull:
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
# to_js converts some containers without asking the default converter.
|
|
38
|
+
_refuse_unsendable(value)
|
|
39
|
+
return to_js(
|
|
40
|
+
value, default_converter=_default_to_js, dict_converter=js.Object.fromEntries
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class JsDict(dict):
|
|
45
|
+
"""A dict received over RPC, whose keys also read as attributes."""
|
|
46
|
+
|
|
47
|
+
def __getattr__(self, name: str) -> Any:
|
|
48
|
+
try:
|
|
49
|
+
return self[name]
|
|
50
|
+
except KeyError:
|
|
51
|
+
raise AttributeError(name) from None
|
|
52
|
+
|
|
53
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
54
|
+
self[name] = value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_PYTHON_TWINS = {
|
|
58
|
+
"Response": Response,
|
|
59
|
+
"Request": Request,
|
|
60
|
+
"FormData": FormData,
|
|
61
|
+
"Blob": Blob,
|
|
62
|
+
"File": File,
|
|
63
|
+
"Date": lambda date: datetime.datetime.fromtimestamp(date.getTime() / 1000),
|
|
64
|
+
"Error": lambda error: Exception(error.toString()),
|
|
65
|
+
"Number": lambda number: number.valueOf(),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _default_from_js(value: Any, converter: Any, cache: Any) -> Any:
|
|
70
|
+
if value is jsnull:
|
|
71
|
+
return None
|
|
72
|
+
twin = _PYTHON_TWINS.get(js_name(value) or "")
|
|
73
|
+
return twin(value) if twin else value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _null_to_none(value: Any) -> Any:
|
|
77
|
+
if value is jsnull:
|
|
78
|
+
return None
|
|
79
|
+
if isinstance(value, dict):
|
|
80
|
+
return JsDict({k: _null_to_none(v) for k, v in value.items()})
|
|
81
|
+
if isinstance(value, list):
|
|
82
|
+
return [_null_to_none(v) for v in value]
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _none_to_null(value: Any) -> Any:
|
|
87
|
+
if value is None:
|
|
88
|
+
return jsnull
|
|
89
|
+
if isinstance(value, dict):
|
|
90
|
+
return {k: _none_to_null(v) for k, v in value.items()}
|
|
91
|
+
if isinstance(value, list):
|
|
92
|
+
return [_none_to_null(v) for v in value]
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _refuse_unsendable(value: Any) -> None:
|
|
97
|
+
# A wrapper is sent as its JS object, and callables as proxies.
|
|
98
|
+
if hasattr(value, "js_object") or (callable(value) and not isinstance(value, type)):
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if js_name(value) == "RegExp":
|
|
102
|
+
raise TypeError(f"{value.constructor.name} cannot be sent over RPC.")
|
|
103
|
+
if isinstance(value, (tuple, bytearray)):
|
|
104
|
+
raise TypeError(f"{type(value)} cannot be sent over RPC.")
|
|
105
|
+
if inspect.isawaitable(value):
|
|
106
|
+
raise TypeError(f"Awaitable {type(value)} cannot be sent over RPC.")
|
|
107
|
+
if isinstance(value, (str, bytes)):
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
items = iter(value.values() if isinstance(value, dict) else value)
|
|
112
|
+
except TypeError:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Iterating an iterator such as a generator would empty it before it is
|
|
116
|
+
# sent, which the stock SDK does. A container gives a fresh iterator.
|
|
117
|
+
if items is value:
|
|
118
|
+
return
|
|
119
|
+
for item in items:
|
|
120
|
+
_refuse_unsendable(item)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _default_to_js(value: Any, converter: Any, cache: Any) -> Any:
|
|
124
|
+
if hasattr(value, "js_object"):
|
|
125
|
+
return value.js_object
|
|
126
|
+
if isinstance(value, datetime.datetime):
|
|
127
|
+
return js.Date.new(value.timestamp() * 1000)
|
|
128
|
+
if isinstance(value, Exception):
|
|
129
|
+
return js.Error.new(str(value))
|
|
130
|
+
if callable(value) and not isinstance(value, type):
|
|
131
|
+
# A persistent proxy, so the receiver can call it after this returns.
|
|
132
|
+
return create_proxy(value)
|
|
133
|
+
|
|
134
|
+
_refuse_unsendable(value)
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# The stock SDK's private wrapper names, which its own tests import from here,
|
|
139
|
+
# and the crosswind classes that play their parts.
|
|
140
|
+
_STOCK_PRIVATE_NAMES = {
|
|
141
|
+
"_BindingWrapper": "Binding",
|
|
142
|
+
"_EnvWrapper": "_EnvWrapper",
|
|
143
|
+
"_FetcherWrapper": "Fetcher",
|
|
144
|
+
"_RpcStubWrapper": "_RpcStub",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def __getattr__(name: str) -> Any:
|
|
149
|
+
# Served lazily, because bindings imports this module.
|
|
150
|
+
if name in _STOCK_PRIVATE_NAMES:
|
|
151
|
+
from . import bindings
|
|
152
|
+
|
|
153
|
+
return getattr(bindings, _STOCK_PRIVATE_NAMES[name])
|
|
154
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
workers/types.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from collections.abc import Awaitable
|
|
2
|
+
from http import HTTPMethod
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Protocol, TypedDict
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
import js
|
|
7
|
+
from pyodide.ffi import JsBuffer
|
|
8
|
+
|
|
9
|
+
from .blob import Blob
|
|
10
|
+
from .formdata import FormData
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Body",
|
|
14
|
+
"Context",
|
|
15
|
+
"FetchKwargs",
|
|
16
|
+
"Headers",
|
|
17
|
+
"JSBody",
|
|
18
|
+
"RequestInitCfProperties",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Context(Protocol):
|
|
23
|
+
def waitUntil(self, other: Awaitable[Any]) -> None: ... # noqa: N802
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
type JSBody = js.Blob | JsBuffer | js.FormData | js.ReadableStream | js.URLSearchParams
|
|
27
|
+
type Body = str | bytes | FormData | Blob | JSBody | None
|
|
28
|
+
type Headers = dict[str, str] | list[tuple[str, str]] | js.Headers
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RequestInitCfProperties(TypedDict, total=False):
|
|
32
|
+
"""https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties"""
|
|
33
|
+
|
|
34
|
+
apps: bool | None
|
|
35
|
+
cacheEverything: bool | None
|
|
36
|
+
cacheKey: str | None
|
|
37
|
+
cacheTags: list[str] | None
|
|
38
|
+
cacheTtl: int
|
|
39
|
+
cacheTtlByStatus: dict[str, int]
|
|
40
|
+
image: Any | None
|
|
41
|
+
mirage: bool | None
|
|
42
|
+
polish: str | None
|
|
43
|
+
resolveOverride: str | None
|
|
44
|
+
scrapeShield: bool | None
|
|
45
|
+
webp: bool | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FetchKwargs(TypedDict, total=False):
|
|
49
|
+
"""https://developers.cloudflare.com/workers/runtime-apis/request/#options"""
|
|
50
|
+
|
|
51
|
+
headers: "Headers | None"
|
|
52
|
+
body: "Body | None"
|
|
53
|
+
method: HTTPMethod | str | None
|
|
54
|
+
redirect: str | None
|
|
55
|
+
cf: RequestInitCfProperties | None
|
|
56
|
+
# A JS fetch function, such as a service binding's.
|
|
57
|
+
fetcher: Any
|
workers/utils.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Module-level helpers with the stock SDK's names and import paths."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, NoReturn
|
|
4
|
+
|
|
5
|
+
import _pyodide_entrypoint_helper
|
|
6
|
+
from pyodide.ffi import JsException
|
|
7
|
+
|
|
8
|
+
__all__ = ["import_from_javascript", "patch_env"]
|
|
9
|
+
|
|
10
|
+
# JS modules workerd imports ahead of time, by the helper attribute holding each.
|
|
11
|
+
_PRELOADED = {
|
|
12
|
+
"cloudflare:workers": "cloudflareWorkersModule",
|
|
13
|
+
"cloudflare:sockets": "cloudflareSocketsModule",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def import_from_javascript(module_name: str) -> Any:
|
|
18
|
+
"""Import a JS module, such as "cloudflare:sockets", from Python.
|
|
19
|
+
|
|
20
|
+
"cloudflare:workers" and "cloudflare:sockets" are imported ahead of time,
|
|
21
|
+
so they work everywhere. Any other module goes through run_sync, which
|
|
22
|
+
Python 3.12 Workers lack. On 3.13 and 3.14 it works at top level too.
|
|
23
|
+
"""
|
|
24
|
+
if module_name in _PRELOADED:
|
|
25
|
+
return getattr(_pyodide_entrypoint_helper, _PRELOADED[module_name])
|
|
26
|
+
|
|
27
|
+
failed = f"Failed to import '{module_name}'"
|
|
28
|
+
try:
|
|
29
|
+
from pyodide.ffi import run_sync
|
|
30
|
+
except ImportError as error:
|
|
31
|
+
raise ImportError(
|
|
32
|
+
f"{failed}: Only 'cloudflare:workers' and 'cloudflare:sockets' are "
|
|
33
|
+
"available until the next python runtime version."
|
|
34
|
+
) from error
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
return run_sync(_pyodide_entrypoint_helper.doAnImport(module_name))
|
|
38
|
+
except JsException as error:
|
|
39
|
+
raise ImportError(f"{failed}: {error}") from error
|
|
40
|
+
except RuntimeError as error:
|
|
41
|
+
if error.args and error.args[0] == "No suspender":
|
|
42
|
+
raise ImportError(
|
|
43
|
+
f"{failed}: Only 'cloudflare:workers' and 'cloudflare:sockets' "
|
|
44
|
+
"are available in the global scope."
|
|
45
|
+
) from error
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def patch_env(*args: Any, **kwargs: Any) -> NoReturn:
|
|
50
|
+
"""Removed from workerd. The stock SDK still exports it, so crosswind does."""
|
|
51
|
+
raise NotImplementedError(
|
|
52
|
+
"patch_env needs a workerd helper that workerd removed on 2026-06-30 "
|
|
53
|
+
"(commit 6581cf0), so it can't swap env. Pass the values you need "
|
|
54
|
+
"explicitly instead."
|
|
55
|
+
)
|