workers-runtime-sdk 1.3.0__tar.gz → 1.4.1__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 (17) hide show
  1. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/CHANGELOG.md +22 -0
  2. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/workers/_workers.py +71 -20
  5. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/.gitignore +0 -0
  6. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/AGENTS.md +0 -0
  7. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/README.md +0 -0
  8. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/_cloudflare_compat_flags.pyi +0 -0
  9. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/_pyodide_entrypoint_helper.pyi +0 -0
  10. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/_workers_sdk_entropy_import_context.pth +0 -0
  11. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/_workers_sdk_entropy_import_context.py +0 -0
  12. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/_workers_sdk_entropy_import_context_loader.py +0 -0
  13. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/asgi.py +0 -0
  14. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/workers/__init__.py +0 -0
  15. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/workers/py.typed +0 -0
  16. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/src/workers/workflows.py +0 -0
  17. {workers_runtime_sdk-1.3.0 → workers_runtime_sdk-1.4.1}/uv.lock +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.4.1 (2026-06-18)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Fix ReadableStream being incorrectly wrapped by BindingWrapper
10
+ ([#128](https://github.com/cloudflare/workers-py/pull/128),
11
+ [`85ad1f3`](https://github.com/cloudflare/workers-py/commit/85ad1f33d5f23fd932c0eac5cc5a9f7d39159423))
12
+
13
+
14
+ ## v1.4.0 (2026-06-17)
15
+
16
+ ### Features
17
+
18
+ - Auto-convert Python objects that are passed to/from Queue
19
+ ([#123](https://github.com/cloudflare/workers-py/pull/123),
20
+ [`906a10a`](https://github.com/cloudflare/workers-py/commit/906a10a7392f9d823a1b6bba044300ece8763401))
21
+
22
+ - Auto-convert Python objects that are passed to/from Queue Binding
23
+ ([#123](https://github.com/cloudflare/workers-py/pull/123),
24
+ [`906a10a`](https://github.com/cloudflare/workers-py/commit/906a10a7392f9d823a1b6bba044300ece8763401))
25
+
26
+
5
27
  ## v1.3.0 (2026-06-15)
6
28
 
7
29
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workers-runtime-sdk
3
- Version: 1.3.0
3
+ Version: 1.4.1
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.3.0"
7
+ version = "1.4.1"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -319,8 +319,13 @@ def _manage_pyproxies():
319
319
  destroy_proxies(proxies)
320
320
 
321
321
 
322
- def _is_js_instance(val, js_cls_name):
323
- return hasattr(val, "constructor") and val.constructor.name == js_cls_name
322
+ def _is_js_instance(val, js_cls_names: str | set[str]):
323
+ if not hasattr(val, "constructor"):
324
+ return False
325
+ name = val.constructor.name
326
+ if isinstance(js_cls_names, set):
327
+ return name in js_cls_names
328
+ return name == js_cls_names
324
329
 
325
330
 
326
331
  try:
@@ -387,6 +392,19 @@ RESPONSE_ACCEPTED_TYPES = {
387
392
  "Response",
388
393
  }
389
394
 
395
+ # JS built-in types that should NOT be wrapped in _BindingWrapper.
396
+ # These have their own Python-side semantics (e.g. passed directly to Response())
397
+ # and wrapping them breaks property access like `.constructor.name`.
398
+ _JS_PASSTHROUGH_TYPES = RESPONSE_ACCEPTED_TYPES | {
399
+ "Headers",
400
+ }
401
+
402
+
403
+ def _get_js_constructor_name(obj) -> str | None:
404
+ if hasattr(obj, "constructor"):
405
+ return obj.constructor.name
406
+ return None
407
+
390
408
 
391
409
  class Response(FetchResponse):
392
410
  """
@@ -409,11 +427,10 @@ class Response(FetchResponse):
409
427
  https://developer.mozilla.org/en-US/docs/Web/API/Response/Response.
410
428
  """
411
429
  # Verify passed in types.
412
- if hasattr(body, "constructor"):
413
- if body.constructor.name not in RESPONSE_ACCEPTED_TYPES:
414
- raise TypeError(
415
- f"Unsupported type in Response: {body.constructor.name}"
416
- )
430
+ js_type = _get_js_constructor_name(body)
431
+ if js_type:
432
+ if js_type not in RESPONSE_ACCEPTED_TYPES:
433
+ raise TypeError(f"Unsupported type in Response: {js_type}")
417
434
  elif not isinstance(body, str | FormData | bytes) and body is not None:
418
435
  raise TypeError(f"Unsupported type in Response: {type(body).__name__}")
419
436
 
@@ -1105,15 +1122,31 @@ class _BindingWrapper:
1105
1122
  def __init__(self, binding):
1106
1123
  self._binding = binding
1107
1124
 
1125
+ def _should_wrap_nested_attribute(self, jsobj) -> bool:
1126
+ if not isinstance(jsobj, JsProxy):
1127
+ return False
1128
+
1129
+ # TODO: This allowlist approach is a workaround. The long-term fix is to
1130
+ # add dedicated Python wrappers for these types in python_from_rpc so they
1131
+ # never reach _BindingWrapper in the first place.
1132
+ js_type = _get_js_constructor_name(jsobj)
1133
+ return js_type and js_type not in _JS_PASSTHROUGH_TYPES
1134
+
1108
1135
  def _convert_result(self, result):
1109
1136
  converted = python_from_rpc(result)
1110
- if isinstance(converted, JsProxy):
1111
- # If the RPC result is another JsProxy, we assume that
1112
- # it is another RPC-wrapped object and wrap it as well.
1113
- # for example, d1.bind() returns the same object as a result.
1114
- # TODO: This is a bit of a hack. We should revisit when there are more
1115
- # bindings to support with different patterns.
1137
+
1138
+ # After python_from_rpc, some objects may still be JsProxy objects.
1139
+ # We need to wrap them with _BindingWrapper (or a subclass of it) again
1140
+ # to ensure that accessing attributes on them will be properly converted.
1141
+ if self._should_wrap_nested_attribute(converted):
1116
1142
  return self.__class__(converted)
1143
+ if isinstance(converted, list):
1144
+ return [
1145
+ self.__class__(item)
1146
+ if self._should_wrap_nested_attribute(item)
1147
+ else item
1148
+ for item in converted
1149
+ ]
1117
1150
  return converted
1118
1151
 
1119
1152
  def _getattr_helper(self, name):
@@ -1252,6 +1285,13 @@ class _WorkflowBindingWrapper:
1252
1285
 
1253
1286
 
1254
1287
  class _EnvWrapper:
1288
+ _BINDING_TYPES = {
1289
+ "KvNamespace",
1290
+ "R2Bucket",
1291
+ "D1Database",
1292
+ "WorkerQueue",
1293
+ }
1294
+
1255
1295
  def __init__(self, env: Any):
1256
1296
  self._env = env
1257
1297
 
@@ -1266,13 +1306,7 @@ class _EnvWrapper:
1266
1306
  if _is_js_instance(binding, "WorkflowImpl"):
1267
1307
  return _WorkflowBindingWrapper(binding)
1268
1308
 
1269
- if _is_js_instance(binding, "KvNamespace"):
1270
- return _BindingWrapper(binding)
1271
-
1272
- if _is_js_instance(binding, "R2Bucket"):
1273
- return _BindingWrapper(binding)
1274
-
1275
- if _is_js_instance(binding, "D1Database"):
1309
+ if _is_js_instance(binding, self._BINDING_TYPES):
1276
1310
  return _BindingWrapper(binding)
1277
1311
 
1278
1312
  return binding
@@ -1550,6 +1584,7 @@ class WorkerEntrypoint:
1550
1584
 
1551
1585
  def __init_subclass__(cls, **_kwargs: Any):
1552
1586
  _wrap_subclass(cls)
1587
+ _wrap_queue_handler(cls)
1553
1588
 
1554
1589
 
1555
1590
  class WorkflowEntrypoint:
@@ -1567,3 +1602,19 @@ class WorkflowEntrypoint:
1567
1602
  def __init_subclass__(cls, **_kwargs: Any):
1568
1603
  _wrap_subclass(cls)
1569
1604
  _wrap_workflow_step(cls)
1605
+
1606
+
1607
+ def _wrap_queue_handler(cls):
1608
+ queue_fn = getattr(cls, "queue", None)
1609
+ if queue_fn is None:
1610
+ return
1611
+
1612
+ @functools.wraps(queue_fn)
1613
+ async def wrapped_queue(self, batch, *args, **kwargs):
1614
+ wrapped_batch = _BindingWrapper(batch)
1615
+ result = queue_fn(self, wrapped_batch, *args, **kwargs)
1616
+ if inspect.iscoroutine(result):
1617
+ result = await result
1618
+ return result
1619
+
1620
+ cls.queue = wrapped_queue