workers-runtime-sdk 1.1.6__tar.gz → 1.3.0__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.1.6 → workers_runtime_sdk-1.3.0}/CHANGELOG.md +25 -0
  2. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/workers/_workers.py +128 -13
  5. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/.gitignore +0 -0
  6. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/AGENTS.md +0 -0
  7. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/README.md +0 -0
  8. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/_cloudflare_compat_flags.pyi +0 -0
  9. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
  10. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/_workers_sdk_entropy_import_context.pth +0 -0
  11. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/_workers_sdk_entropy_import_context.py +0 -0
  12. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/_workers_sdk_entropy_import_context_loader.py +0 -0
  13. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/asgi.py +0 -0
  14. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/workers/__init__.py +0 -0
  15. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/workers/py.typed +0 -0
  16. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/src/workers/workflows.py +0 -0
  17. {workers_runtime_sdk-1.1.6 → workers_runtime_sdk-1.3.0}/uv.lock +0 -0
@@ -2,6 +2,31 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.3.0 (2026-06-15)
6
+
7
+ ### Features
8
+
9
+ - **runtime-sdk**: Revise type conversion for Durable Object binding
10
+ ([#112](https://github.com/cloudflare/workers-py/pull/112),
11
+ [`b12650e`](https://github.com/cloudflare/workers-py/commit/b12650ef91bb71f4ebebd9827bad2d1f0946fd62))
12
+
13
+ - **runtime-sdk**: Revise type conversion to support bindings more natively
14
+ ([#112](https://github.com/cloudflare/workers-py/pull/112),
15
+ [`b12650e`](https://github.com/cloudflare/workers-py/commit/b12650ef91bb71f4ebebd9827bad2d1f0946fd62))
16
+
17
+ - **runtime-sdk**: Update js object conversion logic to support cloudflare bindings more natively.
18
+ ([#112](https://github.com/cloudflare/workers-py/pull/112),
19
+ [`b12650e`](https://github.com/cloudflare/workers-py/commit/b12650ef91bb71f4ebebd9827bad2d1f0946fd62))
20
+
21
+
22
+ ## v1.2.0 (2026-06-12)
23
+
24
+ ### Features
25
+
26
+ - Implements cf accessor on Request
27
+ ([`5777f80`](https://github.com/cloudflare/workers-py/commit/5777f80ead8d9a3c452fe3b6b8f2dc041d6c80d3))
28
+
29
+
5
30
  ## v1.1.6 (2026-05-28)
6
31
 
7
32
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workers-runtime-sdk
3
- Version: 1.1.6
3
+ Version: 1.3.0
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.1.6"
7
+ version = "1.3.0"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -17,7 +17,6 @@ from collections.abc import (
17
17
  from contextlib import ExitStack, contextmanager
18
18
  from enum import StrEnum
19
19
  from http import HTTPMethod, HTTPStatus
20
- from types import LambdaType
21
20
  from typing import TYPE_CHECKING, Any, Never, Protocol, TypedDict, Unpack
22
21
 
23
22
  import _cloudflare_compat_flags
@@ -769,6 +768,20 @@ class Request:
769
768
  def body_used(self) -> bool:
770
769
  return self.js_object.bodyUsed
771
770
 
771
+ @property
772
+ def cf(self) -> "JsProxy | None":
773
+ """
774
+ Cloudflare-specific properties about the incoming request
775
+ (IncomingRequestCfProperties). Access fields via attribute
776
+ notation, for example ``request.cf.colo``.
777
+
778
+ Returns None when not present (for example, in the dashboard/playground
779
+ preview or for requests constructed without a ``cf`` value).
780
+
781
+ See https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties
782
+ """
783
+ return _jsnull_to_none(self.js_object.cf)
784
+
772
785
  @property
773
786
  def cache(self) -> str:
774
787
  return self.js_object.cache
@@ -902,6 +915,9 @@ class Request:
902
915
 
903
916
 
904
917
  def _python_from_rpc_default_converter(value, convert, cache):
918
+ if value is jsnull:
919
+ return None
920
+
905
921
  if not hasattr(value, "constructor"):
906
922
  # Assume that the object doesn't need conversion as it's not a JS object.
907
923
  return value
@@ -933,6 +949,41 @@ def _python_from_rpc_default_converter(value, convert, cache):
933
949
  return value
934
950
 
935
951
 
952
+ class JsDict(dict):
953
+ """
954
+ Python dictionary that allows attribute access to keys.
955
+
956
+ This is used to convert JS objects to Python dictionaries while maintaining
957
+ the ability to access keys as attributes.
958
+ """
959
+
960
+ def __getattr__(self, name):
961
+ # The limitation of this approach is that if there is a key that conflicts with a built-in
962
+ # method or attribute of the dict class, it will not be accessible through attribute access.
963
+ # But that is a reasonable trade-off for the convenience of being able to access keys as
964
+ # attributes.
965
+ try:
966
+ return self[name]
967
+ except KeyError:
968
+ raise AttributeError(name) from None
969
+
970
+ def __setattr__(self, name, value):
971
+ self[name] = value
972
+
973
+
974
+ def _replace_jsnull_with_none(obj):
975
+ """
976
+ Recursively converts JS objects to Python objects.
977
+ """
978
+ if obj is jsnull:
979
+ return None
980
+ if isinstance(obj, dict):
981
+ return JsDict({k: _replace_jsnull_with_none(v) for k, v in obj.items()})
982
+ if isinstance(obj, list):
983
+ return [_replace_jsnull_with_none(v) for v in obj]
984
+ return obj
985
+
986
+
936
987
  def python_from_rpc(obj: "JsProxy"):
937
988
  """
938
989
  Converts JS objects like Response, Request, Blob, etc. to equivalent Python objects defined in
@@ -942,6 +993,9 @@ def python_from_rpc(obj: "JsProxy"):
942
993
  it does not support serializing all JS object types.
943
994
  """
944
995
 
996
+ if obj is jsnull:
997
+ return None
998
+
945
999
  if not hasattr(obj, "constructor"):
946
1000
  return obj
947
1001
 
@@ -952,14 +1006,20 @@ def python_from_rpc(obj: "JsProxy"):
952
1006
 
953
1007
  result = obj.to_py(default_converter=_python_from_rpc_default_converter)
954
1008
 
955
- return result
1009
+ return _replace_jsnull_with_none(result)
956
1010
 
957
1011
 
958
1012
  def _raise_on_disabled_type(value):
1013
+ if isinstance(value, _BindingWrapper):
1014
+ return
1015
+
1016
+ if callable(value) and not isinstance(value, type):
1017
+ return
1018
+
959
1019
  if _is_js_instance(value, "RegExp"):
960
1020
  raise TypeError(f"{value.constructor.name} cannot be sent over RPC.")
961
1021
 
962
- if isinstance(value, (tuple, bytearray, LambdaType)):
1022
+ if isinstance(value, (tuple, bytearray)):
963
1023
  raise TypeError(f"{type(value)} cannot be sent over RPC.")
964
1024
 
965
1025
  if inspect.isawaitable(value):
@@ -977,7 +1037,10 @@ def _raise_on_disabled_type(value):
977
1037
 
978
1038
  def _python_to_rpc_default_converter(obj, convert, cache):
979
1039
  if obj is None:
980
- return obj
1040
+ return jsnull
1041
+
1042
+ if isinstance(obj, _BindingWrapper):
1043
+ return obj._binding
981
1044
 
982
1045
  if hasattr(obj, "js_object"):
983
1046
  return obj.js_object
@@ -989,11 +1052,26 @@ def _python_to_rpc_default_converter(obj, convert, cache):
989
1052
  if isinstance(obj, Exception):
990
1053
  return js.Error.new(str(obj))
991
1054
 
1055
+ if callable(obj) and not isinstance(obj, type):
1056
+ # Wrap function with create_proxy so that
1057
+ # it doesn't get garbage collected
1058
+ return create_proxy(obj)
1059
+
992
1060
  _raise_on_disabled_type(obj)
993
1061
 
994
1062
  return obj
995
1063
 
996
1064
 
1065
+ def _replace_none_with_jsnull(value):
1066
+ if value is None:
1067
+ return jsnull
1068
+ if isinstance(value, dict):
1069
+ return {k: _replace_none_with_jsnull(v) for k, v in value.items()}
1070
+ if isinstance(value, list):
1071
+ return [_replace_none_with_jsnull(v) for v in value]
1072
+ return value
1073
+
1074
+
997
1075
  def python_to_rpc(value) -> JsProxy:
998
1076
  """
999
1077
  Converts Python objects defined in this module (Response, Request, etc) and native Python types
@@ -1003,37 +1081,58 @@ def python_to_rpc(value) -> JsProxy:
1003
1081
  it does not support serializing all Python object types.
1004
1082
  """
1005
1083
 
1084
+ if value is None:
1085
+ return jsnull
1086
+
1087
+ if isinstance(value, _BindingWrapper):
1088
+ return value._binding
1089
+
1090
+ value = _replace_none_with_jsnull(value)
1091
+
1006
1092
  # `to_js` won't always call the default_converter, for example when a list of tuples is passed
1007
1093
  _raise_on_disabled_type(value)
1008
1094
 
1009
1095
  result = to_js(
1010
1096
  value,
1011
1097
  default_converter=_python_to_rpc_default_converter,
1012
- dict_converter=js.Map.new,
1098
+ dict_converter=Object.fromEntries,
1013
1099
  )
1014
1100
 
1015
1101
  return result
1016
1102
 
1017
1103
 
1018
- class _FetcherWrapper:
1104
+ class _BindingWrapper:
1019
1105
  def __init__(self, binding):
1020
1106
  self._binding = binding
1021
1107
 
1108
+ def _convert_result(self, result):
1109
+ 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.
1116
+ return self.__class__(converted)
1117
+ return converted
1118
+
1022
1119
  def _getattr_helper(self, name):
1023
1120
  attr = getattr(self._binding, name)
1024
1121
 
1025
1122
  if not callable(attr):
1026
- return attr
1123
+ return self._convert_result(attr)
1027
1124
 
1028
- # Not using `@functools.wraps(attr)` here because `attr` is a JS proxy.
1029
- async def wrapper(*args, **kwargs):
1125
+ def wrapper(*args, **kwargs):
1030
1126
  js_args = [python_to_rpc(arg) for arg in args]
1031
1127
  js_kwargs = {k: python_to_rpc(v) for k, v in kwargs.items()}
1032
1128
  result = attr(*js_args, **js_kwargs)
1033
1129
  if hasattr(result, "then") and callable(result.then):
1034
- return python_from_rpc(await result)
1035
- else:
1036
- return python_from_rpc(result)
1130
+
1131
+ async def await_and_convert():
1132
+ return self._convert_result(await result)
1133
+
1134
+ return await_and_convert()
1135
+ return self._convert_result(result)
1037
1136
 
1038
1137
  return wrapper
1039
1138
 
@@ -1042,6 +1141,11 @@ class _FetcherWrapper:
1042
1141
  setattr(self, name, result)
1043
1142
  return result
1044
1143
 
1144
+ def __getitem__(self, key):
1145
+ return self._convert_result(getattr(self._binding, key))
1146
+
1147
+
1148
+ class _FetcherWrapper(_BindingWrapper):
1045
1149
  def fetch(self, *args, **kwargs):
1046
1150
  return fetch(*args, fetcher=self._binding.fetch, **kwargs)
1047
1151
 
@@ -1075,6 +1179,9 @@ class DurableObjectContext:
1075
1179
 
1076
1180
  def __getattr__(self, name: str):
1077
1181
  result = getattr(self._ctx, name)
1182
+ if _is_js_instance(result, "DurableObjectStorage"):
1183
+ # durable_object.ctx.storage
1184
+ result = _BindingWrapper(result)
1078
1185
  setattr(self, name, result)
1079
1186
  return result
1080
1187
 
@@ -1159,7 +1266,15 @@ class _EnvWrapper:
1159
1266
  if _is_js_instance(binding, "WorkflowImpl"):
1160
1267
  return _WorkflowBindingWrapper(binding)
1161
1268
 
1162
- # TODO: Implement APIs for bindings.
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"):
1276
+ return _BindingWrapper(binding)
1277
+
1163
1278
  return binding
1164
1279
 
1165
1280
  def __getattr__(self, name):