workers-runtime-sdk 1.7.0__tar.gz → 1.8.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 (177) hide show
  1. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/CHANGELOG.md +22 -0
  2. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/asgi.py +59 -14
  5. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/wsgi.py +14 -3
  6. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/_client.py +32 -0
  7. workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +99 -0
  8. workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +46 -0
  9. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/worker.py +111 -0
  10. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +8 -0
  11. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/worker.py +15 -0
  12. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/tests/test_wsgi.py +19 -3
  13. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/worker.py +21 -9
  14. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/uv.lock +1 -1
  15. workers_runtime_sdk-1.7.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -12
  16. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/.gitignore +0 -0
  17. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/AGENTS.md +0 -0
  18. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/README.md +0 -0
  19. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_cloudflare_compat_flags.pyi +0 -0
  20. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
  21. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_entropy_import_context.py +0 -0
  22. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_package_patches.py +0 -0
  23. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.pth +0 -0
  24. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.py +0 -0
  25. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/asgi.py +0 -0
  26. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/__init__.py +0 -0
  27. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/blob.py +0 -0
  28. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/entrypoints.py +0 -0
  29. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/fetch.py +0 -0
  30. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/formdata.py +0 -0
  31. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/py.typed +0 -0
  32. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/request.py +0 -0
  33. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/response.py +0 -0
  34. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/rpc.py +0 -0
  35. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/types.py +0 -0
  36. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/utils.py +0 -0
  37. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/workflows.py +0 -0
  38. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/data.json +0 -0
  39. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/hello.txt +0 -0
  40. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/pyproject.toml +0 -0
  41. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/conftest.py +0 -0
  42. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_analytics_engine.py +0 -0
  43. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_assets.py +0 -0
  44. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_d1.py +0 -0
  45. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_do.py +0 -0
  46. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
  47. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
  48. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_images.py +0 -0
  49. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_kv.py +0 -0
  50. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_mtls.py +0 -0
  51. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_queue.py +0 -0
  52. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_r2.py +0 -0
  53. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_ratelimit.py +0 -0
  54. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_vectorize.py +0 -0
  55. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_version_metadata.py +0 -0
  56. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_workflow.py +0 -0
  57. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker.py +0 -0
  58. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_durable_object.py +0 -0
  59. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_workflow.py +0 -0
  60. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/wrangler.jsonc +0 -0
  61. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/conftest.py +0 -0
  62. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_bindings.py +0 -0
  63. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_fastapi.py +0 -0
  64. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_in_workerd.py +0 -0
  65. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_web_frameworks.py +0 -0
  66. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
  67. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
  68. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
  69. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
  70. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
  71. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
  72. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
  73. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
  74. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
  75. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
  76. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
  77. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
  78. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
  79. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
  80. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
  81. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
  82. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
  83. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
  84. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
  85. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
  86. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
  87. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
  88. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
  89. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
  90. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
  91. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
  92. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
  93. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
  94. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
  95. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
  96. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
  97. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
  98. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
  99. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
  100. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
  101. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
  102. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
  103. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
  104. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
  105. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
  106. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
  107. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
  108. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
  109. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
  110. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
  111. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
  112. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
  113. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
  114. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
  115. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
  116. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
  117. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
  118. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
  119. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
  120. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
  121. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
  122. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
  123. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
  124. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
  125. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
  126. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
  127. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
  128. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
  129. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/asgi.wd-test +0 -0
  130. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/pyproject.toml +0 -0
  131. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
  132. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/worker.py +0 -0
  133. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
  134. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
  135. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
  136. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
  137. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
  138. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/pyproject.toml +0 -0
  139. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/worker.py +0 -0
  140. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
  141. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
  142. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
  143. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/worker.py +0 -0
  144. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
  145. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
  146. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
  147. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
  148. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
  149. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
  150. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
  151. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
  152. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
  153. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
  154. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
  155. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
  156. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
  157. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
  158. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
  159. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
  160. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
  161. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
  162. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/worker.py +0 -0
  163. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
  164. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
  165. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
  166. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.js +0 -0
  167. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.py +0 -0
  168. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
  169. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/pyproject.toml +0 -0
  170. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/sdk.wd-test +0 -0
  171. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/server.py +0 -0
  172. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
  173. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/worker.py +0 -0
  174. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
  175. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/pyproject.toml +0 -0
  176. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
  177. {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.8.0 (2026-08-28)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Defer ASGI lifespan shutdown until request completion
10
+ ([`3249184`](https://github.com/cloudflare/workers-py/commit/324918442748a0b87affc6128315ceb190e1b368))
11
+
12
+ - Make wsgi streaming response handlers async
13
+ ([#191](https://github.com/cloudflare/workers-py/pull/191),
14
+ [`12168ae`](https://github.com/cloudflare/workers-py/commit/12168aef7599067a910a518f1ce7860a00ae9a58))
15
+
16
+ - **runtime-sdk**: Close the WebSocket transport when the app task ends
17
+ ([#166](https://github.com/cloudflare/workers-py/pull/166),
18
+ [`e67e7e7`](https://github.com/cloudflare/workers-py/commit/e67e7e78af7fa6976f1f9966508b9869a6840ce5))
19
+
20
+ ### Features
21
+
22
+ - Add wsgi.entrypoint like asgi.entrypoint
23
+ ([#232](https://github.com/cloudflare/workers-py/pull/232),
24
+ [`e044561`](https://github.com/cloudflare/workers-py/commit/e04456174b3f5bc9df8b3f8293ced6d08aaffc0f))
25
+
26
+
5
27
  ## v1.7.0 (2026-08-24)
6
28
 
7
29
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: workers-runtime-sdk
3
- Version: 1.7.0
3
+ Version: 1.8.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.7.0"
7
+ version = "1.8.0"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -16,7 +16,7 @@ logger = logging.getLogger("asgi")
16
16
  background_tasks = set()
17
17
 
18
18
 
19
- def run_in_background(coro: Awaitable[Any]) -> None:
19
+ def run_in_background(coro: Awaitable[Any]) -> Future:
20
20
  fut = ensure_future(coro)
21
21
  background_tasks.add(fut)
22
22
 
@@ -27,6 +27,7 @@ def run_in_background(coro: Awaitable[Any]) -> None:
27
27
  logger.error("Unhandled exception in background task", exc_info=exc)
28
28
 
29
29
  fut.add_done_callback(_on_done)
30
+ return fut
30
31
 
31
32
 
32
33
  async def close_stream_quietly(writer):
@@ -185,7 +186,7 @@ async def process_request( # noqa: PLR0913
185
186
  # TODO(later): remove this parameter after unvendoring Python SDK from workerd
186
187
  ctx: Context | None,
187
188
  state: dict[str, Any] | None = None,
188
- ) -> js.Response:
189
+ ) -> tuple[js.Response, Future[None]]:
189
190
  from js import Response, TransformStream
190
191
  from pyodide.ffi import create_proxy
191
192
 
@@ -304,17 +305,22 @@ async def process_request( # noqa: PLR0913
304
305
  # runtime is waiting on through wait_until.
305
306
  run_in_background(close_stream_quietly(writer))
306
307
 
307
- # Create task to run the application in the background
308
- app_task = create_proxy(create_task(run_app()))
309
-
310
- from workers import wait_until
311
-
312
- wait_until(app_task)
308
+ request_task = create_task(run_app())
313
309
 
314
310
  try:
315
- return await result
316
- finally:
317
- app_task.destroy()
311
+ response = await result
312
+ except Exception:
313
+ # The request task handles application exceptions and should be allowed
314
+ # to finish before its error is propagated to fetch().
315
+ await request_task
316
+ raise
317
+
318
+ # Buffered responses do not depend on a consumer, so request cleanup can
319
+ # finish before returning. Streaming responses must run alongside the
320
+ # consumer because writes can be subject to backpressure.
321
+ if writer is None:
322
+ await request_task
323
+ return response, request_task
318
324
 
319
325
 
320
326
  async def process_websocket(
@@ -359,7 +365,10 @@ async def process_websocket(
359
365
  server.onclose = onclose
360
366
  server.onmessage = onmessage
361
367
 
368
+ app_closed = False
369
+
362
370
  async def ws_send(got):
371
+ nonlocal app_closed
363
372
  if got["type"] == "websocket.accept":
364
373
  # The Workers WebSocketPair is accepted before the upgrade response
365
374
  # is returned, so there is no deferred accept operation here.
@@ -376,6 +385,7 @@ async def process_websocket(
376
385
  server.send(s)
377
386
  return
378
387
  if got["type"] == "websocket.close":
388
+ app_closed = True
379
389
  server.close(got.get("code", 1000), got.get("reason", ""))
380
390
  return
381
391
  logger.warning(" == Not implemented %s", got["type"])
@@ -384,13 +394,34 @@ async def process_websocket(
384
394
  received = await queue.get()
385
395
  return received
386
396
 
387
- run_in_background(
397
+ from pyodide.ffi import create_proxy
398
+
399
+ from workers import wait_until
400
+
401
+ task = run_in_background(
388
402
  app(
389
403
  request_to_scope(req, env if env is not None else {}, ws=True),
390
404
  ws_receive,
391
405
  ws_send,
392
406
  )
393
407
  )
408
+ # wait_until is JS's waitUntil, so the task crosses the boundary as a proxy,
409
+ # destroyed once it settles rather than left to leak.
410
+ task_proxy = create_proxy(task)
411
+
412
+ def _close_transport(finished):
413
+ if not app_closed:
414
+ # The app ended without closing, so a half-open connection would
415
+ # leave the client hanging instead of reconnecting.
416
+ failed = not finished.cancelled() and finished.exception() is not None
417
+ try:
418
+ server.close(1011 if failed else 1000, "")
419
+ except Exception:
420
+ pass # the peer may already have closed the socket
421
+ task_proxy.destroy()
422
+
423
+ task.add_done_callback(_close_transport)
424
+ wait_until(task_proxy)
394
425
 
395
426
  return Response.new(None, status=101, webSocket=client)
396
427
 
@@ -401,11 +432,25 @@ async def fetch(
401
432
  logger.debug("ASGI request: %s %s", req.method, req.url)
402
433
  shutdown, state = await start_application(app)
403
434
  try:
404
- result = await process_request(app, req, env, ctx, state=state)
435
+ result, request_task = await process_request(app, req, env, ctx, state=state)
405
436
  except Exception:
406
437
  logger.exception("ASGI request failed")
438
+ await shutdown()
407
439
  raise
408
- await shutdown()
440
+
441
+ if request_task.done():
442
+ await shutdown()
443
+ else:
444
+ from workers import wait_until # noqa: PLC0415
445
+
446
+ async def finalize_request():
447
+ try:
448
+ await request_task
449
+ finally:
450
+ await shutdown()
451
+
452
+ wait_until(run_in_background(finalize_request()))
453
+
409
454
  return result
410
455
 
411
456
 
@@ -7,7 +7,7 @@ from urllib.parse import unquote, urlsplit
7
7
 
8
8
  import js
9
9
 
10
- from workers import Context, Request
10
+ from workers import Context, Request, WorkerEntrypoint
11
11
 
12
12
  logger = logging.getLogger("wsgi")
13
13
  NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
@@ -262,8 +262,9 @@ def _make_streaming_response(
262
262
  for proxy in proxies:
263
263
  proxy.destroy()
264
264
 
265
+ # Make proxies async so that it is possible to stack switch inside them.
265
266
  @create_proxy
266
- def pull(controller: Any) -> None:
267
+ async def pull(controller: Any) -> None:
267
268
  try:
268
269
  chunk = next(chunks, _END)
269
270
  except Exception as exc: # noqa: BLE001 - forward app errors to the stream
@@ -278,7 +279,7 @@ def _make_streaming_response(
278
279
  controller.enqueue(_to_js_uint8array(chunk))
279
280
 
280
281
  @create_proxy
281
- def cancel(_reason: Any = None) -> None:
282
+ async def cancel(_reason: Any = None) -> None:
282
283
  cleanup()
283
284
 
284
285
  proxies = [pull, cancel]
@@ -378,3 +379,13 @@ async def fetch(
378
379
  except Exception:
379
380
  logger.exception("WSGI request failed")
380
381
  raise
382
+
383
+
384
+ def entrypoint(app: Any) -> type[WorkerEntrypoint]:
385
+ """Create the default Worker entrypoint for a WSGI application."""
386
+
387
+ class Default(WorkerEntrypoint):
388
+ async def fetch(self, request):
389
+ return await fetch(app, request, self.env)
390
+
391
+ return Default
@@ -1,5 +1,8 @@
1
1
  import json as _json
2
2
 
3
+ import js
4
+ from pyodide.ffi import create_proxy, to_js
5
+
3
6
  import asgi
4
7
  from workers import Request
5
8
 
@@ -63,6 +66,35 @@ def build_multipart_bytes(files, boundary="----WebTestBoundary"):
63
66
  return bytes(body), f"multipart/form-data; boundary={boundary}"
64
67
 
65
68
 
69
+ async def fetch_chunks(app, path, chunks, env=None, method="POST", headers=None):
70
+ """Send a request body as a JS ReadableStream with explicit chunk boundaries."""
71
+ from js import Object
72
+
73
+ chunks = list(chunks)
74
+
75
+ def start(controller):
76
+ for chunk in chunks:
77
+ controller.enqueue(to_js(chunk))
78
+ controller.close()
79
+
80
+ start_proxy = create_proxy(start)
81
+ try:
82
+ stream = js.ReadableStream.new(
83
+ to_js({"start": start_proxy}, dict_converter=Object.fromEntries)
84
+ )
85
+ hdrs = dict(headers or {})
86
+ hdrs.setdefault("Content-Length", str(sum(len(chunk) for chunk in chunks)))
87
+ request = Request(
88
+ f"{BASE_URL}{path}",
89
+ method=method,
90
+ headers=hdrs,
91
+ body=stream,
92
+ )
93
+ return await asgi.fetch(app, request, env or {})
94
+ finally:
95
+ start_proxy.destroy()
96
+
97
+
66
98
  def build_multipart(files, boundary="----WebTestBoundary"):
67
99
  """Build a multipart/form-data body from a list of (name, filename, content) tuples."""
68
100
  lines = []
@@ -0,0 +1,99 @@
1
+ """Tests across the FastAPI, Pyodide, and Workers runtime boundaries."""
2
+
3
+ import asyncio
4
+ import gzip
5
+ import hashlib
6
+ import json
7
+
8
+ import pytest
9
+ from _client import fetch, fetch_chunks, read_json
10
+ from worker import reset_platform_concurrency
11
+
12
+
13
+ @pytest.mark.asyncio
14
+ async def test_chunked_js_stream_preserves_utf8_and_boundaries(fastapi_app):
15
+ """A JS ReadableStream survives the JS-to-Python ASGI receive bridge."""
16
+ value = {
17
+ "message": "worker \u0063\u0061\u0066\u00e9 \U0001f680",
18
+ "items": [1, 2, 3],
19
+ }
20
+ body = json.dumps(value, ensure_ascii=False).encode()
21
+ emoji = "\U0001f680".encode()
22
+ emoji_start = body.index(emoji)
23
+ cuts = [7, emoji_start + 1, emoji_start + 3, len(body) - 2]
24
+ chunks = []
25
+ start = 0
26
+ for end in cuts + [len(body)]:
27
+ chunks.append(body[start:end])
28
+ start = end
29
+
30
+ resp = await fetch_chunks(
31
+ fastapi_app,
32
+ "/platform/chunked-json",
33
+ chunks,
34
+ headers={"Content-Type": "application/json"},
35
+ )
36
+ assert resp.status == 200
37
+ data = await read_json(resp)
38
+ assert data["chunk_sizes"] == [len(chunk) for chunk in chunks]
39
+ assert data["sha256"] == hashlib.sha256(body).hexdigest()
40
+ assert data["data"] == value
41
+
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_concurrent_requests_keep_bindings_and_data_isolated(fastapi_app):
45
+ """Interleaved requests retain their own env, headers, and bodies."""
46
+ reset_platform_concurrency()
47
+
48
+ async def request(marker):
49
+ resp = await fetch(
50
+ fastapi_app,
51
+ "/platform/concurrent-env",
52
+ env={"marker": marker},
53
+ method="POST",
54
+ headers={"X-Request-Marker": marker},
55
+ body=f"body-{marker}",
56
+ )
57
+ assert resp.status == 200
58
+ return await read_json(resp)
59
+
60
+ first, second = await asyncio.wait_for(
61
+ asyncio.gather(request("first"), request("second")), timeout=5
62
+ )
63
+ assert first == {
64
+ "binding": "first",
65
+ "header": "first",
66
+ "body": "body-first",
67
+ }
68
+ assert second == {
69
+ "binding": "second",
70
+ "header": "second",
71
+ "body": "body-second",
72
+ }
73
+
74
+
75
+ @pytest.mark.asyncio
76
+ async def test_multiple_set_cookie_headers_survive_fastapi(fastapi_app):
77
+ """FastAPI's separate Set-Cookie headers remain distinct through JS Headers."""
78
+ resp = await fetch(fastapi_app, "/platform/multiple-cookies")
79
+ assert resp.status == 200
80
+ cookies = list(resp.headers.getSetCookie())
81
+ assert len(cookies) == 3
82
+ assert cookies[0].startswith("first=1;")
83
+ assert cookies[1].startswith("second=two;")
84
+ assert cookies[2].startswith("dated=3;")
85
+ assert "Wed, 21 Oct 2037 07:28:00 GMT" in cookies[2]
86
+
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_gzip_middleware_preserves_binary_body(fastapi_app):
90
+ """GZipMiddleware compresses binary data streamed through a JS Response."""
91
+ expected = bytes(range(256)) * 32
92
+ resp = await fetch(
93
+ fastapi_app,
94
+ "/platform/gzip-binary",
95
+ headers={"Accept-Encoding": "gzip"},
96
+ )
97
+ assert resp.status == 200
98
+ assert resp.headers.get("content-encoding") == "gzip"
99
+ assert gzip.decompress((await resp.bytes()).to_bytes()) == expected
@@ -0,0 +1,46 @@
1
+ """FastAPI lifecycle tests targeting the Workers ASGI streaming boundary."""
2
+
3
+ import asyncio
4
+
5
+ import pytest
6
+ from _client import fetch, get_json
7
+ from worker import (
8
+ _platform_events,
9
+ _platform_shutdown_complete,
10
+ reset_platform_events,
11
+ )
12
+
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_lifespan_state_reaches_request(fastapi_app):
16
+ """State yielded by FastAPI's lifespan context is copied to HTTP scopes."""
17
+ resp, data = await get_json(fastapi_app, "/platform/lifespan-state")
18
+ assert resp.status == 200
19
+ assert data == {"available": True, "closed": False}
20
+
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_stream_finishes_before_cleanup_and_shutdown(fastapi_app):
24
+ """Streaming, background work, and dependency cleanup precede shutdown."""
25
+ reset_platform_events()
26
+
27
+ resp = await asyncio.wait_for(
28
+ fetch(fastapi_app, "/platform/lifecycle-stream"), timeout=5
29
+ )
30
+ assert resp.status == 200
31
+ assert await asyncio.wait_for(resp.text(), timeout=5) == (
32
+ "chunk-0\nchunk-1\nchunk-2\n"
33
+ )
34
+ await asyncio.wait_for(_platform_shutdown_complete.wait(), timeout=5)
35
+
36
+ assert _platform_events == [
37
+ "lifespan-startup",
38
+ "dependency-open",
39
+ "handler",
40
+ "chunk-0",
41
+ "chunk-1",
42
+ "chunk-2",
43
+ "background-task",
44
+ "dependency-close",
45
+ "lifespan-shutdown",
46
+ ]
@@ -2,6 +2,7 @@ import asyncio
2
2
  import enum
3
3
  import hashlib
4
4
  import importlib.util
5
+ import json
5
6
  from contextlib import asynccontextmanager
6
7
  from pathlib import Path
7
8
 
@@ -32,6 +33,7 @@ from fastapi.staticfiles import StaticFiles
32
33
  from pydantic import BaseModel
33
34
  from pyodide.webloop import WebLoop
34
35
  from starlette.background import BackgroundTask
36
+ from starlette.middleware.gzip import GZipMiddleware
35
37
 
36
38
  import asgi
37
39
 
@@ -51,16 +53,40 @@ class PlatformResource:
51
53
  self.closed = False
52
54
 
53
55
 
56
+ _platform_events: list[str] = []
57
+ _platform_shutdown_complete = asyncio.Event()
58
+
59
+
60
+ def reset_platform_events():
61
+ _platform_events.clear()
62
+ _platform_shutdown_complete.clear()
63
+
64
+
54
65
  @asynccontextmanager
55
66
  async def _platform_lifespan(_app):
56
67
  resource = PlatformResource()
68
+ _platform_events.append("lifespan-startup")
57
69
  try:
58
70
  yield {"platform_resource": resource}
59
71
  finally:
60
72
  resource.closed = True
73
+ _platform_events.append("lifespan-shutdown")
74
+ _platform_shutdown_complete.set()
61
75
 
62
76
 
63
77
  app = FastAPI(lifespan=_platform_lifespan)
78
+ app.add_middleware(GZipMiddleware, minimum_size=1024)
79
+
80
+ _platform_concurrency_ready = asyncio.Event()
81
+ _platform_concurrency_count = 0
82
+
83
+
84
+ def reset_platform_concurrency():
85
+ global _platform_concurrency_count
86
+
87
+ _platform_concurrency_count = 0
88
+ _platform_concurrency_ready.clear()
89
+
64
90
 
65
91
  # --------------------------------------------------------------------------- #
66
92
  # Shared mutable state used to verify side-effects (e.g. background tasks).
@@ -463,6 +489,91 @@ async def platform_upload_spooled(file: UploadFile = File(...)): # noqa: B008
463
489
  }
464
490
 
465
491
 
492
+ @app.post("/platform/chunked-json")
493
+ async def platform_chunked_json(request: Request):
494
+ chunks = [chunk async for chunk in request.stream() if chunk]
495
+ body = b"".join(chunks)
496
+ return {
497
+ "chunk_sizes": [len(chunk) for chunk in chunks],
498
+ "sha256": hashlib.sha256(body).hexdigest(),
499
+ "data": json.loads(body),
500
+ }
501
+
502
+
503
+ @app.post("/platform/concurrent-env")
504
+ async def platform_concurrent_env(
505
+ request: Request,
506
+ bindings=asgi.env, # noqa: B008
507
+ ):
508
+ global _platform_concurrency_count
509
+
510
+ _platform_concurrency_count += 1
511
+ if _platform_concurrency_count == 2:
512
+ _platform_concurrency_ready.set()
513
+ await asyncio.wait_for(_platform_concurrency_ready.wait(), timeout=5)
514
+ body = await request.body()
515
+ return {
516
+ "binding": bindings["marker"],
517
+ "header": request.headers["x-request-marker"],
518
+ "body": body.decode(),
519
+ }
520
+
521
+
522
+ @app.get("/platform/multiple-cookies")
523
+ async def platform_multiple_cookies():
524
+ response = JSONResponse({"ok": True})
525
+ response.set_cookie("first", "1")
526
+ response.set_cookie("second", "two")
527
+ response.set_cookie("dated", "3", expires="Wed, 21 Oct 2037 07:28:00 GMT")
528
+ return response
529
+
530
+
531
+ @app.get("/platform/gzip-binary")
532
+ async def platform_gzip_binary():
533
+ return FastAPIResponse(
534
+ content=(bytes(range(256)) * 32),
535
+ media_type="application/octet-stream",
536
+ )
537
+
538
+
539
+ def _stream_resource():
540
+ resource = PlatformResource()
541
+ _platform_events.append("dependency-open")
542
+ try:
543
+ yield resource
544
+ finally:
545
+ resource.closed = True
546
+ _platform_events.append("dependency-close")
547
+
548
+
549
+ async def _platform_background_task():
550
+ await asyncio.sleep(0)
551
+ _platform_events.append("background-task")
552
+
553
+
554
+ @app.get("/platform/lifecycle-stream")
555
+ async def platform_lifecycle_stream(
556
+ request: Request,
557
+ resource: PlatformResource = Depends(_stream_resource), # noqa: B008
558
+ ):
559
+ lifespan_resource = request.state.platform_resource
560
+ _platform_events.append("handler")
561
+
562
+ async def chunks():
563
+ for index in range(3):
564
+ assert not resource.closed
565
+ assert not lifespan_resource.closed
566
+ _platform_events.append(f"chunk-{index}")
567
+ yield f"chunk-{index}\n"
568
+ await asyncio.sleep(0)
569
+
570
+ return StreamingResponse(
571
+ chunks(),
572
+ media_type="text/plain",
573
+ background=BackgroundTask(_platform_background_task),
574
+ )
575
+
576
+
466
577
  @app.get("/native-file")
467
578
  async def native_file():
468
579
  """Serve a single bundled file with FastAPI's native FileResponse."""
@@ -159,3 +159,11 @@ async def test_environment_reaches_websocket_scope():
159
159
  ws.send("env")
160
160
  event = await asyncio.wait_for(message, TIMEOUT_S)
161
161
  assert event.data == "worker-environment"
162
+
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_app_crash_closes_the_transport():
166
+ async with _ws_session("/ws-crash") as ws:
167
+ close = _listen(ws, "close")
168
+ evt = await asyncio.wait_for(close, TIMEOUT_S)
169
+ assert evt.code == 1011
@@ -133,11 +133,25 @@ class WSEnvApp:
133
133
  await send({"type": "websocket.send", "text": scope["env"]["marker"]})
134
134
 
135
135
 
136
+ class WSCrashApp:
137
+ """Accepts, then raises: the server must close the transport (1011)."""
138
+
139
+ async def __call__(self, scope, receive, send):
140
+ if scope["type"] == "lifespan":
141
+ await _drain_lifespan(receive, send)
142
+ return
143
+ message = await receive()
144
+ assert message["type"] == "websocket.connect"
145
+ await send({"type": "websocket.accept"})
146
+ raise RuntimeError("websocket app crashed")
147
+
148
+
136
149
  ws_app = WSWatchApp()
137
150
  echo_app = WSEchoApp()
138
151
  empty_frame_app = WSEmptyFrameApp()
139
152
  close_app = WSCloseApp()
140
153
  env_app = WSEnvApp()
154
+ crash_app = WSCrashApp()
141
155
 
142
156
 
143
157
  class Default(WorkerEntrypoint):
@@ -152,6 +166,7 @@ class Default(WorkerEntrypoint):
152
166
  "/ws-close": close_app,
153
167
  "/ws-echo": echo_app,
154
168
  "/ws-empty": empty_frame_app,
169
+ "/ws-crash": crash_app,
155
170
  }.get(path, ws_app)
156
171
  return await asgi.websocket(app, request)
157
172
  import json
@@ -8,9 +8,24 @@ from worker import (
8
8
  STREAMING_NUM_CHUNKS,
9
9
  crash_app,
10
10
  example_hdr,
11
+ header_echo_app,
11
12
  )
12
13
 
13
- from workers import Request, env, wsgi
14
+ from workers import Request, WorkerEntrypoint, env, wsgi
15
+
16
+
17
+ @pytest.mark.asyncio
18
+ async def test_generated_entrypoint_serves_wsgi_app():
19
+ default = wsgi.entrypoint(header_echo_app)
20
+ assert default.__name__ == "Default"
21
+ assert issubclass(default, WorkerEntrypoint)
22
+
23
+ entrypoint = object.__new__(default)
24
+ entrypoint.env = {}
25
+ response = await entrypoint.fetch(js.Request.new("http://example.com/"))
26
+
27
+ assert response.status == 200
28
+ assert await response.text() == "Hello, World"
14
29
 
15
30
 
16
31
  @pytest.mark.asyncio
@@ -62,9 +77,10 @@ async def test_cookies():
62
77
  assert "b=2" in cookies
63
78
 
64
79
 
80
+ @pytest.mark.parametrize("endpoint", ("stream", "stream-stack-switch"))
65
81
  @pytest.mark.asyncio
66
- async def test_streaming():
67
- response = await env.SELF.fetch("http://example.com/stream")
82
+ async def test_streaming(endpoint):
83
+ response = await env.SELF.fetch("http://example.com/" + endpoint)
68
84
  assert response.status == 200
69
85
  assert response.headers.get("content-type") == "application/octet-stream"
70
86