workers-runtime-sdk 1.6.11__tar.gz → 1.6.13__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 (169) hide show
  1. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/CHANGELOG.md +18 -0
  2. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/PKG-INFO +2 -2
  3. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/asgi.py +9 -6
  5. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +1 -0
  6. workers_runtime_sdk-1.6.13/tests/web-frameworks-test/fastapi-tests/src/_client.py +66 -0
  7. workers_runtime_sdk-1.6.13/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +128 -0
  8. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/worker.py +94 -2
  9. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi/tests/test_asgi.py +9 -0
  10. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi/worker.py +26 -0
  11. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +27 -0
  12. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi-ws-disconnect/worker.py +23 -1
  13. workers_runtime_sdk-1.6.13/tests/workerd-test/wsgi/pyproject.toml +5 -0
  14. workers_runtime_sdk-1.6.13/tests/workerd-test/wsgi/tests/test_wsgi.py +105 -0
  15. workers_runtime_sdk-1.6.13/tests/workerd-test/wsgi/worker.py +131 -0
  16. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/wsgi/wsgi.wd-test +4 -0
  17. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/uv.lock +1 -1
  18. workers_runtime_sdk-1.6.11/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -26
  19. workers_runtime_sdk-1.6.11/tests/workerd-test/wsgi/pyproject.toml +0 -5
  20. workers_runtime_sdk-1.6.11/tests/workerd-test/wsgi/worker.py +0 -205
  21. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/.gitignore +0 -0
  22. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/AGENTS.md +0 -0
  23. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/README.md +0 -0
  24. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/_cloudflare_compat_flags.pyi +0 -0
  25. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/_pyodide_entrypoint_helper.pyi +0 -0
  26. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/_workers_sdk_entropy_import_context.py +0 -0
  27. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/_workers_sdk_package_patches.py +0 -0
  28. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/_workers_sdk_patch_loader.pth +0 -0
  29. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/_workers_sdk_patch_loader.py +0 -0
  30. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/__init__.py +0 -0
  31. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/blob.py +0 -0
  32. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/entrypoints.py +0 -0
  33. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/fetch.py +0 -0
  34. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/formdata.py +0 -0
  35. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/py.typed +0 -0
  36. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/request.py +0 -0
  37. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/response.py +0 -0
  38. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/rpc.py +0 -0
  39. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/types.py +0 -0
  40. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/utils.py +0 -0
  41. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/workflows.py +0 -0
  42. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/src/workers/wsgi.py +0 -0
  43. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/public/data.json +0 -0
  44. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/public/hello.txt +0 -0
  45. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/pyproject.toml +0 -0
  46. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/conftest.py +0 -0
  47. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_analytics_engine.py +0 -0
  48. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_assets.py +0 -0
  49. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_d1.py +0 -0
  50. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_do.py +0 -0
  51. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_images.py +0 -0
  52. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_kv.py +0 -0
  53. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_mtls.py +0 -0
  54. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_queue.py +0 -0
  55. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_r2.py +0 -0
  56. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_ratelimit.py +0 -0
  57. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_vectorize.py +0 -0
  58. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_version_metadata.py +0 -0
  59. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/test_workflow.py +0 -0
  60. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/worker.py +0 -0
  61. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/worker_durable_object.py +0 -0
  62. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/src/worker_workflow.py +0 -0
  63. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/bindings-test/wrangler.jsonc +0 -0
  64. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/conftest.py +0 -0
  65. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/test_bindings.py +0 -0
  66. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/test_fastapi.py +0 -0
  67. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/test_in_workerd.py +0 -0
  68. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/test_web_frameworks.py +0 -0
  69. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
  70. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
  71. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
  72. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
  73. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
  74. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
  75. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
  76. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
  77. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
  78. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
  79. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
  80. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
  81. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
  82. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
  83. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
  84. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
  85. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
  86. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
  87. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
  88. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
  89. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
  90. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
  91. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
  92. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
  93. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
  94. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
  95. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
  96. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
  97. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
  98. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
  99. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
  100. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
  101. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
  102. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
  103. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
  104. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
  105. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
  106. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
  107. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
  108. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
  109. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
  110. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
  111. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
  112. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
  113. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
  114. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
  115. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
  116. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
  117. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
  118. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
  119. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
  120. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
  121. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
  122. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
  123. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
  124. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
  125. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi/asgi.wd-test +0 -0
  126. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi/pyproject.toml +0 -0
  127. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
  128. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
  129. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
  130. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
  131. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
  132. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object/pyproject.toml +0 -0
  133. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object/worker.py +0 -0
  134. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
  135. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
  136. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
  137. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-abort/worker.py +0 -0
  138. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
  139. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
  140. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
  141. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
  142. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
  143. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
  144. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
  145. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
  146. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
  147. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
  148. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
  149. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
  150. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
  151. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
  152. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
  153. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
  154. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
  155. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
  156. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/worker.py +0 -0
  157. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
  158. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
  159. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
  160. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/python-rpc/worker.js +0 -0
  161. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/python-rpc/worker.py +0 -0
  162. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
  163. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/sdk/pyproject.toml +0 -0
  164. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/sdk/sdk.wd-test +0 -0
  165. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/sdk/server.py +0 -0
  166. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
  167. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/sdk/worker.py +0 -0
  168. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
  169. {workers_runtime_sdk-1.6.11 → workers_runtime_sdk-1.6.13}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
