workers-runtime-sdk 1.6.0__tar.gz → 1.6.2__tar.gz

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.
Files changed (26) hide show
  1. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/CHANGELOG.md +17 -0
  2. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/_workers_sdk_entropy_import_context.py +1 -0
  5. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/asgi.py +79 -27
  6. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/uv.lock +1 -1
  7. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/.gitignore +0 -0
  8. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/AGENTS.md +0 -0
  9. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/README.md +0 -0
  10. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/_cloudflare_compat_flags.pyi +0 -0
  11. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/_pyodide_entrypoint_helper.pyi +0 -0
  12. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/_workers_sdk_entropy_import_context.pth +0 -0
  13. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/_workers_sdk_entropy_import_context_loader.py +0 -0
  14. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/__init__.py +0 -0
  15. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/_workers.py +0 -0
  16. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/blob.py +0 -0
  17. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/fetch.py +0 -0
  18. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/formdata.py +0 -0
  19. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/py.typed +0 -0
  20. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/request.py +0 -0
  21. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/response.py +0 -0
  22. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/rpc.py +0 -0
  23. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/types.py +0 -0
  24. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/utils.py +0 -0
  25. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/workflows.py +0 -0
  26. {workers_runtime_sdk-1.6.0 → workers_runtime_sdk-1.6.2}/src/workers/wsgi.py +0 -0
