workers-runtime-sdk 1.6.14__tar.gz → 1.7.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 (175) hide show
  1. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/CHANGELOG.md +21 -0
  2. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/asgi.py +36 -9
  5. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/request.py +9 -3
  6. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/response.py +2 -19
  7. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/utils.py +20 -0
  8. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/_client.py +17 -0
  9. workers_runtime_sdk-1.7.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +31 -0
  10. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +9 -0
  11. workers_runtime_sdk-1.7.0/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +39 -0
  12. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/worker.py +65 -46
  13. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +19 -0
  14. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi-ws-disconnect/worker.py +43 -1
  15. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/sdk/tests/test_sdk.py +5 -0
  16. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/uv.lock +1 -1
  17. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/.gitignore +0 -0
  18. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/AGENTS.md +0 -0
  19. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/README.md +0 -0
  20. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/_cloudflare_compat_flags.pyi +0 -0
  21. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
  22. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/_workers_sdk_entropy_import_context.py +0 -0
  23. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/_workers_sdk_package_patches.py +0 -0
  24. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/_workers_sdk_patch_loader.pth +0 -0
  25. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/_workers_sdk_patch_loader.py +0 -0
  26. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/asgi.py +0 -0
  27. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/__init__.py +0 -0
  28. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/blob.py +0 -0
  29. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/entrypoints.py +0 -0
  30. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/fetch.py +0 -0
  31. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/formdata.py +0 -0
  32. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/py.typed +0 -0
  33. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/rpc.py +0 -0
  34. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/types.py +0 -0
  35. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/workflows.py +0 -0
  36. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/src/workers/wsgi.py +0 -0
  37. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/public/data.json +0 -0
  38. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/public/hello.txt +0 -0
  39. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/pyproject.toml +0 -0
  40. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/conftest.py +0 -0
  41. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_analytics_engine.py +0 -0
  42. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_assets.py +0 -0
  43. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_d1.py +0 -0
  44. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_do.py +0 -0
  45. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
  46. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
  47. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_images.py +0 -0
  48. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_kv.py +0 -0
  49. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_mtls.py +0 -0
  50. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_queue.py +0 -0
  51. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_r2.py +0 -0
  52. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_ratelimit.py +0 -0
  53. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_vectorize.py +0 -0
  54. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_version_metadata.py +0 -0
  55. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/test_workflow.py +0 -0
  56. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/worker.py +0 -0
  57. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/worker_durable_object.py +0 -0
  58. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/src/worker_workflow.py +0 -0
  59. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/bindings-test/wrangler.jsonc +0 -0
  60. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/conftest.py +0 -0
  61. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/test_bindings.py +0 -0
  62. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/test_fastapi.py +0 -0
  63. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/test_in_workerd.py +0 -0
  64. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/test_web_frameworks.py +0 -0
  65. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
  66. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
  67. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
  68. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
  69. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
  70. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
  71. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
  72. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
  73. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
  74. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
  75. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
  76. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
  77. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
  78. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
  79. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
  80. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
  81. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
  82. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
  83. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
  84. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
  85. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
  86. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
  87. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
  88. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
  89. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
  90. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
  91. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
  92. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
  93. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
  94. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
  95. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
  96. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
  97. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
  98. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
  99. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
  100. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
  101. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
  102. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
  103. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
  104. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
  105. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
  106. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
  107. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
  108. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
  109. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
  110. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
  111. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
  112. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
  113. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
  114. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
  115. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
  116. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
  117. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
  118. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
  119. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
  120. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
  121. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -0
  122. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
  123. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
  124. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
  125. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
  126. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi/asgi.wd-test +0 -0
  127. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi/pyproject.toml +0 -0
  128. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
  129. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi/worker.py +0 -0
  130. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
  131. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
  132. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
  133. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
  134. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
  135. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object/pyproject.toml +0 -0
  136. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object/worker.py +0 -0
  137. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
  138. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
  139. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
  140. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-abort/worker.py +0 -0
  141. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
  142. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
  143. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
  144. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
  145. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
  146. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
  147. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
  148. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
  149. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
  150. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
  151. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
  152. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
  153. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
  154. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
  155. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
  156. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
  157. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
  158. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
  159. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/worker.py +0 -0
  160. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
  161. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
  162. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
  163. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/python-rpc/worker.js +0 -0
  164. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/python-rpc/worker.py +0 -0
  165. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
  166. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/sdk/pyproject.toml +0 -0
  167. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/sdk/sdk.wd-test +0 -0
  168. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/sdk/server.py +0 -0
  169. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/sdk/worker.py +0 -0
  170. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
  171. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/wsgi/pyproject.toml +0 -0
  172. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/wsgi/tests/test_wsgi.py +0 -0
  173. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/wsgi/worker.py +0 -0
  174. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
  175. {workers_runtime_sdk-1.6.14 → workers_runtime_sdk-1.7.0}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
