workers-runtime-sdk 1.6.15__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.6.15 → workers_runtime_sdk-1.8.0}/CHANGELOG.md +35 -0
  2. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/pyproject.toml +1 -1
  4. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/asgi.py +72 -15
  5. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/request.py +9 -3
  6. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/response.py +2 -19
  7. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/utils.py +20 -0
  8. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/wsgi.py +14 -3
  9. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/_client.py +49 -0
  10. workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +99 -0
  11. workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +31 -0
  12. workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +46 -0
  13. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +9 -0
  14. workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +39 -0
  15. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/worker.py +176 -46
  16. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +8 -0
  17. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/worker.py +15 -0
  18. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/tests/test_sdk.py +5 -0
  19. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/tests/test_wsgi.py +19 -3
  20. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/worker.py +21 -9
  21. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/uv.lock +1 -1
  22. workers_runtime_sdk-1.6.15/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -12
  23. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/.gitignore +0 -0
  24. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/AGENTS.md +0 -0
  25. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/README.md +0 -0
  26. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_cloudflare_compat_flags.pyi +0 -0
  27. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
  28. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_entropy_import_context.py +0 -0
  29. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_package_patches.py +0 -0
  30. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.pth +0 -0
  31. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.py +0 -0
  32. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/asgi.py +0 -0
  33. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/__init__.py +0 -0
  34. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/blob.py +0 -0
  35. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/entrypoints.py +0 -0
  36. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/fetch.py +0 -0
  37. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/formdata.py +0 -0
  38. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/py.typed +0 -0
  39. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/rpc.py +0 -0
  40. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/types.py +0 -0
  41. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/workflows.py +0 -0
  42. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/data.json +0 -0
  43. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/hello.txt +0 -0
  44. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/pyproject.toml +0 -0
  45. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/conftest.py +0 -0
  46. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_analytics_engine.py +0 -0
  47. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_assets.py +0 -0
  48. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_d1.py +0 -0
  49. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_do.py +0 -0
  50. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
  51. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
  52. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_images.py +0 -0
  53. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_kv.py +0 -0
  54. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_mtls.py +0 -0
  55. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_queue.py +0 -0
  56. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_r2.py +0 -0
  57. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_ratelimit.py +0 -0
  58. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_vectorize.py +0 -0
  59. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_version_metadata.py +0 -0
  60. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_workflow.py +0 -0
  61. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker.py +0 -0
  62. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_durable_object.py +0 -0
  63. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_workflow.py +0 -0
  64. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/wrangler.jsonc +0 -0
  65. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/conftest.py +0 -0
  66. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_bindings.py +0 -0
  67. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_fastapi.py +0 -0
  68. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_in_workerd.py +0 -0
  69. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_web_frameworks.py +0 -0
  70. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
  71. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
  72. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
  73. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
  74. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
  75. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
  76. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
  77. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
  78. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
  79. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
  80. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
  81. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
  82. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
  83. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
  84. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
  85. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
  86. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
  87. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
  88. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
  89. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
  90. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
  91. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
  92. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
  93. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
  94. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
  95. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
  96. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
  97. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
  98. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
  99. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
  100. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
  101. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
  102. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
  103. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
  104. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
  105. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
  106. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
  107. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
  108. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
  109. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
  110. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
  111. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
  112. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
  113. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
  114. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
  115. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
  116. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
  117. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
  118. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
  119. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
  120. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
  121. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
  122. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
  123. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
  124. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
  125. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
  126. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
  127. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
  128. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
  129. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
  130. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/asgi.wd-test +0 -0
  131. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/pyproject.toml +0 -0
  132. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
  133. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/worker.py +0 -0
  134. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
  135. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
  136. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
  137. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
  138. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
  139. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/pyproject.toml +0 -0
  140. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/worker.py +0 -0
  141. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
  142. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
  143. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
  144. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/worker.py +0 -0
  145. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
  146. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
  147. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
  148. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
  149. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
  150. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
  151. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
  152. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
  153. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
  154. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
  155. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
  156. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
  157. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
  158. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
  159. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
  160. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
  161. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
  162. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
  163. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/worker.py +0 -0
  164. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
  165. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
  166. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
  167. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.js +0 -0
  168. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.py +0 -0
  169. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
  170. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/pyproject.toml +0 -0
  171. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/sdk.wd-test +0 -0
  172. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/server.py +0 -0
  173. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/worker.py +0 -0
  174. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
  175. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/pyproject.toml +0 -0
  176. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
  177. {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
@@ -2,6 +2,41 @@
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
+
27
+ ## v1.7.0 (2026-08-24)
28
+
29
+ ### Bug Fixes
30
+
31
+ - Fixes Request body being released too early
32
+ ([`84a20ac`](https://github.com/cloudflare/workers-py/commit/84a20acbfa28ea82fb69b2c279a56d9e25fd9639))
33
+
34
+ ### Features
35
+
36
+ - Asgi.entrypoint syntax sugar for nicer asgi apps
37
+ ([`5399270`](https://github.com/cloudflare/workers-py/commit/539927085a79e2d88a287cc70ccfa433ec257e1d))
38
+
39
+
5
40
  ## v1.6.15 (2026-08-21)
6
41
 
7
42
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: workers-runtime-sdk
3
- Version: 1.6.15
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.6.15"
7
+ version = "1.8.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"}
@@ -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
 
@@ -415,6 +460,18 @@ async def websocket(
415
460
  return await process_websocket(app, req, env)
416
461
 
417
462
 
463
+ def entrypoint(app: Any) -> type[WorkerEntrypoint]:
464
+ """Create the default Worker entrypoint for an ASGI application."""
465
+
466
+ class Default(WorkerEntrypoint):
467
+ async def fetch(self, request):
468
+ if (request.headers.get("upgrade") or "").lower() == "websocket":
469
+ return await websocket(app, request, self.env)
470
+ return await fetch(app, request, self.env, self.ctx)
471
+
472
+ return Default
473
+
474
+
418
475
  def __getattr__(name):
419
476
  if name == "env":
420
477
  from fastapi import Depends, Request
@@ -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
@@ -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
 
@@ -46,6 +49,52 @@ async def post_json(app, path, data, **kwargs):
46
49
  )
47
50
 
48
51
 
52
+ def build_multipart_bytes(files, boundary="----WebTestBoundary"):
53
+ """Build a binary-safe multipart body from (name, filename, bytes) tuples."""
54
+ body = bytearray()
55
+ for name, filename, content in files:
56
+ if isinstance(content, str):
57
+ content = content.encode()
58
+ body.extend(f"--{boundary}\r\n".encode())
59
+ body.extend(
60
+ f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'.encode()
61
+ )
62
+ body.extend(b"Content-Type: application/octet-stream\r\n\r\n")
63
+ body.extend(content)
64
+ body.extend(b"\r\n")
65
+ body.extend(f"--{boundary}--\r\n".encode())
66
+ return bytes(body), f"multipart/form-data; boundary={boundary}"
67
+
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
+
49
98
  def build_multipart(files, boundary="----WebTestBoundary"):
50
99
  """Build a multipart/form-data body from a list of (name, filename, content) tuples."""
51
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,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
+ }
@@ -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
+ ]
@@ -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()