@@ -2,6 +2,23 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.6.2 (2026-07-09)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Uuid_utils is a Rust package needs rust package import context
10
+ ([#151](https://github.com/cloudflare/workers-py/pull/151),
11
+ [`61e91f3`](https://github.com/cloudflare/workers-py/commit/61e91f3b3cffc26fef411c67d018b7d5728df3c4))
12
+
13
+
14
+ ## v1.6.1 (2026-07-09)
15
+
16
+ ### Bug Fixes
17
+
18
+ - Improve lifespan handling in asgi.py ([#148](https://github.com/cloudflare/workers-py/pull/148),
19
+ [`7c9adfc`](https://github.com/cloudflare/workers-py/commit/7c9adfce6c3997f1547901755e97c685ef6e6be3))
20
+
21
+
5
22
  ## v1.6.0 (2026-07-08)
6
23
 
7
24
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workers-runtime-sdk
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: Python SDK for Cloudflare Workers
5
5
  Project-URL: Homepage, https://github.com/cloudflare/workers-py
6
6
  Project-URL: Bug Tracker, https://github.com/cloudflare/workers-py/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "workers-runtime-sdk"
7
- version = "1.6.0"
7
+ version = "1.6.2"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -25,6 +25,7 @@ class STATE:
25
25
  @register_create_patch("tiktoken._tiktoken")
26
26
  @register_exec_patch("cryptography.exceptions")
27
27
  @register_exec_patch("jiter")
28
+ @register_exec_patch("uuid_utils._uuid_utils")
28
29
  @contextmanager
29
30
  def rust_package_context(module):
30
31
  """Rust packages need one entropy call if they create a rust hash map at
@@ -1,8 +1,7 @@
1
1
  import logging
2
- from asyncio import Event, Future, Queue, create_task, ensure_future, sleep
2
+ from asyncio import Event, Future, Queue, create_task, ensure_future
3
3
  from collections.abc import Awaitable
4
4
  from contextlib import contextmanager
5
- from inspect import isawaitable
6
5
  from typing import Any
7
6
 
8
7
  import js
@@ -10,6 +9,7 @@ import js
10
9
  from workers import Context, Request
11
10
 
12
11
  ASGI = {"spec_version": "2.0", "version": "3.0"}
12
+ NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
13
13
  logger = logging.getLogger("asgi")
14
14
  background_tasks = set()
15
15
 
@@ -76,42 +76,86 @@ def request_to_scope(req, env, ws=False):
76
76
 
77
77
 
78
78
  async def start_application(app):
79
- shutdown_future = Future()
79
+ # Drives one ASGI lifespan startup/shutdown cycle before/after serving the
80
+ # request. The lifespan protocol is optional, so we must tolerate apps that
81
+ # don't implement it and fall back to serving requests without lifespan.
82
+ # https://asgi.readthedocs.io/en/latest/specs/lifespan.html
83
+ receive_queue = Queue()
84
+ await receive_queue.put({"type": "lifespan.startup"})
85
+
86
+ # `startup` resolves True on `lifespan.startup.complete`, False when the app
87
+ # has no lifespan support, and raises on `lifespan.startup.failed`.
88
+ # `shutdown_complete` mirrors the shutdown phase.
89
+ startup = Future()
90
+ shutdown_complete = Future()
80
91
 
81
92
  async def shutdown():
82
- shutdown_future.set_result(None)
83
- await sleep(0)
93
+ # Nothing to shut down if the app never completed a lifespan startup.
94
+ if startup.done() and not startup.result():
95
+ return
96
+ await receive_queue.put({"type": "lifespan.shutdown"})
97
+ await shutdown_complete
84
98
 
85
- it = iter([{"type": "lifespan.startup"}, Future()])
99
+ async def no_lifespan_shutdown():
100
+ return
86
101
 
87
102
  async def receive():
88
- res = next(it)
89
- if isawaitable(res):
90
- await res
91
- return res
92
-
93
- ready = Future()
103
+ return await receive_queue.get()
94
104
 
95
105
  async def send(got):
96
106
  if got["type"] == "lifespan.startup.complete":
97
- ready.set_result(None)
107
+ if not startup.done():
108
+ startup.set_result(True)
109
+ return
110
+ if got["type"] == "lifespan.startup.failed":
111
+ message = got.get("message", "ASGI lifespan startup failed")
112
+ if not startup.done():
113
+ startup.set_exception(RuntimeError(message))
98
114
  return
99
115
  if got["type"] == "lifespan.shutdown.complete":
116
+ if not shutdown_complete.done():
117
+ shutdown_complete.set_result(None)
118
+ return
119
+ if got["type"] == "lifespan.shutdown.failed":
120
+ message = got.get("message", "ASGI lifespan shutdown failed")
121
+ if not shutdown_complete.done():
122
+ shutdown_complete.set_exception(RuntimeError(message))
100
123
  return
101
124
  raise RuntimeError(f"Unexpected lifespan event {got['type']}")
102
125
 
103
- run_in_background(
104
- app(
105
- {
106
- "asgi": ASGI,
107
- "state": {},
108
- "type": "lifespan",
109
- },
110
- receive,
111
- send,
112
- )
113
- )
114
- await ready
126
+ async def run_lifespan():
127
+ try:
128
+ await app(
129
+ {
130
+ "asgi": ASGI,
131
+ "state": {},
132
+ "type": "lifespan",
133
+ },
134
+ receive,
135
+ send,
136
+ )
137
+ # App returned without acking: a missing startup ack means it has no
138
+ # lifespan handler, so mark startup unsupported rather than hanging.
139
+ if not startup.done():
140
+ startup.set_result(False)
141
+ elif not shutdown_complete.done():
142
+ shutdown_complete.set_result(None)
143
+ except Exception as exc:
144
+ # Spec: an exception raised before startup is acked signals that the
145
+ # app doesn't support lifespan; swallow it and serve requests anyway.
146
+ if not startup.done():
147
+ startup.set_result(False)
148
+ return
149
+ # After a successful startup, a shutdown-phase error can't affect the
150
+ # already-served request, so log it and let shutdown complete.
151
+ logger.exception("Exception in ASGI lifespan application", exc_info=exc)
152
+ if not shutdown_complete.done():
153
+ shutdown_complete.set_result(None)
154
+
155
+ run_in_background(run_lifespan())
156
+ supported = await startup
157
+ if not supported:
158
+ return no_lifespan_shutdown
115
159
  return shutdown
116
160
 
117
161
 
@@ -163,10 +207,10 @@ async def process_request(
163
207
  if got["type"] == "http.response.start":
164
208
  status = got["status"]
165
209
  # Like above, we need to convert byte-pairs into string explicitly.
166
- headers = [(k.decode(), v.decode()) for k, v in got["headers"]]
210
+ headers = [(k.decode(), v.decode()) for k, v in got.get("headers", [])]
167
211
 
168
212
  elif got["type"] == "http.response.body":
169
- body = got["body"]
213
+ body = got.get("body", b"")
170
214
  more_body = got.get("more_body", False)
171
215
 
172
216
  if writer is not None:
@@ -189,6 +233,14 @@ async def process_request(
189
233
  result.set_result(resp)
190
234
  with acquire_js_buffer(body) as jsbytes:
191
235
  await writer.write(jsbytes.slice())
236
+ elif status in NULL_BODY_STATUSES:
237
+ # 101/103/204/205/304 must not carry a body per the Fetch spec.
238
+ # https://fetch.spec.whatwg.org/#null-body-status
239
+ resp = Response.new(
240
+ None, headers=Object.fromEntries(headers), status=status
241
+ )
242
+ result.set_result(resp)
243
+ finished_response.set()
192
244
  else:
193
245
  # Complete body in a single chunk
194
246
  px = create_proxy(body)
@@ -229,7 +229,7 @@ wheels = [
229
229
 
230
230
  [[package]]
231
231
  name = "workers-runtime-sdk"
232
- version = "1.6.0"
232
+ version = "1.6.2"
233
233
  source = { editable = "." }
234
234
 
235
235
  [package.dev-dependencies]