@@ -2,6 +2,24 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.6.13 (2026-08-12)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **runtime-sdk**: Preserve repeated response headers (multiple `Set-Cookie`)
10
+ ([#167](https://github.com/cloudflare/workers-py/pull/167),
11
+ [`f68bf6d`](https://github.com/cloudflare/workers-py/commit/f68bf6d622c896b6c61fe3c615d43d6e9b05d19b))
12
+
13
+
14
+ ## v1.6.12 (2026-08-11)
15
+
16
+ ### Bug Fixes
17
+
18
+ - **runtime-sdk**: Send empty WebSocket frames instead of dropping them
19
+ ([#165](https://github.com/cloudflare/workers-py/pull/165),
20
+ [`a4128ce`](https://github.com/cloudflare/workers-py/commit/a4128cee402aa6f28f01e3e2268241d05d9fd393))
21
+
22
+
5
23
  ## v1.6.11 (2026-08-10)
6
24
 
7
25
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: workers-runtime-sdk
3
- Version: 1.6.11
3
+ Version: 1.6.13
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.11"
7
+ version = "1.6.13"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -8,6 +8,7 @@ from urllib.parse import unquote
8
8
  import js
9
9
 
10
10
  from workers import Context, Request
11
+ from workers.utils import _to_js_headers
11
12
 
12
13
  ASGI = {"spec_version": "2.0", "version": "3.0"}
13
14
  NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
@@ -179,7 +180,7 @@ async def process_request(
179
180
  # TODO(later): remove this parameter after unvendoring Python SDK from workerd
180
181
  ctx: Context | None,
181
182
  ) -> js.Response:
182
- from js import Object, Response, TransformStream
183
+ from js import Response, TransformStream
183
184
  from pyodide.ffi import create_proxy
184
185
 
185
186
  status = None
@@ -221,6 +222,8 @@ async def process_request(
221
222
  if got["type"] == "http.response.start":
222
223
  status = got["status"]
223
224
  # Like above, we need to convert byte-pairs into string explicitly.
225
+ # A response header name may repeat (multiple Set-Cookie), so this
226
+ # must stay a sequence of pairs, not a name-keyed mapping.
224
227
  headers = [(k.decode(), v.decode()) for k, v in got.get("headers", [])]
225
228
 
226
229
  elif got["type"] == "http.response.body":
@@ -243,7 +246,7 @@ async def process_request(
243
246
  readable = transform_stream.readable
244
247
  writer = transform_stream.writable.getWriter()
245
248
  resp = Response.new(
246
- readable, headers=Object.fromEntries(headers), status=status
249
+ readable, headers=_to_js_headers(headers), status=status
247
250
  )
248
251
  result.set_result(resp)
249
252
  with acquire_js_buffer(body) as jsbytes:
@@ -252,7 +255,7 @@ async def process_request(
252
255
  # 101/103/204/205/304 must not carry a body per the Fetch spec.
253
256
  # https://fetch.spec.whatwg.org/#null-body-status
254
257
  resp = Response.new(
255
- None, headers=Object.fromEntries(headers), status=status
258
+ None, headers=_to_js_headers(headers), status=status
256
259
  )
257
260
  result.set_result(resp)
258
261
  finished_response.set()
@@ -262,7 +265,7 @@ async def process_request(
262
265
  buf = px.getBuffer()
263
266
  px.destroy()
264
267
  resp = Response.new(
265
- buf.data, headers=Object.fromEntries(headers), status=status
268
+ buf.data, headers=_to_js_headers(headers), status=status
266
269
  )
267
270
  result.set_result(resp)
268
271
  finished_response.set()
@@ -352,12 +355,12 @@ async def process_websocket(app: Any, req: "Request | js.Request") -> js.Respons
352
355
  if got["type"] == "websocket.send":
353
356
  b = got.get("bytes", None)
354
357
  s = got.get("text", None)
355
- if b:
358
+ if b is not None:
356
359
  with acquire_js_buffer(b) as jsbytes:
357
360
  # Unlike the `Response` constructor, server.send seems to
358
361
  # eagerly copy the source buffer
359
362
  server.send(jsbytes)
360
- if s:
363
+ if s is not None:
361
364
  server.send(s)
362
365
 
363
366
  else:
@@ -4,6 +4,7 @@ version = "0.1.0"
4
4
  requires-python = ">=3.12"
5
5
  dependencies = [
6
6
  "fastapi",
7
+ "python-multipart",
7
8
  "pytest",
8
9
  "pytest-asyncio<1.2.0",
9
10
  ]
@@ -0,0 +1,66 @@
1
+ import json as _json
2
+
3
+ import asgi
4
+ from workers import Request
5
+
6
+ BASE_URL = "http://testserver"
7
+
8
+
9
+ def _with_content_length(headers, body):
10
+ """Ensure Content-Length is present when a body is provided."""
11
+ hdrs = dict(headers or {})
12
+ if body is not None and not any(k.lower() == "content-length" for k in hdrs):
13
+ length = len(body.encode() if isinstance(body, str) else body)
14
+ hdrs["Content-Length"] = str(length)
15
+ return hdrs
16
+
17
+
18
+ async def fetch(app, path, env=None, method="GET", headers=None, body=None):
19
+ request = Request(
20
+ f"{BASE_URL}{path}",
21
+ method=method,
22
+ headers=_with_content_length(headers, body),
23
+ body=body,
24
+ )
25
+ return await asgi.fetch(app, request, env or {})
26
+
27
+
28
+ async def read_json(response):
29
+ text = await response.text()
30
+ return _json.loads(text) if text else None
31
+
32
+
33
+ async def get_json(app, path, **kwargs):
34
+ response = await fetch(app, path, **kwargs)
35
+ return response, await read_json(response)
36
+
37
+
38
+ async def post_json(app, path, data, **kwargs):
39
+ return await fetch(
40
+ app,
41
+ path,
42
+ method="POST",
43
+ headers={"Content-Type": "application/json"},
44
+ body=_json.dumps(data),
45
+ **kwargs,
46
+ )
47
+
48
+
49
+ def build_multipart(files, boundary="----WebTestBoundary"):
50
+ """Build a multipart/form-data body from a list of (name, filename, content) tuples."""
51
+ lines = []
52
+ for name, filename, content in files:
53
+ if isinstance(content, bytes):
54
+ content = content.decode()
55
+ lines.append(f"--{boundary}")
56
+ lines.append(
57
+ f'Content-Disposition: form-data; name="{name}"; filename="{filename}"'
58
+ )
59
+ lines.append("Content-Type: application/octet-stream")
60
+ lines.append("")
61
+ lines.append(content)
62
+ lines.append(f"--{boundary}--")
63
+ lines.append("")
64
+ body = "\r\n".join(lines)
65
+ content_type = f"multipart/form-data; boundary={boundary}"
66
+ return body, content_type
@@ -0,0 +1,128 @@
1
+ """Tests for FastAPI features that rely on anyio.to_thread.run_sync.
2
+
3
+ The workers runtime is single-threaded, so ``anyio.to_thread.run_sync`` is
4
+ patched to run the callable inline. Without the patch every test in this file
5
+ would fail with ``RuntimeError: can't start new thread``.
6
+
7
+ Covered call-sites:
8
+ - Sync ``def`` route handlers (``starlette.routing`` / ``fastapi.routing``)
9
+ - Sync dependencies (``fastapi.dependencies.utils``)
10
+ - Sync ``BackgroundTask`` (``starlette.background``)
11
+ - Sync-iterator ``StreamingResponse`` (``starlette.concurrency.iterate_in_threadpool``)
12
+ - ``UploadFile.read`` (``starlette.datastructures``)
13
+ """
14
+
15
+ import pytest
16
+ from _client import build_multipart, fetch, get_json, read_json
17
+
18
+ # -- sync route handlers -----------------------------------------------------
19
+
20
+
21
+ @pytest.mark.asyncio
22
+ async def test_sync_handler_returns_json(fastapi_app):
23
+ """A plain ``def`` route handler returns a JSON response."""
24
+ resp, data = await get_json(fastapi_app, "/sync/hello")
25
+ assert resp.status == 200
26
+ assert data["message"] == "sync hello"
27
+
28
+
29
+ @pytest.mark.asyncio
30
+ async def test_sync_post_handler(fastapi_app):
31
+ """A sync POST handler receives the request and responds."""
32
+ resp = await fetch(fastapi_app, "/sync/echo", method="POST", body="hi")
33
+ assert resp.status == 200
34
+ data = await read_json(resp)
35
+ assert data["method"] == "POST"
36
+
37
+
38
+ # -- sync dependency ----------------------------------------------------------
39
+
40
+
41
+ @pytest.mark.asyncio
42
+ async def test_sync_dependency(fastapi_app):
43
+ """An async handler that depends on a sync ``def`` dependency."""
44
+ resp, data = await get_json(fastapi_app, "/sync/dep")
45
+ assert resp.status == 200
46
+ assert data["greeting"] == "hello from sync dep"
47
+
48
+
49
+ # -- sync background task -----------------------------------------------------
50
+
51
+
52
+ @pytest.mark.asyncio
53
+ async def test_sync_background_task(fastapi_app):
54
+ """A sync function passed to ``BackgroundTask`` runs to completion."""
55
+ resp, data = await get_json(fastapi_app, "/sync/background/run")
56
+ assert resp.status == 200
57
+ assert data["submitted"] is True
58
+
59
+ # The background task runs inline (patched), so by the time the response
60
+ # is fully sent the side-effect should already be visible.
61
+ resp2, data2 = await get_json(fastapi_app, "/sync/background/check")
62
+ assert resp2.status == 200
63
+ assert data2["bg_ran"] is True
64
+
65
+
66
+ # -- sync-iterator streaming response ----------------------------------------
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_sync_streaming_response(fastapi_app):
71
+ """StreamingResponse backed by a sync generator delivers all chunks."""
72
+ resp = await fetch(fastapi_app, "/sync/stream")
73
+ assert resp.status == 200
74
+ body = await resp.text()
75
+ for i in range(5):
76
+ assert f"chunk-{i}" in body
77
+
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_sync_streaming_content_type(fastapi_app):
81
+ """StreamingResponse preserves the declared media type."""
82
+ resp = await fetch(fastapi_app, "/sync/stream")
83
+ assert resp.status == 200
84
+ assert "text/plain" in resp.headers.get("content-type")
85
+
86
+
87
+ # -- file upload (UploadFile) -------------------------------------------------
88
+
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_single_file_upload(fastapi_app):
92
+ """A single file upload is received and its content is echoed back."""
93
+ body, content_type = build_multipart([("file", "greet.txt", "hello world")])
94
+ resp = await fetch(
95
+ fastapi_app,
96
+ "/upload/single",
97
+ method="POST",
98
+ headers={"Content-Type": content_type},
99
+ body=body,
100
+ )
101
+ assert resp.status == 200
102
+ data = await read_json(resp)
103
+ assert data["filename"] == "greet.txt"
104
+ assert data["size"] > 0
105
+ assert "hello world" in data["text"]
106
+
107
+
108
+ @pytest.mark.asyncio
109
+ async def test_multiple_file_upload(fastapi_app):
110
+ """Multiple files are received and their metadata is echoed back."""
111
+ body, content_type = build_multipart(
112
+ [
113
+ ("files", "one.txt", "first"),
114
+ ("files", "two.txt", "second"),
115
+ ]
116
+ )
117
+ resp = await fetch(
118
+ fastapi_app,
119
+ "/upload/multiple",
120
+ method="POST",
121
+ headers={"Content-Type": content_type},
122
+ body=body,
123
+ )
124
+ assert resp.status == 200
125
+ data = await read_json(resp)
126
+ assert len(data) == 2
127
+ assert [f["filename"] for f in data] == ["one.txt", "two.txt"]
128
+ assert all(f["size"] > 0 for f in data)
@@ -3,11 +3,12 @@ import importlib.util
3
3
  from pathlib import Path
4
4
 
5
5
  import pytest
6
- from fastapi import FastAPI, Request
7
- from fastapi.responses import FileResponse
6
+ from fastapi import Depends, FastAPI, File, Request, UploadFile
7
+ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
8
8
  from fastapi.responses import Response as FastAPIResponse
9
9
  from fastapi.staticfiles import StaticFiles
10
10
  from pyodide.webloop import WebLoop
11
+ from starlette.background import BackgroundTask
11
12
 
12
13
  import asgi
13
14
  from workers import Response, WorkerEntrypoint
@@ -24,6 +25,16 @@ STATIC_DIR = Path(__file__).parent / "static"
24
25
 
25
26
  app = FastAPI()
26
27
 
28
+ # --------------------------------------------------------------------------- #
29
+ # Shared mutable state used to verify side-effects (e.g. background tasks).
30
+ # --------------------------------------------------------------------------- #
31
+ _side_effects: dict = {}
32
+
33
+
34
+ # --------------------------------------------------------------------------- #
35
+ # Routes exercising the anyio.to_thread.run_sync patch
36
+ # --------------------------------------------------------------------------- #
37
+
27
38
 
28
39
  @app.get("/api/hello")
29
40
  async def api_hello():
@@ -35,6 +46,87 @@ async def health():
35
46
  return {"ok": True}
36
47
 
37
48
 
49
+ # -- sync route handler (dispatched via run_in_threadpool) -------------------
50
+ @app.get("/sync/hello")
51
+ def sync_hello():
52
+ """A plain `def` (non-async) route handler."""
53
+ return {"message": "sync hello"}
54
+
55
+
56
+ @app.post("/sync/echo")
57
+ def sync_echo(request: Request):
58
+ """Sync POST handler that reads the body."""
59
+ # request.body() is async; the sync handler can still return a dict.
60
+ return {"method": request.method}
61
+
62
+
63
+ # -- sync dependency ---------------------------------------------------------
64
+ def _get_greeting():
65
+ """A plain `def` dependency (not async)."""
66
+ return "hello from sync dep"
67
+
68
+
69
+ @app.get("/sync/dep")
70
+ async def sync_dep_route(greeting: str = Depends(_get_greeting)):
71
+ """Async handler with a sync dependency."""
72
+ return {"greeting": greeting}
73
+
74
+
75
+ # -- sync background task ----------------------------------------------------
76
+ def _bg_task():
77
+ """Sync function executed as a BackgroundTask."""
78
+ _side_effects["bg_ran"] = True
79
+
80
+
81
+ @app.get("/sync/background/run")
82
+ async def sync_background_run():
83
+ """Fire a sync background task and return immediately."""
84
+ _side_effects.pop("bg_ran", None)
85
+ return JSONResponse({"submitted": True}, background=BackgroundTask(_bg_task))
86
+
87
+
88
+ @app.get("/sync/background/check")
89
+ async def sync_background_check():
90
+ """Return whether the background task has executed."""
91
+ return {"bg_ran": _side_effects.get("bg_ran", False)}
92
+
93
+
94
+ # -- sync iterator streaming response ---------------------------------------
95
+ def _sync_chunks():
96
+ """A plain generator (not async) yielding text chunks."""
97
+ for i in range(5):
98
+ yield f"chunk-{i}\n"
99
+
100
+
101
+ @app.get("/sync/stream")
102
+ async def sync_stream():
103
+ """StreamingResponse backed by a sync iterator."""
104
+ return StreamingResponse(_sync_chunks(), media_type="text/plain")
105
+
106
+
107
+ # -- file upload (UploadFile.read/write/seek go through run_in_threadpool) ---
108
+ @app.post("/upload/single")
109
+ async def upload_single(file: UploadFile = File(...)): # noqa: B008
110
+ """Accept a single file upload and echo its metadata + content."""
111
+ content = await file.read()
112
+ return {
113
+ "filename": file.filename,
114
+ "content_type": file.content_type,
115
+ "size": len(content),
116
+ "text": content.decode(errors="replace"),
117
+ }
118
+
119
+
120
+ @app.post("/upload/multiple")
121
+ async def upload_multiple(files: list[UploadFile] = File(...)): # noqa: B008
122
+ """Accept multiple file uploads and echo their metadata."""
123
+ result = []
124
+ for f in files:
125
+ data = await f.read()
126
+ result.append({"filename": f.filename, "size": len(data)})
127
+ return result
128
+
129
+
38
130
  @app.get("/native-file")
39
131
  async def native_file():
40
132
  """Serve a single bundled file with FastAPI's native FileResponse."""
@@ -412,3 +412,12 @@ async def test_late_stream_failure_terminates_response():
412
412
  )
413
413
  body = await asyncio.wait_for(response.text(), timeout=5)
414
414
  assert body == "chunk-1"
415
+
416
+
417
+ @pytest.mark.asyncio
418
+ async def test_multiple_set_cookie_headers_survive():
419
+ response = await env.SELF.fetch("http://example.com/multi-cookie")
420
+ assert response.headers.get_all("set-cookie") == [
421
+ "first=1; Path=/",
422
+ "second=2; Path=/",
423
+ ]
@@ -233,11 +233,35 @@ class ScopeEchoApp:
233
233
  await send({"type": "http.response.body", "body": body})
234
234
 
235
235
 
236
+ class MultiCookieApp:
237
+ """Sends two Set-Cookie headers; both must reach the client."""
238
+
239
+ async def __call__(self, scope, receive, send):
240
+ if scope["type"] == "lifespan":
241
+ message = await receive()
242
+ if message["type"] == "lifespan.startup":
243
+ await send({"type": "lifespan.startup.complete"})
244
+ return
245
+ await receive()
246
+ await send(
247
+ {
248
+ "type": "http.response.start",
249
+ "status": 200,
250
+ "headers": [
251
+ (b"set-cookie", b"first=1; Path=/"),
252
+ (b"set-cookie", b"second=2; Path=/"),
253
+ ],
254
+ }
255
+ )
256
+ await send({"type": "http.response.body", "body": b"ok"})
257
+
258
+
236
259
  app = HeaderEchoApp()
237
260
  sse_app = SSEApp()
238
261
  streaming_app = StreamingApp()
239
262
  scope_echo_app = ScopeEchoApp()
240
263
  late_failure_stream_app = LateFailureStreamApp()
264
+ multi_cookie_app = MultiCookieApp()
241
265
 
242
266
  example_hdr = {"Header1": "Value1", "Header2": "Value2"}
243
267
 
@@ -259,6 +283,8 @@ class Default(WorkerEntrypoint):
259
283
  return await asgi.fetch(
260
284
  late_failure_stream_app, request, self.env, self.ctx
261
285
  )
286
+ elif path == "/multi-cookie":
287
+ return await asgi.fetch(multi_cookie_app, request, self.env, self.ctx)
262
288
 
263
289
  return await asgi.fetch(app, request, self.env, self.ctx)
264
290
 
@@ -26,6 +26,22 @@ def _listen(ws, event_type):
26
26
  return fut
27
27
 
28
28
 
29
+ def _collect(ws, count):
30
+ """Future resolved with the payloads of the first `count` messages."""
31
+ fut = asyncio.get_event_loop().create_future()
32
+ messages = []
33
+
34
+ def callback(evt):
35
+ messages.append(evt.data)
36
+ if len(messages) == count and not fut.done():
37
+ fut.set_result(messages)
38
+
39
+ proxy = create_proxy(callback)
40
+ _proxies.append(proxy)
41
+ ws.addEventListener("message", proxy)
42
+ return fut
43
+
44
+
29
45
  async def _ws_connect(path):
30
46
  # Upgrade through the raw JS binding (as durable-object-websocket's
31
47
  # tester.js does): the SDK fetch wrapper routes through pyfetch, whose
@@ -113,3 +129,14 @@ async def test_binary_frame_arrives_as_asgi_bytes():
113
129
  data = evt.data
114
130
  assert not isinstance(data, str), f"delivered to the app as text: {data!r}"
115
131
  assert data.to_bytes() == b"bytes=" + payload
132
+
133
+
134
+ @pytest.mark.asyncio
135
+ async def test_empty_frames_reach_the_client():
136
+ async with _ws_session("/ws-empty") as ws:
137
+ received = _collect(ws, 3)
138
+ ws.send("go")
139
+ messages = await asyncio.wait_for(received, TIMEOUT_S)
140
+ assert messages[0] == ""
141
+ assert messages[1].to_bytes() == b""
142
+ assert messages[2] == "done"
@@ -80,15 +80,37 @@ class WSEchoApp:
80
80
  await send({"type": "websocket.send", "text": reply})
81
81
 
82
82
 
83
+ class WSEmptyFrameApp:
84
+ """On each client frame, replies with an empty text frame, an empty binary
85
+ frame, and a sentinel, so a test can tell whether empty frames reach the
86
+ client at all."""
87
+
88
+ async def __call__(self, scope, receive, send):
89
+ if scope["type"] == "lifespan":
90
+ await _drain_lifespan(receive, send)
91
+ return
92
+ message = await receive()
93
+ assert message["type"] == "websocket.connect"
94
+ await send({"type": "websocket.accept"})
95
+ while True:
96
+ message = await receive()
97
+ if message["type"] == "websocket.disconnect":
98
+ return
99
+ await send({"type": "websocket.send", "text": ""})
100
+ await send({"type": "websocket.send", "bytes": b""})
101
+ await send({"type": "websocket.send", "text": "done"})
102
+
103
+
83
104
  ws_app = WSWatchApp()
84
105
  echo_app = WSEchoApp()
106
+ empty_frame_app = WSEmptyFrameApp()
85
107
 
86
108
 
87
109
  class Default(WorkerEntrypoint):
88
110
  async def fetch(self, request):
89
111
  if (request.headers.get("upgrade") or "").lower() == "websocket":
90
112
  path = urlsplit(request.url).path
91
- app = echo_app if path == "/ws-echo" else ws_app
113
+ app = {"/ws-echo": echo_app, "/ws-empty": empty_frame_app}.get(path, ws_app)
92
114
  return await asgi.websocket(app, request)
93
115
  import json
94
116
 
@@ -0,0 +1,5 @@
1
+ [project]
2
+ name = "test"
3
+ version = "0.0.0"
4
+ requires-python = ">=3.12"
5
+ dependencies = ["pytest", "pytest-asyncio<1.2.0"]