@@ -2,6 +2,27 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.7.0 (2026-08-24)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Fixes Request body being released too early
10
+ ([`84a20ac`](https://github.com/cloudflare/workers-py/commit/84a20acbfa28ea82fb69b2c279a56d9e25fd9639))
11
+
12
+ ### Features
13
+
14
+ - Asgi.entrypoint syntax sugar for nicer asgi apps
15
+ ([`5399270`](https://github.com/cloudflare/workers-py/commit/539927085a79e2d88a287cc70ccfa433ec257e1d))
16
+
17
+
18
+ ## v1.6.15 (2026-08-21)
19
+
20
+ ### Bug Fixes
21
+
22
+ - Handle ASGI WebSocket close events and environment propagation
23
+ ([`47cc488`](https://github.com/cloudflare/workers-py/commit/47cc488e2bad1c9f3030a16d73301b80fca90f5c))
24
+
25
+
5
26
  ## v1.6.14 (2026-08-19)
6
27
 
7
28
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: workers-runtime-sdk
3
- Version: 1.6.14
3
+ Version: 1.7.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.6.14"
7
+ version = "1.7.0"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,7 +7,7 @@ from urllib.parse import unquote
7
7
 
8
8
  import js
9
9
 
10
- from workers import Context, Request
10
+ from workers import Context, Request, WorkerEntrypoint
11
11
  from workers.utils import _to_js_headers
12
12
 
13
13
  ASGI = {"spec_version": "2.0", "version": "3.0"}
@@ -317,7 +317,9 @@ async def process_request( # noqa: PLR0913
317
317
  app_task.destroy()
318
318
 
319
319
 
320
- async def process_websocket(app: Any, req: "Request | js.Request") -> js.Response:
320
+ async def process_websocket(
321
+ app: Any, req: "Request | js.Request", env: Any = None
322
+ ) -> js.Response:
321
323
  from js import Response, WebSocketPair
322
324
 
323
325
  client, server = WebSocketPair.new().object_values()
@@ -358,6 +360,10 @@ async def process_websocket(app: Any, req: "Request | js.Request") -> js.Respons
358
360
  server.onmessage = onmessage
359
361
 
360
362
  async def ws_send(got):
363
+ if got["type"] == "websocket.accept":
364
+ # The Workers WebSocketPair is accepted before the upgrade response
365
+ # is returned, so there is no deferred accept operation here.
366
+ return
361
367
  if got["type"] == "websocket.send":
362
368
  b = got.get("bytes", None)
363
369
  s = got.get("text", None)
@@ -368,16 +374,23 @@ async def process_websocket(app: Any, req: "Request | js.Request") -> js.Respons
368
374
  server.send(jsbytes)
369
375
  if s is not None:
370
376
  server.send(s)
371
-
372
- else:
373
- logger.warning(" == Not implemented %s", got["type"])
377
+ return
378
+ if got["type"] == "websocket.close":
379
+ server.close(got.get("code", 1000), got.get("reason", ""))
380
+ return
381
+ logger.warning(" == Not implemented %s", got["type"])
374
382
 
375
383
  async def ws_receive():
376
384
  received = await queue.get()
377
385
  return received
378
386
 
379
- env = {}
380
- run_in_background(app(request_to_scope(req, env, ws=True), ws_receive, ws_send))
387
+ run_in_background(
388
+ app(
389
+ request_to_scope(req, env if env is not None else {}, ws=True),
390
+ ws_receive,
391
+ ws_send,
392
+ )
393
+ )
381
394
 
382
395
  return Response.new(None, status=101, webSocket=client)
383
396
 
@@ -396,8 +409,22 @@ async def fetch(
396
409
  return result
397
410
 
398
411
 
399
- async def websocket(app: Any, req: "Request | js.Request") -> js.Response:
400
- return await process_websocket(app, req)
412
+ async def websocket(
413
+ app: Any, req: "Request | js.Request", env: Any = None
414
+ ) -> js.Response:
415
+ return await process_websocket(app, req, env)
416
+
417
+
418
+ def entrypoint(app: Any) -> type[WorkerEntrypoint]:
419
+ """Create the default Worker entrypoint for an ASGI application."""
420
+
421
+ class Default(WorkerEntrypoint):
422
+ async def fetch(self, request):
423
+ if (request.headers.get("upgrade") or "").lower() == "websocket":
424
+ return await websocket(app, request, self.env)
425
+ return await fetch(app, request, self.env, self.ctx)
426
+
427
+ return Default
401
428
 
402
429
 
403
430
  def __getattr__(name):
@@ -9,6 +9,7 @@ from .blob import Blob
9
9
  from .formdata import FormData
10
10
  from .types import FetchKwargs
11
11
  from .utils import (
12
+ _get_js_body,
12
13
  _is_js_instance,
13
14
  _js_headers_to_http_message,
14
15
  _jsnull_to_none,
@@ -36,9 +37,14 @@ class Request:
36
37
 
37
38
  if "headers" in other_options:
38
39
  other_options["headers"] = _to_js_headers(other_options["headers"])
39
- self._js_request = js.Request.new(
40
- input._js_request if isinstance(input, Request) else input, **other_options
41
- )
40
+ body = other_options.pop("body", None)
41
+ with _get_js_body(body) as js_body:
42
+ if body is not None:
43
+ other_options["body"] = js_body
44
+ self._js_request = js.Request.new(
45
+ input._js_request if isinstance(input, Request) else input,
46
+ **other_options,
47
+ )
42
48
 
43
49
  def __repr__(self):
44
50
  return (
@@ -1,17 +1,17 @@
1
1
  import json
2
- from contextlib import contextmanager
3
2
  from http import HTTPStatus
4
3
  from typing import Any, Never
5
4
 
6
5
  import js
7
6
  import pyodide.http
8
- from pyodide.ffi import JsException, JsProxy, create_proxy
7
+ from pyodide.ffi import JsException, JsProxy
9
8
 
10
9
  from .blob import Blob
11
10
  from .formdata import FormData
12
11
  from .types import Body, Headers
13
12
  from .utils import (
14
13
  RESPONSE_ACCEPTED_TYPES,
14
+ _get_js_body,
15
15
  _get_js_constructor_name,
16
16
  _is_js_instance,
17
17
  _js_headers_to_http_message,
@@ -21,23 +21,6 @@ from .utils import (
21
21
  )
22
22
 
23
23
 
24
- @contextmanager
25
- def _get_js_body(body):
26
- if isinstance(body, bytes):
27
- proxy_bytes = create_proxy(body)
28
- proxy_buffer = proxy_bytes.getBuffer()
29
- try:
30
- yield proxy_buffer.data
31
- return
32
- finally:
33
- proxy_buffer.release()
34
- proxy_bytes.destroy()
35
- if isinstance(body, FormData):
36
- yield body.js_object
37
- return
38
- yield body
39
-
40
-
41
24
  class FetchResponse(pyodide.http.FetchResponse):
42
25
  # TODO: Consider upstreaming the `body` attribute
43
26
  # TODO: Behind a compat flag make this return a native stream (StreamReader?), or perhaps
@@ -6,6 +6,7 @@ import _pyodide_entrypoint_helper
6
6
  import js
7
7
  from pyodide.ffi import (
8
8
  JsException,
9
+ create_proxy,
9
10
  destroy_proxies,
10
11
  to_js,
11
12
  )
@@ -51,6 +52,25 @@ _JS_PASSTHROUGH_TYPES = RESPONSE_ACCEPTED_TYPES | {
51
52
  }
52
53
 
53
54
 
55
+ @contextmanager
56
+ def _get_js_body(body):
57
+ from .formdata import FormData
58
+
59
+ if isinstance(body, bytes):
60
+ proxy_bytes = create_proxy(body)
61
+ proxy_buffer = proxy_bytes.getBuffer()
62
+ try:
63
+ yield proxy_buffer.data
64
+ return
65
+ finally:
66
+ proxy_buffer.release()
67
+ proxy_bytes.destroy()
68
+ if isinstance(body, FormData):
69
+ yield body.js_object
70
+ return
71
+ yield body
72
+
73
+
54
74
  def _jsnull_to_none(x):
55
75
  if x is jsnull:
56
76
  return None
@@ -46,6 +46,23 @@ async def post_json(app, path, data, **kwargs):
46
46
  )
47
47
 
48
48
 
49
+ def build_multipart_bytes(files, boundary="----WebTestBoundary"):
50
+ """Build a binary-safe multipart body from (name, filename, bytes) tuples."""
51
+ body = bytearray()
52
+ for name, filename, content in files:
53
+ if isinstance(content, str):
54
+ content = content.encode()
55
+ body.extend(f"--{boundary}\r\n".encode())
56
+ body.extend(
57
+ f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'.encode()
58
+ )
59
+ body.extend(b"Content-Type: application/octet-stream\r\n\r\n")
60
+ body.extend(content)
61
+ body.extend(b"\r\n")
62
+ body.extend(f"--{boundary}--\r\n".encode())
63
+ return bytes(body), f"multipart/form-data; boundary={boundary}"
64
+
65
+
49
66
  def build_multipart(files, boundary="----WebTestBoundary"):
50
67
  """Build a multipart/form-data body from a list of (name, filename, content) tuples."""
51
68
  lines = []
@@ -0,0 +1,31 @@
1
+ """Binary I/O tests across FastAPI, Pyodide, and workerd."""
2
+
3
+ import hashlib
4
+
5
+ import pytest
6
+ from _client import build_multipart_bytes, fetch, read_json
7
+
8
+
9
+ @pytest.mark.asyncio
10
+ async def test_upload_spills_binary_file_to_virtual_disk(fastapi_app):
11
+ """UploadFile remains correct after SpooledTemporaryFile rolls to disk."""
12
+ payload = bytes(range(256)) * 5120 # 1.25 MiB, above Starlette's 1 MiB limit.
13
+ body, content_type = build_multipart_bytes(
14
+ [("file", "binary.dat", payload)], boundary="----SpoolBoundary"
15
+ )
16
+
17
+ resp = await fetch(
18
+ fastapi_app,
19
+ "/platform/upload-spooled",
20
+ method="POST",
21
+ headers={"Content-Type": content_type},
22
+ body=body,
23
+ )
24
+ assert resp.status == 200
25
+ data = await read_json(resp)
26
+ assert data == {
27
+ "size": len(payload),
28
+ "sha256": hashlib.sha256(payload).hexdigest(),
29
+ "reread_matches": True,
30
+ "rolled_to_disk": True,
31
+ }
@@ -20,6 +20,15 @@ These are relevant for Pyodide/Workers because:
20
20
 
21
21
  import pytest
22
22
  from _client import fetch, get_json, read_json
23
+ from worker import Default
24
+
25
+ from workers import WorkerEntrypoint
26
+
27
+
28
+ def test_asgi_entrypoint_factory():
29
+ assert Default.__name__ == "Default"
30
+ assert issubclass(Default, WorkerEntrypoint)
31
+
23
32
 
24
33
  # -- Path parameters ---------------------------------------------------------
25
34
 
@@ -0,0 +1,39 @@
1
+ import asyncio
2
+
3
+ import pytest
4
+ from pyodide.ffi import create_proxy
5
+ from worker import Default
6
+
7
+ from workers import Request
8
+
9
+ TIMEOUT_S = 5
10
+ _proxies = []
11
+
12
+
13
+ @pytest.mark.asyncio
14
+ async def test_generated_entrypoint_serves_fastapi_websocket():
15
+ entrypoint = object.__new__(Default)
16
+ entrypoint.env = {}
17
+ request = Request(
18
+ "http://testserver/websocket/echo", headers={"Upgrade": "websocket"}
19
+ )
20
+
21
+ response = await entrypoint.fetch(request)
22
+ assert response.status == 101
23
+
24
+ websocket = response.webSocket
25
+ websocket.accept()
26
+ message = asyncio.get_event_loop().create_future()
27
+
28
+ def onmessage(event):
29
+ if not message.done():
30
+ message.set_result(event.data)
31
+
32
+ proxy = create_proxy(onmessage)
33
+ _proxies.append(proxy)
34
+ websocket.addEventListener("message", proxy)
35
+ try:
36
+ websocket.send("hello")
37
+ assert await asyncio.wait_for(message, TIMEOUT_S) == "echo:hello"
38
+ finally:
39
+ websocket.close()
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import enum
3
+ import hashlib
3
4
  import importlib.util
4
5
  from contextlib import asynccontextmanager
5
6
  from pathlib import Path
@@ -15,6 +16,7 @@ from fastapi import (
15
16
  Query,
16
17
  Request,
17
18
  UploadFile,
19
+ WebSocket,
18
20
  )
19
21
  from fastapi.exceptions import HTTPException
20
22
  from fastapi.responses import (
@@ -32,7 +34,6 @@ from pyodide.webloop import WebLoop
32
34
  from starlette.background import BackgroundTask
33
35
 
34
36
  import asgi
35
- from workers import Response, WorkerEntrypoint
36
37
 
37
38
 
38
39
  async def _noop(*args):
@@ -82,6 +83,14 @@ async def health():
82
83
  return {"ok": True}
83
84
 
84
85
 
86
+ @app.websocket("/websocket/echo")
87
+ async def websocket_echo(websocket: WebSocket):
88
+ await websocket.accept()
89
+ message = await websocket.receive_text()
90
+ await websocket.send_text(f"echo:{message}")
91
+ await websocket.close()
92
+
93
+
85
94
  # -- sync route handler (dispatched via run_in_threadpool) -------------------
86
95
  @app.get("/sync/hello")
87
96
  def sync_hello():
@@ -436,6 +445,24 @@ async def platform_lifespan_state(request: Request):
436
445
  return {"available": True, "closed": resource.closed}
437
446
 
438
447
 
448
+ # --------------------------------------------------------------------------- #
449
+ # Pyodide and Workers platform boundary routes
450
+ # --------------------------------------------------------------------------- #
451
+
452
+
453
+ @app.post("/platform/upload-spooled")
454
+ async def platform_upload_spooled(file: UploadFile = File(...)): # noqa: B008
455
+ first = await file.read()
456
+ await file.seek(0)
457
+ second = await file.read()
458
+ return {
459
+ "size": len(first),
460
+ "sha256": hashlib.sha256(first).hexdigest(),
461
+ "reread_matches": first == second,
462
+ "rolled_to_disk": bool(getattr(file.file, "_rolled", False)),
463
+ }
464
+
465
+
439
466
  @app.get("/native-file")
440
467
  async def native_file():
441
468
  """Serve a single bundled file with FastAPI's native FileResponse."""
@@ -447,17 +474,6 @@ async def native_file():
447
474
  app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
448
475
 
449
476
 
450
- @app.get("/{path:path}")
451
- async def frontend(path: str, request: Request):
452
- """Proxy static asset requests to the Workers Assets binding."""
453
- env = request.scope["env"]
454
- asset_url = f"https://assets.local/{path}"
455
- resp = await env.ASSETS.fetch(asset_url)
456
- body = await resp.bytes()
457
- headers = dict(resp.headers)
458
- return FastAPIResponse(content=body, status_code=resp.status, headers=headers)
459
-
460
-
461
477
  class ResultCollector:
462
478
  def __init__(self):
463
479
  self.results = {}
@@ -524,37 +540,40 @@ class FastAPIAppPlugin:
524
540
  return app
525
541
 
526
542
 
527
- class Default(WorkerEntrypoint):
528
- async def fetch(self, request):
529
- from urllib.parse import urlparse
530
-
531
- path = urlparse(request.url).path
532
-
533
- if path.startswith("/run-tests/"):
534
- suite_name = path[len("/run-tests/") :]
535
- return self._run_suite(suite_name)
536
-
537
- return await asgi.fetch(app, request, self.env, self.ctx)
538
-
539
- def _run_suite(self, suite_name):
540
- module = f"test_{suite_name}"
541
- if importlib.util.find_spec(module) is None:
542
- return Response.json(
543
- {"error": f"Unknown suite '{suite_name}' (no module '{module}')"},
544
- status=404,
545
- )
546
-
547
- collector = ResultCollector()
548
- saved_loop = asyncio.events._get_running_loop()
549
- try:
550
- pytest.main(
551
- ["--pyargs", module, "-p", "no:cacheprovider"],
552
- plugins=[
553
- collector,
554
- EnvPlugin(self.env),
555
- FastAPIAppPlugin(),
556
- ],
557
- )
558
- finally:
559
- asyncio.events._set_running_loop(saved_loop)
560
- return Response.json(collector.results)
543
+ @app.get("/run-tests/{suite_name:path}")
544
+ async def run_suite(suite_name: str, request: Request):
545
+ module = f"test_{suite_name}"
546
+ if importlib.util.find_spec(module) is None:
547
+ return JSONResponse(
548
+ {"error": f"Unknown suite '{suite_name}' (no module '{module}')"},
549
+ status_code=404,
550
+ )
551
+
552
+ collector = ResultCollector()
553
+ saved_loop = asyncio.events._get_running_loop()
554
+ try:
555
+ pytest.main(
556
+ ["--pyargs", module, "-p", "no:cacheprovider"],
557
+ plugins=[
558
+ collector,
559
+ EnvPlugin(request.scope["env"]),
560
+ FastAPIAppPlugin(),
561
+ ],
562
+ )
563
+ finally:
564
+ asyncio.events._set_running_loop(saved_loop)
565
+ return collector.results
566
+
567
+
568
+ @app.get("/{path:path}")
569
+ async def frontend(path: str, request: Request):
570
+ """Proxy static asset requests to the Workers Assets binding."""
571
+ env = request.scope["env"]
572
+ asset_url = f"https://assets.local/{path}"
573
+ resp = await env.ASSETS.fetch(asset_url)
574
+ body = await resp.bytes()
575
+ headers = dict(resp.headers)
576
+ return FastAPIResponse(content=body, status_code=resp.status, headers=headers)
577
+
578
+
579
+ Default = asgi.entrypoint(app)
@@ -140,3 +140,22 @@ async def test_empty_frames_reach_the_client():
140
140
  assert messages[0] == ""
141
141
  assert messages[1].to_bytes() == b""
142
142
  assert messages[2] == "done"
143
+
144
+
145
+ @pytest.mark.asyncio
146
+ async def test_application_close_reaches_client_with_code_and_reason():
147
+ async with _ws_session("/ws-close") as ws:
148
+ closed = _listen(ws, "close")
149
+ ws.send("close")
150
+ event = await asyncio.wait_for(closed, TIMEOUT_S)
151
+ assert event.code == 4001
152
+ assert event.reason == "application-close"
153
+
154
+
155
+ @pytest.mark.asyncio
156
+ async def test_environment_reaches_websocket_scope():
157
+ async with _ws_session("/ws-env") as ws:
158
+ message = _listen(ws, "message")
159
+ ws.send("env")
160
+ event = await asyncio.wait_for(message, TIMEOUT_S)
161
+ assert event.data == "worker-environment"
@@ -101,16 +101,58 @@ class WSEmptyFrameApp:
101
101
  await send({"type": "websocket.send", "text": "done"})
102
102
 
103
103
 
104
+ class WSCloseApp:
105
+ """Closes the connection with an application-provided code and reason."""
106
+
107
+ async def __call__(self, scope, receive, send):
108
+ message = await receive()
109
+ assert message["type"] == "websocket.connect"
110
+ await send({"type": "websocket.accept"})
111
+ message = await receive()
112
+ if message["type"] == "websocket.disconnect":
113
+ return
114
+ await send(
115
+ {
116
+ "type": "websocket.close",
117
+ "code": 4001,
118
+ "reason": "application-close",
119
+ }
120
+ )
121
+
122
+
123
+ class WSEnvApp:
124
+ """Sends a value supplied through the ASGI WebSocket scope environment."""
125
+
126
+ async def __call__(self, scope, receive, send):
127
+ message = await receive()
128
+ assert message["type"] == "websocket.connect"
129
+ await send({"type": "websocket.accept"})
130
+ message = await receive()
131
+ if message["type"] == "websocket.disconnect":
132
+ return
133
+ await send({"type": "websocket.send", "text": scope["env"]["marker"]})
134
+
135
+
104
136
  ws_app = WSWatchApp()
105
137
  echo_app = WSEchoApp()
106
138
  empty_frame_app = WSEmptyFrameApp()
139
+ close_app = WSCloseApp()
140
+ env_app = WSEnvApp()
107
141
 
108
142
 
109
143
  class Default(WorkerEntrypoint):
110
144
  async def fetch(self, request):
111
145
  if (request.headers.get("upgrade") or "").lower() == "websocket":
112
146
  path = urlsplit(request.url).path
113
- app = {"/ws-echo": echo_app, "/ws-empty": empty_frame_app}.get(path, ws_app)
147
+ if path == "/ws-env":
148
+ return await asgi.websocket(
149
+ env_app, request, {"marker": "worker-environment"}
150
+ )
151
+ app = {
152
+ "/ws-close": close_app,
153
+ "/ws-echo": echo_app,
154
+ "/ws-empty": empty_frame_app,
155
+ }.get(path, ws_app)
114
156
  return await asgi.websocket(app, request)
115
157
  import json
116
158
 
@@ -436,6 +436,11 @@ async def test_request_unit_tests():
436
436
  blob = await req_for_blob.blob()
437
437
  assert (await blob.text()) == "foobar"
438
438
 
439
+ # Verify that Python bytes are converted without UTF-8 decoding or truncation.
440
+ binary_body = bytes(range(256))
441
+ req_for_bytes = Request("http://example.com", body=binary_body, method="POST")
442
+ assert await req_for_bytes.bytes() == binary_body
443
+
439
444
  # Verify that we can get a FormData back.
440
445
  js_form_data = js.FormData.new()
441
446
  js_form_data.append("foobar", 123)
@@ -345,7 +345,7 @@ wheels = [
345
345
 
346
346
  [[package]]
347
347
  name = "workers-runtime-sdk"
348
- version = "1.6.14"
348
+ version = "1.7.0"
349
349
  source = { editable = "." }
350
350
 
351
351
  [package.dev-dependencies]