workers-runtime-sdk 1.8.2__tar.gz → 1.8.4__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 (188) hide show
  1. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/CHANGELOG.md +18 -0
  2. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/PKG-INFO +1 -1
  3. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/pyproject.toml +6 -30
  4. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/_workers_sdk_entropy_import_context.py +11 -0
  5. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/asgi.py +12 -3
  6. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/pyproject.toml +4 -1
  7. workers_runtime_sdk-1.8.4/tests/bindings-test/src/worker.py +32 -0
  8. workers_runtime_sdk-1.8.4/tests/conftest.py +116 -0
  9. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/test_fastapi.py +1 -5
  10. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/test_in_workerd.py +11 -7
  11. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/pyproject.toml +4 -0
  12. workers_runtime_sdk-1.8.4/tests/web-frameworks-test/django-async/src/worker.py +104 -0
  13. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +4 -0
  14. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/worker.py +4 -73
  15. workers_runtime_sdk-1.8.4/tests/web-frameworks-test/flask-tests/pyproject.toml +8 -0
  16. workers_runtime_sdk-1.8.4/tests/web-frameworks-test/flask-tests/src/worker.py +5 -0
  17. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi/tests/test_asgi.py +46 -0
  18. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +11 -14
  19. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/pyproject.toml +4 -1
  20. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -1
  21. workers_runtime_sdk-1.8.2/tests/workerd-test/entropy-patches/tests/test_rust_packages.py → workers_runtime_sdk-1.8.4/tests/workerd-test/entropy-patches/tests/test_litestar.py +1 -4
  22. workers_runtime_sdk-1.8.4/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +19 -0
  23. workers_runtime_sdk-1.8.4/tests/workerd-test/entropy-patches/worker.py +17 -0
  24. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/uv.lock +23 -1
  25. workers_runtime_sdk-1.8.2/tests/bindings-test/src/worker.py +0 -186
  26. workers_runtime_sdk-1.8.2/tests/conftest.py +0 -290
  27. workers_runtime_sdk-1.8.2/tests/web-frameworks-test/django-async/src/worker.py +0 -206
  28. workers_runtime_sdk-1.8.2/tests/web-frameworks-test/flask-tests/pyproject.toml +0 -5
  29. workers_runtime_sdk-1.8.2/tests/web-frameworks-test/flask-tests/src/worker.py +0 -89
  30. workers_runtime_sdk-1.8.2/tests/workerd-test/entropy-patches/worker.py +0 -27
  31. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/.gitignore +0 -0
  32. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/AGENTS.md +0 -0
  33. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/README.md +0 -0
  34. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/_cloudflare_compat_flags.pyi +0 -0
  35. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/_pyodide_entrypoint_helper.pyi +0 -0
  36. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/_workers_sdk_package_patches.py +0 -0
  37. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/_workers_sdk_patch_loader.pth +0 -0
  38. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/_workers_sdk_patch_loader.py +0 -0
  39. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/asgi.py +0 -0
  40. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/__init__.py +0 -0
  41. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/blob.py +0 -0
  42. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/entrypoints.py +0 -0
  43. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/fetch.py +0 -0
  44. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/formdata.py +0 -0
  45. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/py.typed +0 -0
  46. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/request.py +0 -0
  47. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/response.py +0 -0
  48. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/rpc.py +0 -0
  49. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/types.py +0 -0
  50. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/utils.py +0 -0
  51. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/workflows.py +0 -0
  52. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/src/workers/wsgi.py +0 -0
  53. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/public/data.json +0 -0
  54. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/public/hello.txt +0 -0
  55. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/conftest.py +0 -0
  56. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_analytics_engine.py +0 -0
  57. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_assets.py +0 -0
  58. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_d1.py +0 -0
  59. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_do.py +0 -0
  60. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
  61. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
  62. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_images.py +0 -0
  63. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_kv.py +0 -0
  64. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_mtls.py +0 -0
  65. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_queue.py +0 -0
  66. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_r2.py +0 -0
  67. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_ratelimit.py +0 -0
  68. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_vectorize.py +0 -0
  69. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_version_metadata.py +0 -0
  70. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/test_workflow.py +0 -0
  71. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/worker_durable_object.py +0 -0
  72. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/src/worker_workflow.py +0 -0
  73. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/bindings-test/wrangler.jsonc +0 -0
  74. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/test_bindings.py +0 -0
  75. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/test_flask.py +0 -0
  76. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/test_web_frameworks.py +0 -0
  77. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
  78. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
  79. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
  80. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
  81. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
  82. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
  83. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
  84. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
  85. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
  86. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
  87. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
  88. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
  89. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
  90. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
  91. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
  92. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
  93. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
  94. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
  95. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
  96. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
  97. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
  98. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
  99. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
  100. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
  101. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
  102. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
  103. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
  104. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
  105. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
  106. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
  107. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
  108. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
  109. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
  110. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
  111. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
  112. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
  113. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
  114. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
  115. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
  116. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
  117. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
  118. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
  119. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
  120. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
  121. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
  122. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
  123. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -0
  124. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
  125. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
  126. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
  127. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
  128. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
  129. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
  130. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
  131. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +0 -0
  132. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
  133. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -0
  134. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
  135. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
  136. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
  137. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
  138. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
  139. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
  140. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/flask-tests/src/test_response.py +0 -0
  141. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/web-frameworks-test/flask-tests/wrangler.jsonc +0 -0
  142. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi/asgi.wd-test +0 -0
  143. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi/pyproject.toml +0 -0
  144. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi/worker.py +0 -0
  145. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
  146. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
  147. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
  148. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi-ws-disconnect/worker.py +0 -0
  149. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
  150. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
  151. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object/pyproject.toml +0 -0
  152. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object/worker.py +0 -0
  153. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
  154. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
  155. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
  156. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-abort/worker.py +0 -0
  157. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
  158. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
  159. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
  160. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
  161. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
  162. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
  163. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
  164. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
  165. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
  166. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
  167. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
  168. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
  169. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
  170. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
  171. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
  172. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
  173. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
  174. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
  175. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/python-rpc/worker.js +0 -0
  176. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/python-rpc/worker.py +0 -0
  177. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
  178. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/sdk/pyproject.toml +0 -0
  179. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/sdk/sdk.wd-test +0 -0
  180. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/sdk/server.py +0 -0
  181. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
  182. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/sdk/worker.py +0 -0
  183. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
  184. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/wsgi/pyproject.toml +0 -0
  185. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/wsgi/tests/test_wsgi.py +0 -0
  186. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/wsgi/worker.py +0 -0
  187. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
  188. {workers_runtime_sdk-1.8.2 → workers_runtime_sdk-1.8.4}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
