workers-runtime-sdk 1.9.3__tar.gz → 1.9.5__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 (211) hide show
  1. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/AGENTS.md +1 -0
  2. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/CHANGELOG.md +18 -0
  3. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/PKG-INFO +1 -1
  4. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/pyproject.toml +1 -1
  5. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_entropy_import_context.py +34 -5
  6. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/asgi.py +35 -7
  7. workers_runtime_sdk-1.9.5/src/workers/gateway/__init__.py +5 -0
  8. workers_runtime_sdk-1.9.5/src/workers/gateway/options.py +30 -0
  9. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/wsgi.py +23 -5
  10. workers_runtime_sdk-1.9.5/tests/workerd-test/asgi/asgi.wd-test +36 -0
  11. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/tests/test_asgi.py +39 -1
  12. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/worker.py +60 -0
  13. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/pyproject.toml +1 -0
  14. workers_runtime_sdk-1.9.5/tests/workerd-test/entropy-patches/tests/test_boto3.py +8 -0
  15. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/worker.py +1 -0
  16. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/tests/test_wsgi.py +34 -0
  17. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/worker.py +34 -0
  18. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/wsgi.wd-test +18 -1
  19. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/uv.lock +1 -1
  20. workers_runtime_sdk-1.9.3/tests/workerd-test/asgi/asgi.wd-test +0 -19
  21. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/.gitignore +0 -0
  22. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/LICENSE +0 -0
  23. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/README.md +0 -0
  24. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_cloudflare_compat_flags.pyi +0 -0
  25. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_pyodide_entrypoint_helper.pyi +0 -0
  26. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_package_patches.py +0 -0
  27. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_patch_loader.pth +0 -0
  28. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_patch_loader.py +0 -0
  29. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/asgi.py +0 -0
  30. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/__init__.py +0 -0
  31. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/blob.py +0 -0
  32. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/entrypoints.py +0 -0
  33. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/fetch.py +0 -0
  34. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/formdata.py +0 -0
  35. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/py.typed +0 -0
  36. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/request.py +0 -0
  37. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/response.py +0 -0
  38. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/rpc.py +0 -0
  39. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/types.py +0 -0
  40. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/utils.py +0 -0
  41. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/workflows.js +0 -0
  42. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/workflows.py +0 -0
  43. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/public/data.json +0 -0
  44. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/public/hello.txt +0 -0
  45. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/pyproject.toml +0 -0
  46. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/conftest.py +0 -0
  47. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_analytics_engine.py +0 -0
  48. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_assets.py +0 -0
  49. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_d1.py +0 -0
  50. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_do.py +0 -0
  51. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
  52. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
  53. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_images.py +0 -0
  54. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_kv.py +0 -0
  55. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_mtls.py +0 -0
  56. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_queue.py +0 -0
  57. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_r2.py +0 -0
  58. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_ratelimit.py +0 -0
  59. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_vectorize.py +0 -0
  60. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_version_metadata.py +0 -0
  61. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_workflow.py +0 -0
  62. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/worker.py +0 -0
  63. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/worker_durable_object.py +0 -0
  64. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/worker_workflow.py +0 -0
  65. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/wrangler.jsonc +0 -0
  66. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/conftest.py +0 -0
  67. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_bindings.py +0 -0
  68. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_django.py +0 -0
  69. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_django_databases.py +0 -0
  70. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_fastapi.py +0 -0
  71. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_flask.py +0 -0
  72. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_in_workerd.py +0 -0
  73. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
  74. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
  75. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
  76. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
  77. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
  78. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
  79. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
  80. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
  81. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
  82. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
  83. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
  84. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
  85. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
  86. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
  87. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
  88. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
  89. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
  90. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
  91. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
  92. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
  93. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
  94. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
  95. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
  96. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
  97. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
  98. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
  99. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
  100. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
  101. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
  102. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
  103. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
  104. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
  105. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
  106. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
  107. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
  108. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
  109. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
  110. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
  111. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
  112. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
  113. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
  114. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
  115. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
  116. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
  117. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
  118. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/pyproject.toml +0 -0
  119. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/django_db_config.py +0 -0
  120. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/django_db_helpers.py +0 -0
  121. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/test_mysql.py +0 -0
  122. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/test_postgresql.py +0 -0
  123. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/worker.py +0 -0
  124. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/wrangler.jsonc +0 -0
  125. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
  126. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
  127. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
  128. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
  129. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -0
  130. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
  131. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
  132. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
  133. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
  134. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
  135. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
  136. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
  137. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +0 -0
  138. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
  139. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -0
  140. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
  141. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
  142. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
  143. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
  144. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
  145. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/worker.py +0 -0
  146. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
  147. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/pyproject.toml +0 -0
  148. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/src/test_response.py +0 -0
  149. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/src/worker.py +0 -0
  150. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/wrangler.jsonc +0 -0
  151. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/pyproject.toml +0 -0
  152. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
  153. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
  154. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
  155. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +0 -0
  156. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/worker.py +0 -0
  157. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
  158. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
  159. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/pyproject.toml +0 -0
  160. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/worker.py +0 -0
  161. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
  162. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
  163. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
  164. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/worker.py +0 -0
  165. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
  166. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
  167. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
  168. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
  169. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
  170. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
  171. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
  172. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
  173. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
  174. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
  175. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
  176. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
  177. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_jsonschema.py +0 -0
  178. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
  179. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_litestar.py +0 -0
  180. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_mcp.py +0 -0
  181. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
  182. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_opentelemetry.py +0 -0
  183. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
  184. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
  185. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
  186. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
  187. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/http-client.wd-test +0 -0
  188. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/origin.py +0 -0
  189. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/pyproject.toml +0 -0
  190. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/tests/test_http_client.py +0 -0
  191. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/tests/test_urllib_request.py +0 -0
  192. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/worker.py +0 -0
  193. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/wrangler.jsonc +0 -0
  194. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
  195. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
  196. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/worker.js +0 -0
  197. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/worker.py +0 -0
  198. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
  199. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/pyproject.toml +0 -0
  200. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/sdk.wd-test +0 -0
  201. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/server.py +0 -0
  202. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
  203. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/worker.py +0 -0
  204. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
  205. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/pyproject.toml +0 -0
  206. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/worker.js +0 -0
  207. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/worker.py +0 -0
  208. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/workflow.wd-test +0 -0
  209. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/wrangler.jsonc +0 -0
  210. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/pyproject.toml +0 -0
  211. {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
@@ -16,6 +16,7 @@ This package provides utilities for:
16
16
  - `_pyodide_entrypoint_helper` and `_cloudflare_compat_flags` are internal values that are imported from `workerd`.
17
17
  - `workerd` (https://github.com/cloudflare/workerd) is the runtime that Cloudflare Workers use to run Python code.
18
18
  - When using a feature from `workerd`, always check if we can implement it in this package instead to avoid tight coupling with the runtime.
19
+ - Code shared by the ASGI (`workers/asgi.py`) and WSGI (`workers/wsgi.py`) adapters lives in the `workers/gateway/` directory.
19
20
 
20
21
  ## Testing
21
22
 
@@ -2,6 +2,24 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.9.5 (2026-10-20)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **runtime-sdk**: Add options argument to asgi and wsgi
10
+ ([#289](https://github.com/cloudflare/workers-py/pull/289),
11
+ [`eec1e54`](https://github.com/cloudflare/workers-py/commit/eec1e54fa0348e6c90161d7f1f022344e4e6e867))
12
+
13
+
14
+ ## v1.9.4 (2026-10-16)
15
+
16
+ ### Bug Fixes
17
+
18
+ - **runtime-sdk**: Lazily bound ssl module to make boto3 work
19
+ ([#292](https://github.com/cloudflare/workers-py/pull/292),
20
+ [`f3e7851`](https://github.com/cloudflare/workers-py/commit/f3e785144b9e581fc2b1b4cebd0ad5bc86e4550b))
21
+
22
+
5
23
  ## v1.9.3 (2026-10-06)
6
24
 
7
25
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: workers-runtime-sdk
3
- Version: 1.9.3
3
+ Version: 1.9.5
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.9.3"
7
+ version = "1.9.5"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -149,13 +149,41 @@ class NoSslFinder:
149
149
  ) from None
150
150
 
151
151
 
152
+ def add_lazy_ssl_attribute(module):
153
+ """
154
+ When `import ssl` fails inside a module, the fallback code would leave the name `ssl`
155
+ unbound. Other packages may still re-export it from there, e.g. botocore does
156
+ `from urllib3.util.ssl_ import ssl`.
157
+
158
+ This is a rare situation, and actually something that needs to be fixed in the package.
159
+ However, boto3 is a very commonly downloaded package in Python, so we handle this case specifically.
160
+ TODO: We can drop this when https://github.com/boto/botocore/pull/3640 is merged.
161
+ """
162
+ if "ssl" in module.__dict__:
163
+ return
164
+
165
+ orig_getattr = module.__dict__.get("__getattr__")
166
+
167
+ def __getattr__(name):
168
+ if name == "ssl":
169
+ import ssl
170
+
171
+ module.ssl = ssl
172
+ return ssl
173
+ if orig_getattr is not None:
174
+ return orig_getattr(name)
175
+ raise AttributeError(f"module {module.__name__!r} has no attribute {name!r}")
176
+
177
+ module.__getattr__ = __getattr__
178
+
179
+
152
180
  @contextmanager
153
- def no_ssl():
181
+ def no_ssl(module):
154
182
  """
155
183
  Various packages will call ssl.create_default_context() at top level which uses entropy if they
156
184
  can import ssl. By temporarily making importing ssl raise an import error, we exercise the
157
185
  workaround code and so avoid the entropy calls. After, we put the ssl module back to the normal
158
- value.
186
+ value and make `module.ssl` available again in case the workaround code left it unbound.
159
187
  """
160
188
  try:
161
189
  f = NoSslFinder()
@@ -166,26 +194,27 @@ def no_ssl():
166
194
  sys.meta_path.remove(f)
167
195
  if ssl:
168
196
  sys.modules["ssl"] = ssl
197
+ add_lazy_ssl_attribute(module)
169
198
 
170
199
 
171
200
  @register_exec_patch("aiohttp.connector")
172
201
  @contextmanager
173
202
  def aiohttp_connector_context(module):
174
- with no_ssl():
203
+ with no_ssl(module):
175
204
  yield
176
205
 
177
206
 
178
207
  @register_exec_patch("requests.adapters")
179
208
  @contextmanager
180
209
  def requests_adapters_context(module):
181
- with no_ssl():
210
+ with no_ssl(module):
182
211
  yield
183
212
 
184
213
 
185
214
  @register_exec_patch("urllib3.util.ssl_")
186
215
  @contextmanager
187
216
  def urllib3_util_ssl__context(module):
188
- with no_ssl():
217
+ with no_ssl(module):
189
218
  yield
190
219
 
191
220
 
@@ -2,12 +2,14 @@ import logging
2
2
  from asyncio import Event, Future, Queue, create_task, ensure_future
3
3
  from collections.abc import Awaitable
4
4
  from contextlib import contextmanager
5
+ from dataclasses import dataclass
5
6
  from typing import Any
6
7
  from urllib.parse import unquote
7
8
 
8
9
  import js
9
10
 
10
11
  from workers import Context, Request, WorkerEntrypoint
12
+ from workers.gateway import BaseOptions
11
13
  from workers.utils import _to_js_headers
12
14
 
13
15
  ASGI = {"spec_version": "2.0", "version": "3.0"}
@@ -16,6 +18,14 @@ logger = logging.getLogger("asgi")
16
18
  background_tasks = set()
17
19
 
18
20
 
21
+ @dataclass(frozen=True, kw_only=True)
22
+ class Options(BaseOptions):
23
+ """Options for the ASGI adapter"""
24
+
25
+
26
+ DEFAULT_OPTIONS = Options()
27
+
28
+
19
29
  def run_in_background(coro: Awaitable[Any]) -> Future:
20
30
  fut = ensure_future(coro)
21
31
  background_tasks.add(fut)
@@ -195,10 +205,12 @@ async def process_request( # noqa: PLR0913
195
205
  # TODO(later): remove this parameter after unvendoring Python SDK from workerd
196
206
  ctx: Context | None,
197
207
  state: dict[str, Any] | None = None,
208
+ options: Options = DEFAULT_OPTIONS,
198
209
  ) -> tuple[js.Response, Future[None]]:
199
210
  from js import Response, TransformStream
200
211
  from pyodide.ffi import create_proxy
201
212
 
213
+ response_init = options.response_init()
202
214
  status = None
203
215
  headers = None
204
216
  result = Future()
@@ -262,7 +274,10 @@ async def process_request( # noqa: PLR0913
262
274
  readable = transform_stream.readable
263
275
  writer = transform_stream.writable.getWriter()
264
276
  resp = Response.new(
265
- readable, headers=_to_js_headers(headers), status=status
277
+ readable,
278
+ headers=_to_js_headers(headers),
279
+ status=status,
280
+ **response_init,
266
281
  )
267
282
  result.set_result(resp)
268
283
  with acquire_js_buffer(body) as jsbytes:
@@ -271,7 +286,10 @@ async def process_request( # noqa: PLR0913
271
286
  # 101/103/204/205/304 must not carry a body per the Fetch spec.
272
287
  # https://fetch.spec.whatwg.org/#null-body-status
273
288
  resp = Response.new(
274
- None, headers=_to_js_headers(headers), status=status
289
+ None,
290
+ headers=_to_js_headers(headers),
291
+ status=status,
292
+ **response_init,
275
293
  )
276
294
  result.set_result(resp)
277
295
  finished_response.set()
@@ -281,7 +299,10 @@ async def process_request( # noqa: PLR0913
281
299
  buf = px.getBuffer()
282
300
  px.destroy()
283
301
  resp = Response.new(
284
- buf.data, headers=_to_js_headers(headers), status=status
302
+ buf.data,
303
+ headers=_to_js_headers(headers),
304
+ status=status,
305
+ **response_init,
285
306
  )
286
307
  result.set_result(resp)
287
308
  finished_response.set()
@@ -436,12 +457,19 @@ async def process_websocket(
436
457
 
437
458
 
438
459
  async def fetch(
439
- app: Any, req: "Request | js.Request", env: Any, ctx: Context | None = None
460
+ app: Any,
461
+ req: "Request | js.Request",
462
+ env: Any,
463
+ ctx: Context | None = None,
464
+ *,
465
+ options: Options | None = None,
440
466
  ) -> js.Response:
441
467
  logger.debug("ASGI request: %s %s", req.method, req.url)
442
468
  shutdown, state = await start_application(app)
443
469
  try:
444
- result, request_task = await process_request(app, req, env, ctx, state=state)
470
+ result, request_task = await process_request(
471
+ app, req, env, ctx, state=state, options=options or DEFAULT_OPTIONS
472
+ )
445
473
  except Exception:
446
474
  logger.exception("ASGI request failed")
447
475
  await shutdown()
@@ -469,14 +497,14 @@ async def websocket(
469
497
  return await process_websocket(app, req, env)
470
498
 
471
499
 
472
- def entrypoint(app: Any) -> type[WorkerEntrypoint]:
500
+ def entrypoint(app: Any, *, options: Options | None = None) -> type[WorkerEntrypoint]:
473
501
  """Create the default Worker entrypoint for an ASGI application."""
474
502
 
475
503
  class Default(WorkerEntrypoint):
476
504
  async def fetch(self, request):
477
505
  if (request.headers.get("upgrade") or "").lower() == "websocket":
478
506
  return await websocket(app, request, self.env)
479
- return await fetch(app, request, self.env, self.ctx)
507
+ return await fetch(app, request, self.env, self.ctx, options=options)
480
508
 
481
509
  return Default
482
510
 
@@ -0,0 +1,5 @@
1
+ """Shared building blocks for the ASGI and WSGI adapters."""
2
+
3
+ from .options import BaseOptions, EncodeBody
4
+
5
+ __all__ = ["BaseOptions", "EncodeBody"]
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Literal, get_args
3
+
4
+ EncodeBody = Literal["automatic", "manual"]
5
+
6
+
7
+ @dataclass(frozen=True, kw_only=True)
8
+ class BaseOptions:
9
+ """Options shared by the ASGI and WSGI adapters.
10
+
11
+ Downstream adapters should inherit from this class to define their own options.
12
+ """
13
+
14
+ # Whether to encode the body according to the ``Content-Encoding`` header
15
+ # See https://developers.cloudflare.com/workers/runtime-apis/response/#the-encodebody-option
16
+ encode_body: EncodeBody = "automatic"
17
+
18
+ def __post_init__(self) -> None:
19
+ if self.encode_body not in get_args(EncodeBody):
20
+ raise ValueError(
21
+ f"encode_body must be one of {get_args(EncodeBody)}, "
22
+ f"got {self.encode_body!r}"
23
+ )
24
+
25
+ def response_init(self) -> dict[str, Any]:
26
+ """Extra keyword arguments for ``Response.new``."""
27
+ init: dict[str, Any] = {}
28
+ if self.encode_body != "automatic":
29
+ init["encodeBody"] = self.encode_body
30
+ return init
@@ -2,17 +2,27 @@ import io
2
2
  import logging
3
3
  import sys
4
4
  from collections.abc import Callable, Iterator
5
+ from dataclasses import dataclass
5
6
  from typing import Any
6
7
  from urllib.parse import unquote, urlsplit
7
8
 
8
9
  import js
9
10
 
10
11
  from workers import Context, Request, WorkerEntrypoint
12
+ from workers.gateway import BaseOptions
11
13
 
12
14
  logger = logging.getLogger("wsgi")
13
15
  NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
14
16
 
15
17
 
18
+ @dataclass(frozen=True, kw_only=True)
19
+ class Options(BaseOptions):
20
+ """Options for the WSGI adapter, see ``BaseOptions`` for shared fields."""
21
+
22
+
23
+ DEFAULT_OPTIONS = Options()
24
+
25
+
16
26
  def _wsgi_native_string(value: str) -> str:
17
27
  """Convert a (possibly non-ASCII) Python ``str`` into a WSGI "native string".
18
28
 
@@ -217,6 +227,7 @@ def _make_streaming_response(
217
227
  headers: "list[tuple[str, str]]",
218
228
  chunks: "Iterator[bytes]",
219
229
  on_close: "Callable[[], None]",
230
+ response_init: dict[str, Any],
220
231
  ) -> js.Response:
221
232
  """Build a ``js.Response`` whose body is pulled lazily from *chunks*.
222
233
 
@@ -232,7 +243,7 @@ def _make_streaming_response(
232
243
  # WSGI status is e.g. "200 OK"; split into code + reason phrase.
233
244
  code_str, _, reason = status.partition(" ")
234
245
  code = int(code_str)
235
- options: dict[str, Any] = {"status": code}
246
+ options: dict[str, Any] = {"status": code, **response_init}
236
247
  if reason:
237
248
  options["statusText"] = reason
238
249
 
@@ -291,6 +302,7 @@ def process_request(
291
302
  req: "Request | js.Request",
292
303
  env: Any,
293
304
  body: "bytes | io.IOBase",
305
+ options: Options = DEFAULT_OPTIONS,
294
306
  ) -> js.Response:
295
307
  environ = build_environ(req, env, body)
296
308
 
@@ -357,7 +369,11 @@ def process_request(
357
369
  yield chunk
358
370
 
359
371
  return _make_streaming_response(
360
- response_state["status"], response_state["headers"], body_chunks(), close_all
372
+ response_state["status"],
373
+ response_state["headers"],
374
+ body_chunks(),
375
+ close_all,
376
+ options.response_init(),
361
377
  )
362
378
 
363
379
 
@@ -367,6 +383,8 @@ async def fetch(
367
383
  env: Any,
368
384
  # Accepted for parity with asgi.fetch; WSGI has no use for it.
369
385
  ctx: Context | None = None,
386
+ *,
387
+ options: Options | None = None,
370
388
  ) -> js.Response:
371
389
  logger.debug("WSGI request: %s %s", req.method, req.url)
372
390
  # Prefer lazily streaming the body through `wsgi.input` (no full buffering);
@@ -375,17 +393,17 @@ async def fetch(
375
393
  if body is None:
376
394
  body = await _read_body(req)
377
395
  try:
378
- return process_request(app, req, env, body)
396
+ return process_request(app, req, env, body, options or DEFAULT_OPTIONS)
379
397
  except Exception:
380
398
  logger.exception("WSGI request failed")
381
399
  raise
382
400
 
383
401
 
384
- def entrypoint(app: Any) -> type[WorkerEntrypoint]:
402
+ def entrypoint(app: Any, *, options: Options | None = None) -> type[WorkerEntrypoint]:
385
403
  """Create the default Worker entrypoint for a WSGI application."""
386
404
 
387
405
  class Default(WorkerEntrypoint):
388
406
  async def fetch(self, request):
389
- return await fetch(app, request, self.env)
407
+ return await fetch(app, request, self.env, options=options)
390
408
 
391
409
  return Default
@@ -0,0 +1,36 @@
1
+ using Workerd = import "/workerd/workerd.capnp";
2
+
3
+ const unitTests :Workerd.Config = (
4
+ services = [
5
+ ( name = "python-asgi",
6
+ worker = (
7
+ modules = [
8
+ (name = "worker.py", pythonModule = embed "worker.py"),
9
+ %PYTHON_MODULES
10
+ ],
11
+ bindings = [
12
+ ( name = "SELF", service = "python-asgi" ),
13
+ # SELF.fetch does not serialize the response over HTTP, so it cannot
14
+ # observe the runtime encoding the body. ASGI_HTTP goes through the
15
+ # loopback HTTP socket below instead.
16
+ ( name = "ASGI_HTTP", service = "asgi-http" ),
17
+ ],
18
+ compatibilityDate = "%COMPAT_DATE",
19
+ compatibilityFlags = ["python_workers"],
20
+ )
21
+ ),
22
+ ( name = "asgi-http",
23
+ external = (
24
+ address = "loopback:asgi-http",
25
+ http = (),
26
+ )
27
+ ),
28
+ ],
29
+ sockets = [
30
+ ( name = "asgi-http",
31
+ address = "loopback:asgi-http",
32
+ http = (),
33
+ service = "python-asgi",
34
+ ),
35
+ ],
36
+ );
@@ -1,11 +1,17 @@
1
1
  import asyncio
2
+ import gzip
2
3
  import json
3
4
  import logging
4
5
 
5
6
  import js
6
7
  import pytest
7
8
  from pyodide.ffi import to_js
8
- from worker import STREAMING_CHUNK_SIZE, STREAMING_NUM_CHUNKS, example_hdr
9
+ from worker import (
10
+ ENCODED_RESPONSE_PAYLOAD,
11
+ STREAMING_CHUNK_SIZE,
12
+ STREAMING_NUM_CHUNKS,
13
+ example_hdr,
14
+ )
9
15
 
10
16
  import asgi
11
17
  from workers import Request, env
@@ -474,3 +480,35 @@ async def test_lifespan_preack_crash_is_logged_and_treated_as_unsupported():
474
480
  )
475
481
  finally:
476
482
  _remove_handler(handler)
483
+
484
+
485
+ def test_options_validate_encode_body():
486
+ assert asgi.Options().response_init() == {}
487
+ assert asgi.Options(encode_body="manual").response_init() == {
488
+ "encodeBody": "manual"
489
+ }
490
+ with pytest.raises(ValueError, match="encode_body"):
491
+ asgi.Options(encode_body="gzip")
492
+
493
+
494
+ @pytest.mark.asyncio
495
+ @pytest.mark.parametrize("mode", ["buffered", "streaming"])
496
+ @pytest.mark.parametrize("encoding", ["gzip", "identity"])
497
+ @pytest.mark.parametrize("encode_body", ["automatic", "manual"])
498
+ async def test_encode_body_option(encode_body, encoding, mode):
499
+ # SELF.fetch does not serialize the response over HTTP, so it cannot
500
+ # observe the runtime encoding the body; ASGI_HTTP goes over a socket.
501
+ response = await env.ASGI_HTTP.fetch(
502
+ f"http://example.com/encoded/{encoding}/{mode}/{encode_body}",
503
+ headers={"accept-encoding": "gzip"},
504
+ )
505
+ assert response.status == 200
506
+ assert response.headers["content-encoding"] == encoding
507
+
508
+ # fetch() decodes the declared Content-Encoding once, like a browser.
509
+ body = await response.bytes()
510
+ if encoding == "gzip" and encode_body == "automatic":
511
+ # The runtime gzips the app's already-gzipped bytes a second time.
512
+ assert gzip.decompress(body) == ENCODED_RESPONSE_PAYLOAD
513
+ else:
514
+ assert body == ENCODED_RESPONSE_PAYLOAD
@@ -1,9 +1,11 @@
1
+ import gzip
1
2
  import os
2
3
 
3
4
  from testlib.entrypoint import run_pytest
4
5
 
5
6
  import asgi
6
7
  from workers import WorkerEntrypoint
8
+ from workers.asgi import Options
7
9
 
8
10
  # ---------------------------------------------------------------------------
9
11
  # ASGI apps
@@ -238,6 +240,54 @@ class MultiCookieApp:
238
240
  await send({"type": "http.response.body", "body": b"ok"})
239
241
 
240
242
 
243
+ ENCODED_RESPONSE_PAYLOAD = b'{"message":"already encoded by the app"}' * 64
244
+
245
+
246
+ class EncodedResponseApp:
247
+ """Sends a body the app has already encoded to match Content-Encoding."""
248
+
249
+ def __init__(self, encoding, streaming):
250
+ self.encoding = encoding
251
+ self.streaming = streaming
252
+ self.body = (
253
+ gzip.compress(ENCODED_RESPONSE_PAYLOAD, mtime=0)
254
+ if encoding == "gzip"
255
+ else ENCODED_RESPONSE_PAYLOAD
256
+ )
257
+
258
+ async def __call__(self, scope, receive, send):
259
+ if scope["type"] == "lifespan":
260
+ message = await receive()
261
+ if message["type"] == "lifespan.startup":
262
+ await send({"type": "lifespan.startup.complete"})
263
+ return
264
+ await receive()
265
+ await send(
266
+ {
267
+ "type": "http.response.start",
268
+ "status": 200,
269
+ "headers": [
270
+ (b"content-type", b"application/json"),
271
+ (b"content-encoding", self.encoding.encode()),
272
+ ],
273
+ }
274
+ )
275
+ if not self.streaming:
276
+ await send({"type": "http.response.body", "body": self.body})
277
+ return
278
+ # Split one gzip member across chunks (inside its header and before
279
+ # its footer) and finish with an empty terminal chunk.
280
+ chunks = (self.body[:7], self.body[7:-8], self.body[-8:], b"")
281
+ for i, chunk in enumerate(chunks):
282
+ await send(
283
+ {
284
+ "type": "http.response.body",
285
+ "body": chunk,
286
+ "more_body": i < len(chunks) - 1,
287
+ }
288
+ )
289
+
290
+
241
291
  app = HeaderEchoApp()
242
292
  sse_app = SSEApp()
243
293
  streaming_app = StreamingApp()
@@ -267,6 +317,16 @@ class Default(WorkerEntrypoint):
267
317
  )
268
318
  elif path == "/multi-cookie":
269
319
  return await asgi.fetch(multi_cookie_app, request, self.env, self.ctx)
320
+ elif path.startswith("/encoded/"):
321
+ # /encoded/<gzip|identity>/<buffered|streaming>/<automatic|manual>
322
+ _, _, encoding, mode, encode_body = path.split("/")
323
+ return await asgi.fetch(
324
+ EncodedResponseApp(encoding, streaming=mode == "streaming"),
325
+ request,
326
+ self.env,
327
+ self.ctx,
328
+ options=Options(encode_body=encode_body),
329
+ )
270
330
 
271
331
  return await asgi.fetch(app, request, self.env, self.ctx)
272
332
 
@@ -9,6 +9,7 @@ dependencies = [
9
9
  "pydantic",
10
10
  "requests",
11
11
  "urllib3",
12
+ "boto3",
12
13
  "tiktoken",
13
14
  "cryptography",
14
15
  "jiter",
@@ -0,0 +1,8 @@
1
+ # ruff: noqa: F401
2
+ # boto3 does `from urllib3.util.ssl_ import ssl`, but `urllib3.util.ssl_` is
3
+ # imported without ssl to avoid entropy calls, which leaves `ssl` unbound there.
4
+ import boto3
5
+
6
+
7
+ def test_import():
8
+ pass
@@ -2,6 +2,7 @@
2
2
  # a snapshot.
3
3
  from tests import ( # noqa: F401
4
4
  test_aiohttp_websocket,
5
+ test_boto3,
5
6
  test_jsonschema,
6
7
  test_langsmith,
7
8
  test_litestar,
@@ -1,9 +1,11 @@
1
+ import gzip
1
2
  import json
2
3
 
3
4
  import js
4
5
  import pytest
5
6
  from pyodide.ffi import to_js
6
7
  from worker import (
8
+ ENCODED_RESPONSE_PAYLOAD,
7
9
  STREAMING_CHUNK_SIZE,
8
10
  STREAMING_NUM_CHUNKS,
9
11
  crash_app,
@@ -119,3 +121,35 @@ def test_build_environ_handles_js_and_python_requests():
119
121
  py_env = wsgi.build_environ(py_request, env, b"")
120
122
  assert js_env["HTTP_HEADER1"] == py_env["HTTP_HEADER1"] == "Value1"
121
123
  assert js_env["HTTP_HEADER2"] == py_env["HTTP_HEADER2"] == "Value2"
124
+
125
+
126
+ def test_options_validate_encode_body():
127
+ assert wsgi.Options().response_init() == {}
128
+ assert wsgi.Options(encode_body="manual").response_init() == {
129
+ "encodeBody": "manual"
130
+ }
131
+ with pytest.raises(ValueError, match="encode_body"):
132
+ wsgi.Options(encode_body="gzip")
133
+
134
+
135
+ @pytest.mark.asyncio
136
+ @pytest.mark.parametrize("mode", ["buffered", "streaming"])
137
+ @pytest.mark.parametrize("encoding", ["gzip", "identity"])
138
+ @pytest.mark.parametrize("encode_body", ["automatic", "manual"])
139
+ async def test_encode_body_option(encode_body, encoding, mode):
140
+ # SELF.fetch does not serialize the response over HTTP, so it cannot
141
+ # observe the runtime encoding the body; WSGI_HTTP goes over a socket.
142
+ response = await env.WSGI_HTTP.fetch(
143
+ f"http://example.com/encoded/{encoding}/{mode}/{encode_body}",
144
+ headers={"accept-encoding": "gzip"},
145
+ )
146
+ assert response.status == 200
147
+ assert response.headers["content-encoding"] == encoding
148
+
149
+ # fetch() decodes the declared Content-Encoding once, like a browser.
150
+ body = await response.bytes()
151
+ if encoding == "gzip" and encode_body == "automatic":
152
+ # The runtime gzips the app's already-gzipped bytes a second time.
153
+ assert gzip.decompress(body) == ENCODED_RESPONSE_PAYLOAD
154
+ else:
155
+ assert body == ENCODED_RESPONSE_PAYLOAD
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import gzip
2
3
  import os
3
4
 
4
5
  from pyodide.ffi import run_sync
@@ -91,6 +92,31 @@ def streaming_app_stack_switch(environ, start_response):
91
92
  return generate()
92
93
 
93
94
 
95
+ ENCODED_RESPONSE_PAYLOAD = b'{"message":"already encoded by the app"}' * 64
96
+
97
+
98
+ def make_encoded_response_app(encoding, streaming):
99
+ """WSGI app sending a body already encoded to match Content-Encoding."""
100
+ body = (
101
+ gzip.compress(ENCODED_RESPONSE_PAYLOAD, mtime=0)
102
+ if encoding == "gzip"
103
+ else ENCODED_RESPONSE_PAYLOAD
104
+ )
105
+
106
+ def app(environ, start_response):
107
+ start_response(
108
+ "200 OK",
109
+ [("Content-Type", "application/json"), ("Content-Encoding", encoding)],
110
+ )
111
+ if not streaming:
112
+ return [body]
113
+ # Split one gzip member across chunks (inside its header and before
114
+ # its footer).
115
+ return iter((body[:7], body[7:-8], body[-8:]))
116
+
117
+ return app
118
+
119
+
94
120
  def crash_app(environ, start_response):
95
121
  raise RuntimeError("app crash before response for testing")
96
122
 
@@ -115,6 +141,14 @@ class Default(WorkerEntrypoint):
115
141
  "/stream-stack-switch": streaming_app_stack_switch,
116
142
  }.get(path, header_echo_app)
117
143
 
144
+ if path.startswith("/encoded/"):
145
+ # /encoded/<gzip|identity>/<buffered|streaming>/<automatic|manual>
146
+ _, _, encoding, mode, encode_body = path.split("/")
147
+ app = make_encoded_response_app(encoding, streaming=mode == "streaming")
148
+ return await wsgi.fetch(
149
+ app, request, self.env, options=wsgi.Options(encode_body=encode_body)
150
+ )
151
+
118
152
  return await wsgi.fetch(app, request, self.env)
119
153
 
120
154
  async def test(self, ctrl):