@@ -2,6 +2,24 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.8.4 (2026-09-08)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **runtime-sdk**: Fix top-level entropy call for litestar and fix entropy tests
10
+ ([#241](https://github.com/cloudflare/workers-py/pull/241),
11
+ [`99b77c7`](https://github.com/cloudflare/workers-py/commit/99b77c7658d1e4b0591233b8ce6cc179719751e3))
12
+
13
+
14
+ ## v1.8.3 (2026-08-31)
15
+
16
+ ### Bug Fixes
17
+
18
+ - **runtime-sdk**: Stop losing details of ASGI lifespan failures
19
+ ([#172](https://github.com/cloudflare/workers-py/pull/172),
20
+ [`c6d1f69`](https://github.com/cloudflare/workers-py/commit/c6d1f69e96fd13b5b9e41f48e70aebb7469310c8))
21
+
22
+
5
23
  ## v1.8.2 (2026-08-31)
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.8.2
3
+ Version: 1.8.4
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.8.2"
7
+ version = "1.8.4"
8
8
  description = "Python SDK for Cloudflare Workers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -21,42 +21,18 @@ dev = [
21
21
  "ruff>=0.1.0",
22
22
  "mypy>=1.0.0",
23
23
  "requests>=2.28.0",
24
+ "testlib[host]",
24
25
  ]
25
26
 
27
+ [tool.uv.sources]
28
+ testlib = { path = "../testlib", editable = true }
29
+
26
30
  [project.urls]
27
31
  "Homepage" = "https://github.com/cloudflare/workers-py"
28
32
  "Bug Tracker" = "https://github.com/cloudflare/workers-py/issues"
29
33
 
30
34
  [tool.ruff]
31
- target-version = "py311"
32
- lint.select = [
33
- "B0", # bugbear (all B0* checks enabled by default)
34
- "B904", # bugbear (Within an except clause, raise exceptions with raise ... from err)
35
- "B905", # bugbear (zip() without an explicit strict= parameter set.)
36
- "C4", # flake8-comprehensions
37
- "C9", # mccabe complexity
38
- "E", # pycodestyles
39
- "F", # pyflakes
40
- "I", # isort
41
- "PERF", # Perflint
42
- "PGH", # pygrep-hooks
43
- "PL", # Pylint
44
- "UP", # pyupgrade
45
- "W", # pycodestyles
46
- ]
47
- lint.ignore = [
48
- "C901", # Too complex
49
- "E402",
50
- "E501",
51
- "E731",
52
- "E741",
53
- "PLR0911", # Too many return statements
54
- "PLR0915", # Too many statements
55
- "PLW2901",
56
- "UP031",
57
- "UP038", # Use X | Y instead of (X, Y)
58
- ]
59
- lint.flake8-comprehensions.allow-dict-calls-with-keyword-arguments = true
35
+ extend = "../../ruff.toml"
60
36
  lint.per-file-ignores."tests/**" = ["PLR2004", "PLR0913"]
61
37
  lint.per-file-ignores."tests/workerd-test/**" = ["C901", "PLR0911", "PLR0912", "PLR2004", "PLW0603"]
62
38
  lint.per-file-ignores."tests/bindings-test/**" = ["C901", "PLR0911", "PLR0912", "PLR2004", "PLW0603"]
@@ -178,3 +178,14 @@ def langsmith__internal__constants_context(module):
178
178
  def langchain_openai_chat_models_base_context(module):
179
179
  with allow_bad_entropy_calls(1):
180
180
  yield
181
+
182
+
183
+ @register_exec_patch("litestar.openapi.controller")
184
+ @register_exec_patch("litestar.constants")
185
+ @contextmanager
186
+ def litestar_context(module):
187
+ # Uses os.urandom() to generate a OPENAPI_JSON_HANDLER_NAME
188
+ # https://github.com/litestar-org/litestar/blob/e3b6a1d103a9160a575dce01ae34839e6e9bf990/litestar/constants.py#L19
189
+ # https://github.com/litestar-org/litestar/blob/v2.24.0/litestar/openapi/controller.py#L27
190
+ with allow_bad_entropy_calls(1):
191
+ yield
@@ -127,7 +127,9 @@ async def start_application(app):
127
127
  startup.set_result(True)
128
128
  return
129
129
  if got["type"] == "lifespan.startup.failed":
130
- message = got.get("message", "ASGI lifespan startup failed")
130
+ # `or`, not a .get default: an empty message field is legal and
131
+ # would otherwise produce RuntimeError('').
132
+ message = got.get("message") or "ASGI lifespan startup failed"
131
133
  if not startup.done():
132
134
  startup.set_exception(RuntimeError(message))
133
135
  return
@@ -136,7 +138,7 @@ async def start_application(app):
136
138
  shutdown_complete.set_result(None)
137
139
  return
138
140
  if got["type"] == "lifespan.shutdown.failed":
139
- message = got.get("message", "ASGI lifespan shutdown failed")
141
+ message = got.get("message") or "ASGI lifespan shutdown failed"
140
142
  if not shutdown_complete.done():
141
143
  shutdown_complete.set_exception(RuntimeError(message))
142
144
  return
@@ -161,8 +163,15 @@ async def start_application(app):
161
163
  shutdown_complete.set_result(None)
162
164
  except Exception as exc:
163
165
  # Spec: an exception raised before startup is acked signals that the
164
- # app doesn't support lifespan; swallow it and serve requests anyway.
166
+ # app doesn't support lifespan; serve requests anyway. Log it, or an
167
+ # app that crashed mid-startup looks like one with no lifespan
168
+ # handler at all.
165
169
  if not startup.done():
170
+ logger.debug(
171
+ "ASGI lifespan task raised before reporting startup; "
172
+ "treating the app as not supporting lifespan",
173
+ exc_info=exc,
174
+ )
166
175
  startup.set_result(False)
167
176
  return
168
177
  # After a successful startup, a shutdown-phase error can't affect the
@@ -2,4 +2,7 @@
2
2
  name = "bindings-test"
3
3
  version = "0.1.0"
4
4
  requires-python = ">=3.12"
5
- dependencies = ["pytest", "pytest-asyncio<1.2.0", "pg8000", "pymysql", "cryptography"]
5
+ dependencies = ["pytest", "pytest-asyncio<1.2.0", "pg8000", "pymysql", "cryptography", "testlib"]
6
+
7
+ [tool.uv.sources]
8
+ testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
@@ -0,0 +1,32 @@
1
+ """Bindings test worker.
2
+
3
+ Each binding suite lives in a `test_<suite>.py` module written as ordinary pytest
4
+ tests (see test_kv.py). The `/run-tests/<suite>` endpoint runs pytest against that
5
+ module inside workerd and returns per-test results as JSON, which the host-side
6
+ test_bindings.py maps onto individual pytest cases.
7
+
8
+ To add a new binding: create `src/test_<binding>.py` with pytest tests.
9
+ """
10
+
11
+ from testlib.entrypoint import TestRunnerEntrypoint
12
+ from worker_durable_object import (
13
+ TestDurableObject, # noqa: F401 - import to trigger side effect of registering the Durable Object
14
+ )
15
+ from worker_workflow import (
16
+ TestWorkflow, # noqa: F401 - import to trigger side effect of registering the Workflow
17
+ )
18
+
19
+ RECEIVED_MESSAGES = []
20
+
21
+
22
+ class Default(TestRunnerEntrypoint):
23
+ async def queue(self, batch, env, ctx):
24
+ for message in batch.messages:
25
+ RECEIVED_MESSAGES.append(
26
+ {
27
+ "id": message.id,
28
+ "body": message.body,
29
+ "attempts": message.attempts,
30
+ }
31
+ )
32
+ message.ack()
@@ -0,0 +1,116 @@
1
+ """Shared fixtures and helpers for the host-side test suite."""
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ from collections.abc import Generator
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import pytest
11
+ from testlib.host import (
12
+ COMPAT_CONFIGS,
13
+ CompatConfig,
14
+ configure_compatibility,
15
+ )
16
+ from testlib.host import (
17
+ dev_server as run_dev_server,
18
+ )
19
+ from testlib.host import (
20
+ register_in_worker_suites as register_testlib_suites,
21
+ )
22
+
23
+ TEST_DIR: Path = Path(__file__).parent
24
+ WORKERS_PY: Path = TEST_DIR.parent.parent / "cli"
25
+ WORKERS_RUNTIME_SDK: Path = TEST_DIR.parent / "src"
26
+ TESTLIB: Path = TEST_DIR.parent.parent / "testlib"
27
+
28
+ DEV_STARTUP_TIMEOUT: int = 120
29
+ OPT_IN_MARKERS: tuple[str, ...] = ("hyperdrive",)
30
+
31
+
32
+ def pytest_collection_modifyitems(
33
+ config: pytest.Config, items: list[pytest.Item]
34
+ ) -> None:
35
+ """Skip opt-in suites unless the run explicitly asks for them via ``-m``."""
36
+ markexpr: str = config.getoption("markexpr")
37
+ for marker in OPT_IN_MARKERS:
38
+ if marker in markexpr:
39
+ continue
40
+ skip = pytest.mark.skip(reason=f"needs local services; run with -m {marker}")
41
+ for item in items:
42
+ if marker in item.keywords:
43
+ item.add_marker(skip)
44
+
45
+
46
+ @pytest.fixture(scope="session", autouse=True)
47
+ def build_testlib():
48
+ subprocess.run(["uv", "build"], cwd=TESTLIB, check=True)
49
+
50
+
51
+ @pytest.fixture(
52
+ scope="module",
53
+ params=COMPAT_CONFIGS,
54
+ ids=[c.python_version for c in COMPAT_CONFIGS],
55
+ )
56
+ def compat_config(request: pytest.FixtureRequest) -> CompatConfig:
57
+ return request.param
58
+
59
+
60
+ @pytest.fixture(scope="module")
61
+ def worker_project_dir() -> Path:
62
+ """Worker project the `dev_server` fixture should serve.
63
+
64
+ Test modules using `dev_server` must override this fixture.
65
+ """
66
+ raise NotImplementedError(
67
+ "override the `worker_project_dir` fixture in your test module"
68
+ )
69
+
70
+
71
+ @pytest.fixture(scope="module")
72
+ def dev_server(
73
+ tmp_path_factory: pytest.TempPathFactory,
74
+ worker_project_dir: Path,
75
+ compat_config: CompatConfig,
76
+ ) -> Generator[str]:
77
+ """Start a pywrangler dev server on a free port and yield its base URL."""
78
+ tmp_path = tmp_path_factory.mktemp(f"{worker_project_dir.name}_dev")
79
+ target = tmp_path / worker_project_dir.name
80
+ shutil.copytree(worker_project_dir, target, ignore=shutil.ignore_patterns(".venv"))
81
+ shutil.copytree(TESTLIB, tmp_path / "testlib")
82
+ env = os.environ | {"_PYODIDE_EXTRA_MOUNTS": str(tmp_path)}
83
+
84
+ wrangler_jsonc = target / "wrangler.jsonc"
85
+ configure_compatibility(wrangler_jsonc, compat_config)
86
+
87
+ pywrangler_cmd = ["uv", "run", "--no-project", "--with", WORKERS_PY, "pywrangler"]
88
+
89
+ subprocess.run(
90
+ [*pywrangler_cmd, "sync"],
91
+ cwd=target,
92
+ check=True,
93
+ env=env,
94
+ )
95
+
96
+ shutil.copytree(WORKERS_RUNTIME_SDK, target / "python_modules", dirs_exist_ok=True)
97
+
98
+ with run_dev_server(
99
+ target,
100
+ tmp_path,
101
+ env,
102
+ pywrangler_cmd,
103
+ startup_timeout=DEV_STARTUP_TIMEOUT,
104
+ readiness_path="/health",
105
+ require_success=True,
106
+ log_name="dev.log",
107
+ ) as (base_url, _):
108
+ yield base_url
109
+
110
+
111
+ def register_in_worker_suites(
112
+ namespace: dict[str, Any],
113
+ src_dir: Path,
114
+ marks: dict[str, pytest.MarkDecorator] | None = None,
115
+ ) -> None:
116
+ register_testlib_suites(namespace, src_dir, marks=marks, class_name=str.upper)
@@ -4,9 +4,6 @@ Python 3.12 (Pyodide 0.26.0a2) is excluded. The in-worker pytest suite drives
4
4
  async tests via ``loop.run_until_complete``, which is a no-op on Pyodide 0.26.0a2
5
5
  (no ``run_sync``/JSPI): async tests return unawaited futures and report false
6
6
  passes.
7
-
8
- Python 3.14 is excluded because pydantic_core has a circular import error on
9
- the 3.14 Pyodide runtime (``validate_core_schema`` not found during module init).
10
7
  """
11
8
 
12
9
  from pathlib import Path
@@ -26,9 +23,8 @@ def worker_project_dir() -> Path:
26
23
 
27
24
 
28
25
  # Exclude Python 3.12 (Pyodide 0.26.0a2) for async tests.
29
- # Exclude Python 3.14 due to pydantic_core circular import error on Pyodide.
30
26
  FASTAPI_COMPAT_CONFIGS = [
31
- c for c in COMPAT_CONFIGS if c.python_version not in ("3.12", "3.14")
27
+ c for c in COMPAT_CONFIGS if c.python_version not in ("3.12",)
32
28
  ]
33
29
 
34
30
 
@@ -7,8 +7,7 @@ import pytest
7
7
  from conftest import (
8
8
  COMPAT_CONFIGS,
9
9
  CompatConfig,
10
- inject_compat_flags,
11
- replace_compat_date,
10
+ configure_compatibility,
12
11
  )
13
12
 
14
13
  TEST_DIR = Path(__file__).parent
@@ -62,7 +61,7 @@ def bundle_cache_dir(tmp_path_factory):
62
61
  ids=[c.python_version for c in COMPAT_CONFIGS],
63
62
  )
64
63
  @pytest.mark.parametrize("test_dir, wd_test_file", discover_workerd_tests())
65
- def test_in_workerd( # noqa: PLR0913 (too-many-arguments)
64
+ def test_in_workerd( # noqa: PLR0913, PLR0917 (too-many-arguments)
66
65
  tmp_path,
67
66
  test_dir,
68
67
  wd_test_file,
@@ -71,22 +70,27 @@ def test_in_workerd( # noqa: PLR0913 (too-many-arguments)
71
70
  bundle_cache_dir,
72
71
  ):
73
72
  compat_date = compat_config.compat_date
73
+ python_version = compat_config.python_version
74
74
 
75
75
  # `wsgi` streams the request body via `pyodide.ffi.run_sync` (JSPI), which
76
76
  # is only available in newer Pyodide runtimes.
77
- if test_dir.name == "wsgi" and compat_date < "2026-01-01":
77
+ if test_dir.name == "wsgi" and python_version == "3.12":
78
78
  pytest.skip(
79
79
  "wsgi requires pyodide.ffi.run_sync (JSPI), unavailable before 2026-01-01"
80
80
  )
81
81
 
82
+ if test_dir.name == "entropy-patches" and python_version == "3.14":
83
+ pytest.skip(
84
+ "TODO: enable me after https://github.com/cloudflare/workerd/pull/7200 lands in wrangler"
85
+ )
86
+
82
87
  color = pytestconfig.get_terminal_writer().hasmarkup
83
88
  target = tmp_path / test_dir.name
84
89
  disk_service_dir = target / DISK_SERVICE_NAME
85
90
  shutil.copytree(test_dir, target, ignore=shutil.ignore_patterns(".venv"))
86
91
  disk_service_dir.mkdir(exist_ok=True)
87
92
 
88
- replace_compat_date(target / "wrangler.jsonc", compat_date)
89
- inject_compat_flags(target / "wrangler.jsonc", compat_config.extra_compat_flags)
93
+ configure_compatibility(target / "wrangler.jsonc", compat_config)
90
94
 
91
95
  pywrangler_cmd = ["uv", "run", "--no-project", "--with", WORKERS_PY, "pywrangler"]
92
96
 
@@ -114,7 +118,7 @@ def test_in_workerd( # noqa: PLR0913 (too-many-arguments)
114
118
  .replace("%COLOR", str(color).lower())
115
119
  .replace("%COMPAT_DATE", compat_date)
116
120
  )
117
- inject_compat_flags(wd_config, compat_config.extra_compat_flags)
121
+ configure_compatibility(wd_config, compat_config)
118
122
 
119
123
  subprocess.run(
120
124
  ["npm", "i", "workerd"],
@@ -10,4 +10,8 @@ dependencies = [
10
10
  "django-cors-headers",
11
11
  "pytest",
12
12
  "pytest-asyncio<1.2.0",
13
+ "testlib",
13
14
  ]
15
+
16
+ [tool.uv.sources]
17
+ testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
@@ -0,0 +1,104 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+ from testlib.entrypoint import TestRunnerEntrypoint
5
+
6
+ import asgi
7
+
8
+ BASE_DIR = Path(__file__).parent
9
+ TEMPLATES_DIR = str(BASE_DIR / "templates")
10
+
11
+
12
+ import django.conf # noqa: E402
13
+
14
+ django.conf.settings.configure(
15
+ DEBUG=False,
16
+ SECRET_KEY="test-secret-key-for-workers-py",
17
+ ROOT_URLCONF="django_app.urls",
18
+ ALLOWED_HOSTS=["*"],
19
+ INSTALLED_APPS=[
20
+ "django.contrib.contenttypes",
21
+ "django.contrib.sessions",
22
+ "django.contrib.auth",
23
+ "rest_framework",
24
+ "corsheaders",
25
+ ],
26
+ MIDDLEWARE=[
27
+ "corsheaders.middleware.CorsMiddleware",
28
+ "django.middleware.security.SecurityMiddleware",
29
+ "django.contrib.sessions.middleware.SessionMiddleware",
30
+ "django.middleware.common.CommonMiddleware",
31
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
32
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
33
+ "django_app.middleware.CustomAsyncMiddleware",
34
+ ],
35
+ TEMPLATES=[
36
+ {
37
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
38
+ "DIRS": [TEMPLATES_DIR],
39
+ "APP_DIRS": False,
40
+ "OPTIONS": {
41
+ "context_processors": [
42
+ "django.template.context_processors.request",
43
+ ],
44
+ "builtins": ["django_app.templatetags.custom_tags"],
45
+ },
46
+ }
47
+ ],
48
+ CACHES={
49
+ "default": {
50
+ "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
51
+ }
52
+ },
53
+ SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies",
54
+ AUTHENTICATION_BACKENDS=["django_app.auth_backend.InMemoryBackend"],
55
+ DATABASES={},
56
+ TIME_ZONE="UTC",
57
+ USE_TZ=True,
58
+ DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
59
+ CORS_ALLOWED_ORIGINS=[
60
+ "http://localhost:3000",
61
+ "https://example.com",
62
+ ],
63
+ CORS_ALLOW_CREDENTIALS=True,
64
+ CORS_ALLOWED_ORIGIN_REGEXES=[r"^https://.*\.example\.com$"],
65
+ REST_FRAMEWORK={
66
+ "DEFAULT_RENDERER_CLASSES": ["rest_framework.renderers.JSONRenderer"],
67
+ "DEFAULT_PARSER_CLASSES": [
68
+ "rest_framework.parsers.JSONParser",
69
+ "rest_framework.parsers.FormParser",
70
+ "rest_framework.parsers.MultiPartParser",
71
+ ],
72
+ "DEFAULT_THROTTLE_RATES": {
73
+ "anon": "10/min",
74
+ "user": "100/min",
75
+ },
76
+ },
77
+ )
78
+
79
+ import django # noqa: E402
80
+
81
+ django.setup()
82
+
83
+ from django.core.handlers.asgi import ASGIHandler # noqa: E402
84
+
85
+ django_asgi_app = ASGIHandler()
86
+
87
+
88
+ class DjangoAppPlugin:
89
+ @pytest.fixture
90
+ def django_asgi_app(self):
91
+ return django_asgi_app
92
+
93
+
94
+ class Default(TestRunnerEntrypoint):
95
+ app = django_asgi_app
96
+
97
+ async def fetch(self, request):
98
+ result = await super().fetch(request)
99
+ if result.status == 404:
100
+ result = await asgi.fetch(django_asgi_app, request, self.env, self.ctx)
101
+ return result
102
+
103
+ def plugins(self):
104
+ return super().plugins() + [DjangoAppPlugin()]
@@ -7,4 +7,8 @@ dependencies = [
7
7
  "python-multipart",
8
8
  "pytest",
9
9
  "pytest-asyncio<1.2.0",
10
+ "testlib",
10
11
  ]
12
+
13
+ [tool.uv.sources]
14
+ testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  import enum
3
3
  import hashlib
4
- import importlib.util
5
4
  import json
6
5
  from contextlib import asynccontextmanager
7
6
  from pathlib import Path
@@ -34,6 +33,7 @@ from pydantic import BaseModel
34
33
  from pyodide.webloop import WebLoop
35
34
  from starlette.background import BackgroundTask
36
35
  from starlette.middleware.gzip import GZipMiddleware
36
+ from testlib.entrypoint import TestRunner
37
37
 
38
38
  import asgi
39
39
 
@@ -585,57 +585,6 @@ async def native_file():
585
585
  app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
586
586
 
587
587
 
588
- class ResultCollector:
589
- def __init__(self):
590
- self.results = {}
591
-
592
- @staticmethod
593
- def _key(item):
594
- name = item.name
595
- return name[len("test_") :] if name.startswith("test_") else name
596
-
597
- @pytest.hookimpl(hookwrapper=True)
598
- def pytest_runtest_makereport(self, item, call):
599
- outcome = yield
600
- report = outcome.get_result()
601
- key = self._key(item)
602
-
603
- if report.when == "call":
604
- if report.passed:
605
- self.results[key] = {"status": "passed"}
606
- elif report.skipped:
607
- self.results[key] = {
608
- "status": "skipped",
609
- "reason": str(report.longrepr),
610
- }
611
- elif report.failed:
612
- excinfo = call.excinfo
613
- if excinfo is not None and excinfo.errisinstance(AssertionError):
614
- self.results[key] = {
615
- "status": "failed",
616
- "error": str(excinfo.value),
617
- }
618
- else:
619
- self.results[key] = {
620
- "status": "error",
621
- "error": f"{excinfo.typename}: {excinfo.value}"
622
- if excinfo is not None
623
- else "unknown error",
624
- "traceback": report.longreprtext,
625
- }
626
- elif report.when in ("setup", "teardown") and report.skipped:
627
- self.results[key] = {
628
- "status": "skipped",
629
- "reason": str(report.longrepr),
630
- }
631
- elif report.when in ("setup", "teardown") and report.failed:
632
- self.results[key] = {
633
- "status": "error",
634
- "error": report.longreprtext,
635
- "traceback": report.longreprtext,
636
- }
637
-
638
-
639
588
  class EnvPlugin:
640
589
  def __init__(self, env):
641
590
  self._env = env
@@ -653,27 +602,9 @@ class FastAPIAppPlugin:
653
602
 
654
603
  @app.get("/run-tests/{suite_name:path}")
655
604
  async def run_suite(suite_name: str, request: Request):
656
- module = f"test_{suite_name}"
657
- if importlib.util.find_spec(module) is None:
658
- return JSONResponse(
659
- {"error": f"Unknown suite '{suite_name}' (no module '{module}')"},
660
- status_code=404,
661
- )
662
-
663
- collector = ResultCollector()
664
- saved_loop = asyncio.events._get_running_loop()
665
- try:
666
- pytest.main(
667
- ["--pyargs", module, "-p", "no:cacheprovider"],
668
- plugins=[
669
- collector,
670
- EnvPlugin(request.scope["env"]),
671
- FastAPIAppPlugin(),
672
- ],
673
- )
674
- finally:
675
- asyncio.events._set_running_loop(saved_loop)
676
- return collector.results
605
+ runner = TestRunner(request.scope["env"])
606
+ result = runner.run_suite(suite_name)
607
+ return JSONResponse(result.payload, status_code=result.status)
677
608
 
678
609
 
679
610
  @app.get("/{path:path}")
@@ -0,0 +1,8 @@
1
+ [project]
2
+ name = "flask-tests"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.12"
5
+ dependencies = ["flask", "pytest", "testlib"]
6
+
7
+ [tool.uv.sources]
8
+ testlib = { path = "../testlib/dist/testlib-0.0.0-py3-none-any.whl" }
@@ -0,0 +1,5 @@
1
+ from testlib.entrypoint import TestRunnerEntrypoint
2
+
3
+
4
+ class Default(TestRunnerEntrypoint):
5
+ pass
@@ -428,3 +428,49 @@ async def test_multiple_set_cookie_headers_survive():
428
428
  "first=1; Path=/",
429
429
  "second=2; Path=/",
430
430
  ]
431
+
432
+
433
+ class _StartupFailEmptyMessageApp:
434
+ async def __call__(self, scope, receive, send):
435
+ if scope["type"] == "lifespan":
436
+ message = await receive()
437
+ if message["type"] == "lifespan.startup":
438
+ await send({"type": "lifespan.startup.failed", "message": ""})
439
+ return
440
+
441
+
442
+ @pytest.mark.asyncio
443
+ async def test_lifespan_startup_failure_with_empty_message():
444
+ req = js.Request.new("http://example.com/startup-fail-empty")
445
+ with pytest.raises(RuntimeError, match="ASGI lifespan startup failed"):
446
+ await asyncio.wait_for(
447
+ asgi.fetch(_StartupFailEmptyMessageApp(), req, env), timeout=5
448
+ )
449
+
450
+
451
+ class _PreAckCrashApp:
452
+ """Raises on the lifespan scope before any ack; HTTP must still serve."""
453
+
454
+ async def __call__(self, scope, receive, send):
455
+ if scope["type"] == "lifespan":
456
+ raise RuntimeError("crashed before ack")
457
+ await receive()
458
+ await send({"type": "http.response.start", "status": 200, "headers": []})
459
+ await send({"type": "http.response.body", "body": b"ok"})
460
+
461
+
462
+ @pytest.mark.asyncio
463
+ async def test_lifespan_preack_crash_is_logged_and_treated_as_unsupported():
464
+ handler = _install_handler()
465
+ try:
466
+ req = js.Request.new("http://example.com/preack-crash")
467
+ response = await asyncio.wait_for(
468
+ asgi.fetch(_PreAckCrashApp(), req, env), timeout=5
469
+ )
470
+ assert await response.text() == "ok"
471
+ assert any(
472
+ "before reporting startup" in record.getMessage()
473
+ for record in handler.records
474
+ )
475
+ finally:
476
+ _remove_handler(handler)