workers-runtime-sdk 1.6.15__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/CHANGELOG.md +35 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/PKG-INFO +1 -1
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/pyproject.toml +1 -1
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/asgi.py +72 -15
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/request.py +9 -3
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/response.py +2 -19
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/utils.py +20 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/wsgi.py +14 -3
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/_client.py +49 -0
- workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +99 -0
- workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +31 -0
- workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +46 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +9 -0
- workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +39 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/worker.py +176 -46
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +8 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/worker.py +15 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/tests/test_sdk.py +5 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/tests/test_wsgi.py +19 -3
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/worker.py +21 -9
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/uv.lock +1 -1
- workers_runtime_sdk-1.6.15/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -12
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/.gitignore +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/AGENTS.md +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/README.md +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_cloudflare_compat_flags.pyi +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_entropy_import_context.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_package_patches.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.pth +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/asgi.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/__init__.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/blob.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/entrypoints.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/fetch.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/formdata.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/py.typed +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/rpc.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/types.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/src/workers/workflows.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/data.json +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/hello.txt +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/conftest.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_analytics_engine.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_d1.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_do.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_images.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_kv.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_mtls.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_queue.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_r2.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_ratelimit.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_vectorize.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_version_metadata.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_workflow.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_durable_object.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_workflow.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/bindings-test/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/conftest.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_bindings.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_fastapi.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_in_workerd.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/test_web_frameworks.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/asgi.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.js +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/sdk.wd-test +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/server.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/worker.py +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.15 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.8.0 (2026-08-28)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Defer ASGI lifespan shutdown until request completion
|
|
10
|
+
([`3249184`](https://github.com/cloudflare/workers-py/commit/324918442748a0b87affc6128315ceb190e1b368))
|
|
11
|
+
|
|
12
|
+
- Make wsgi streaming response handlers async
|
|
13
|
+
([#191](https://github.com/cloudflare/workers-py/pull/191),
|
|
14
|
+
[`12168ae`](https://github.com/cloudflare/workers-py/commit/12168aef7599067a910a518f1ce7860a00ae9a58))
|
|
15
|
+
|
|
16
|
+
- **runtime-sdk**: Close the WebSocket transport when the app task ends
|
|
17
|
+
([#166](https://github.com/cloudflare/workers-py/pull/166),
|
|
18
|
+
[`e67e7e7`](https://github.com/cloudflare/workers-py/commit/e67e7e78af7fa6976f1f9966508b9869a6840ce5))
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Add wsgi.entrypoint like asgi.entrypoint
|
|
23
|
+
([#232](https://github.com/cloudflare/workers-py/pull/232),
|
|
24
|
+
[`e044561`](https://github.com/cloudflare/workers-py/commit/e04456174b3f5bc9df8b3f8293ced6d08aaffc0f))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## v1.7.0 (2026-08-24)
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
- Fixes Request body being released too early
|
|
32
|
+
([`84a20ac`](https://github.com/cloudflare/workers-py/commit/84a20acbfa28ea82fb69b2c279a56d9e25fd9639))
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
- Asgi.entrypoint syntax sugar for nicer asgi apps
|
|
37
|
+
([`5399270`](https://github.com/cloudflare/workers-py/commit/539927085a79e2d88a287cc70ccfa433ec257e1d))
|
|
38
|
+
|
|
39
|
+
|
|
5
40
|
## v1.6.15 (2026-08-21)
|
|
6
41
|
|
|
7
42
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workers-runtime-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Python SDK for Cloudflare Workers
|
|
5
5
|
Project-URL: Homepage, https://github.com/cloudflare/workers-py
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/cloudflare/workers-py/issues
|
|
@@ -7,7 +7,7 @@ from urllib.parse import unquote
|
|
|
7
7
|
|
|
8
8
|
import js
|
|
9
9
|
|
|
10
|
-
from workers import Context, Request
|
|
10
|
+
from workers import Context, Request, WorkerEntrypoint
|
|
11
11
|
from workers.utils import _to_js_headers
|
|
12
12
|
|
|
13
13
|
ASGI = {"spec_version": "2.0", "version": "3.0"}
|
|
@@ -16,7 +16,7 @@ logger = logging.getLogger("asgi")
|
|
|
16
16
|
background_tasks = set()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def run_in_background(coro: Awaitable[Any]) ->
|
|
19
|
+
def run_in_background(coro: Awaitable[Any]) -> Future:
|
|
20
20
|
fut = ensure_future(coro)
|
|
21
21
|
background_tasks.add(fut)
|
|
22
22
|
|
|
@@ -27,6 +27,7 @@ def run_in_background(coro: Awaitable[Any]) -> None:
|
|
|
27
27
|
logger.error("Unhandled exception in background task", exc_info=exc)
|
|
28
28
|
|
|
29
29
|
fut.add_done_callback(_on_done)
|
|
30
|
+
return fut
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
async def close_stream_quietly(writer):
|
|
@@ -185,7 +186,7 @@ async def process_request( # noqa: PLR0913
|
|
|
185
186
|
# TODO(later): remove this parameter after unvendoring Python SDK from workerd
|
|
186
187
|
ctx: Context | None,
|
|
187
188
|
state: dict[str, Any] | None = None,
|
|
188
|
-
) -> js.Response:
|
|
189
|
+
) -> tuple[js.Response, Future[None]]:
|
|
189
190
|
from js import Response, TransformStream
|
|
190
191
|
from pyodide.ffi import create_proxy
|
|
191
192
|
|
|
@@ -304,17 +305,22 @@ async def process_request( # noqa: PLR0913
|
|
|
304
305
|
# runtime is waiting on through wait_until.
|
|
305
306
|
run_in_background(close_stream_quietly(writer))
|
|
306
307
|
|
|
307
|
-
|
|
308
|
-
app_task = create_proxy(create_task(run_app()))
|
|
309
|
-
|
|
310
|
-
from workers import wait_until
|
|
311
|
-
|
|
312
|
-
wait_until(app_task)
|
|
308
|
+
request_task = create_task(run_app())
|
|
313
309
|
|
|
314
310
|
try:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
311
|
+
response = await result
|
|
312
|
+
except Exception:
|
|
313
|
+
# The request task handles application exceptions and should be allowed
|
|
314
|
+
# to finish before its error is propagated to fetch().
|
|
315
|
+
await request_task
|
|
316
|
+
raise
|
|
317
|
+
|
|
318
|
+
# Buffered responses do not depend on a consumer, so request cleanup can
|
|
319
|
+
# finish before returning. Streaming responses must run alongside the
|
|
320
|
+
# consumer because writes can be subject to backpressure.
|
|
321
|
+
if writer is None:
|
|
322
|
+
await request_task
|
|
323
|
+
return response, request_task
|
|
318
324
|
|
|
319
325
|
|
|
320
326
|
async def process_websocket(
|
|
@@ -359,7 +365,10 @@ async def process_websocket(
|
|
|
359
365
|
server.onclose = onclose
|
|
360
366
|
server.onmessage = onmessage
|
|
361
367
|
|
|
368
|
+
app_closed = False
|
|
369
|
+
|
|
362
370
|
async def ws_send(got):
|
|
371
|
+
nonlocal app_closed
|
|
363
372
|
if got["type"] == "websocket.accept":
|
|
364
373
|
# The Workers WebSocketPair is accepted before the upgrade response
|
|
365
374
|
# is returned, so there is no deferred accept operation here.
|
|
@@ -376,6 +385,7 @@ async def process_websocket(
|
|
|
376
385
|
server.send(s)
|
|
377
386
|
return
|
|
378
387
|
if got["type"] == "websocket.close":
|
|
388
|
+
app_closed = True
|
|
379
389
|
server.close(got.get("code", 1000), got.get("reason", ""))
|
|
380
390
|
return
|
|
381
391
|
logger.warning(" == Not implemented %s", got["type"])
|
|
@@ -384,13 +394,34 @@ async def process_websocket(
|
|
|
384
394
|
received = await queue.get()
|
|
385
395
|
return received
|
|
386
396
|
|
|
387
|
-
|
|
397
|
+
from pyodide.ffi import create_proxy
|
|
398
|
+
|
|
399
|
+
from workers import wait_until
|
|
400
|
+
|
|
401
|
+
task = run_in_background(
|
|
388
402
|
app(
|
|
389
403
|
request_to_scope(req, env if env is not None else {}, ws=True),
|
|
390
404
|
ws_receive,
|
|
391
405
|
ws_send,
|
|
392
406
|
)
|
|
393
407
|
)
|
|
408
|
+
# wait_until is JS's waitUntil, so the task crosses the boundary as a proxy,
|
|
409
|
+
# destroyed once it settles rather than left to leak.
|
|
410
|
+
task_proxy = create_proxy(task)
|
|
411
|
+
|
|
412
|
+
def _close_transport(finished):
|
|
413
|
+
if not app_closed:
|
|
414
|
+
# The app ended without closing, so a half-open connection would
|
|
415
|
+
# leave the client hanging instead of reconnecting.
|
|
416
|
+
failed = not finished.cancelled() and finished.exception() is not None
|
|
417
|
+
try:
|
|
418
|
+
server.close(1011 if failed else 1000, "")
|
|
419
|
+
except Exception:
|
|
420
|
+
pass # the peer may already have closed the socket
|
|
421
|
+
task_proxy.destroy()
|
|
422
|
+
|
|
423
|
+
task.add_done_callback(_close_transport)
|
|
424
|
+
wait_until(task_proxy)
|
|
394
425
|
|
|
395
426
|
return Response.new(None, status=101, webSocket=client)
|
|
396
427
|
|
|
@@ -401,11 +432,25 @@ async def fetch(
|
|
|
401
432
|
logger.debug("ASGI request: %s %s", req.method, req.url)
|
|
402
433
|
shutdown, state = await start_application(app)
|
|
403
434
|
try:
|
|
404
|
-
result = await process_request(app, req, env, ctx, state=state)
|
|
435
|
+
result, request_task = await process_request(app, req, env, ctx, state=state)
|
|
405
436
|
except Exception:
|
|
406
437
|
logger.exception("ASGI request failed")
|
|
438
|
+
await shutdown()
|
|
407
439
|
raise
|
|
408
|
-
|
|
440
|
+
|
|
441
|
+
if request_task.done():
|
|
442
|
+
await shutdown()
|
|
443
|
+
else:
|
|
444
|
+
from workers import wait_until # noqa: PLC0415
|
|
445
|
+
|
|
446
|
+
async def finalize_request():
|
|
447
|
+
try:
|
|
448
|
+
await request_task
|
|
449
|
+
finally:
|
|
450
|
+
await shutdown()
|
|
451
|
+
|
|
452
|
+
wait_until(run_in_background(finalize_request()))
|
|
453
|
+
|
|
409
454
|
return result
|
|
410
455
|
|
|
411
456
|
|
|
@@ -415,6 +460,18 @@ async def websocket(
|
|
|
415
460
|
return await process_websocket(app, req, env)
|
|
416
461
|
|
|
417
462
|
|
|
463
|
+
def entrypoint(app: Any) -> type[WorkerEntrypoint]:
|
|
464
|
+
"""Create the default Worker entrypoint for an ASGI application."""
|
|
465
|
+
|
|
466
|
+
class Default(WorkerEntrypoint):
|
|
467
|
+
async def fetch(self, request):
|
|
468
|
+
if (request.headers.get("upgrade") or "").lower() == "websocket":
|
|
469
|
+
return await websocket(app, request, self.env)
|
|
470
|
+
return await fetch(app, request, self.env, self.ctx)
|
|
471
|
+
|
|
472
|
+
return Default
|
|
473
|
+
|
|
474
|
+
|
|
418
475
|
def __getattr__(name):
|
|
419
476
|
if name == "env":
|
|
420
477
|
from fastapi import Depends, Request
|
|
@@ -9,6 +9,7 @@ from .blob import Blob
|
|
|
9
9
|
from .formdata import FormData
|
|
10
10
|
from .types import FetchKwargs
|
|
11
11
|
from .utils import (
|
|
12
|
+
_get_js_body,
|
|
12
13
|
_is_js_instance,
|
|
13
14
|
_js_headers_to_http_message,
|
|
14
15
|
_jsnull_to_none,
|
|
@@ -36,9 +37,14 @@ class Request:
|
|
|
36
37
|
|
|
37
38
|
if "headers" in other_options:
|
|
38
39
|
other_options["headers"] = _to_js_headers(other_options["headers"])
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
body = other_options.pop("body", None)
|
|
41
|
+
with _get_js_body(body) as js_body:
|
|
42
|
+
if body is not None:
|
|
43
|
+
other_options["body"] = js_body
|
|
44
|
+
self._js_request = js.Request.new(
|
|
45
|
+
input._js_request if isinstance(input, Request) else input,
|
|
46
|
+
**other_options,
|
|
47
|
+
)
|
|
42
48
|
|
|
43
49
|
def __repr__(self):
|
|
44
50
|
return (
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from contextlib import contextmanager
|
|
3
2
|
from http import HTTPStatus
|
|
4
3
|
from typing import Any, Never
|
|
5
4
|
|
|
6
5
|
import js
|
|
7
6
|
import pyodide.http
|
|
8
|
-
from pyodide.ffi import JsException, JsProxy
|
|
7
|
+
from pyodide.ffi import JsException, JsProxy
|
|
9
8
|
|
|
10
9
|
from .blob import Blob
|
|
11
10
|
from .formdata import FormData
|
|
12
11
|
from .types import Body, Headers
|
|
13
12
|
from .utils import (
|
|
14
13
|
RESPONSE_ACCEPTED_TYPES,
|
|
14
|
+
_get_js_body,
|
|
15
15
|
_get_js_constructor_name,
|
|
16
16
|
_is_js_instance,
|
|
17
17
|
_js_headers_to_http_message,
|
|
@@ -21,23 +21,6 @@ from .utils import (
|
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
@contextmanager
|
|
25
|
-
def _get_js_body(body):
|
|
26
|
-
if isinstance(body, bytes):
|
|
27
|
-
proxy_bytes = create_proxy(body)
|
|
28
|
-
proxy_buffer = proxy_bytes.getBuffer()
|
|
29
|
-
try:
|
|
30
|
-
yield proxy_buffer.data
|
|
31
|
-
return
|
|
32
|
-
finally:
|
|
33
|
-
proxy_buffer.release()
|
|
34
|
-
proxy_bytes.destroy()
|
|
35
|
-
if isinstance(body, FormData):
|
|
36
|
-
yield body.js_object
|
|
37
|
-
return
|
|
38
|
-
yield body
|
|
39
|
-
|
|
40
|
-
|
|
41
24
|
class FetchResponse(pyodide.http.FetchResponse):
|
|
42
25
|
# TODO: Consider upstreaming the `body` attribute
|
|
43
26
|
# TODO: Behind a compat flag make this return a native stream (StreamReader?), or perhaps
|
|
@@ -6,6 +6,7 @@ import _pyodide_entrypoint_helper
|
|
|
6
6
|
import js
|
|
7
7
|
from pyodide.ffi import (
|
|
8
8
|
JsException,
|
|
9
|
+
create_proxy,
|
|
9
10
|
destroy_proxies,
|
|
10
11
|
to_js,
|
|
11
12
|
)
|
|
@@ -51,6 +52,25 @@ _JS_PASSTHROUGH_TYPES = RESPONSE_ACCEPTED_TYPES | {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
@contextmanager
|
|
56
|
+
def _get_js_body(body):
|
|
57
|
+
from .formdata import FormData
|
|
58
|
+
|
|
59
|
+
if isinstance(body, bytes):
|
|
60
|
+
proxy_bytes = create_proxy(body)
|
|
61
|
+
proxy_buffer = proxy_bytes.getBuffer()
|
|
62
|
+
try:
|
|
63
|
+
yield proxy_buffer.data
|
|
64
|
+
return
|
|
65
|
+
finally:
|
|
66
|
+
proxy_buffer.release()
|
|
67
|
+
proxy_bytes.destroy()
|
|
68
|
+
if isinstance(body, FormData):
|
|
69
|
+
yield body.js_object
|
|
70
|
+
return
|
|
71
|
+
yield body
|
|
72
|
+
|
|
73
|
+
|
|
54
74
|
def _jsnull_to_none(x):
|
|
55
75
|
if x is jsnull:
|
|
56
76
|
return None
|
|
@@ -7,7 +7,7 @@ from urllib.parse import unquote, urlsplit
|
|
|
7
7
|
|
|
8
8
|
import js
|
|
9
9
|
|
|
10
|
-
from workers import Context, Request
|
|
10
|
+
from workers import Context, Request, WorkerEntrypoint
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger("wsgi")
|
|
13
13
|
NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
|
|
@@ -262,8 +262,9 @@ def _make_streaming_response(
|
|
|
262
262
|
for proxy in proxies:
|
|
263
263
|
proxy.destroy()
|
|
264
264
|
|
|
265
|
+
# Make proxies async so that it is possible to stack switch inside them.
|
|
265
266
|
@create_proxy
|
|
266
|
-
def pull(controller: Any) -> None:
|
|
267
|
+
async def pull(controller: Any) -> None:
|
|
267
268
|
try:
|
|
268
269
|
chunk = next(chunks, _END)
|
|
269
270
|
except Exception as exc: # noqa: BLE001 - forward app errors to the stream
|
|
@@ -278,7 +279,7 @@ def _make_streaming_response(
|
|
|
278
279
|
controller.enqueue(_to_js_uint8array(chunk))
|
|
279
280
|
|
|
280
281
|
@create_proxy
|
|
281
|
-
def cancel(_reason: Any = None) -> None:
|
|
282
|
+
async def cancel(_reason: Any = None) -> None:
|
|
282
283
|
cleanup()
|
|
283
284
|
|
|
284
285
|
proxies = [pull, cancel]
|
|
@@ -378,3 +379,13 @@ async def fetch(
|
|
|
378
379
|
except Exception:
|
|
379
380
|
logger.exception("WSGI request failed")
|
|
380
381
|
raise
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def entrypoint(app: Any) -> type[WorkerEntrypoint]:
|
|
385
|
+
"""Create the default Worker entrypoint for a WSGI application."""
|
|
386
|
+
|
|
387
|
+
class Default(WorkerEntrypoint):
|
|
388
|
+
async def fetch(self, request):
|
|
389
|
+
return await fetch(app, request, self.env)
|
|
390
|
+
|
|
391
|
+
return Default
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import json as _json
|
|
2
2
|
|
|
3
|
+
import js
|
|
4
|
+
from pyodide.ffi import create_proxy, to_js
|
|
5
|
+
|
|
3
6
|
import asgi
|
|
4
7
|
from workers import Request
|
|
5
8
|
|
|
@@ -46,6 +49,52 @@ async def post_json(app, path, data, **kwargs):
|
|
|
46
49
|
)
|
|
47
50
|
|
|
48
51
|
|
|
52
|
+
def build_multipart_bytes(files, boundary="----WebTestBoundary"):
|
|
53
|
+
"""Build a binary-safe multipart body from (name, filename, bytes) tuples."""
|
|
54
|
+
body = bytearray()
|
|
55
|
+
for name, filename, content in files:
|
|
56
|
+
if isinstance(content, str):
|
|
57
|
+
content = content.encode()
|
|
58
|
+
body.extend(f"--{boundary}\r\n".encode())
|
|
59
|
+
body.extend(
|
|
60
|
+
f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'.encode()
|
|
61
|
+
)
|
|
62
|
+
body.extend(b"Content-Type: application/octet-stream\r\n\r\n")
|
|
63
|
+
body.extend(content)
|
|
64
|
+
body.extend(b"\r\n")
|
|
65
|
+
body.extend(f"--{boundary}--\r\n".encode())
|
|
66
|
+
return bytes(body), f"multipart/form-data; boundary={boundary}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def fetch_chunks(app, path, chunks, env=None, method="POST", headers=None):
|
|
70
|
+
"""Send a request body as a JS ReadableStream with explicit chunk boundaries."""
|
|
71
|
+
from js import Object
|
|
72
|
+
|
|
73
|
+
chunks = list(chunks)
|
|
74
|
+
|
|
75
|
+
def start(controller):
|
|
76
|
+
for chunk in chunks:
|
|
77
|
+
controller.enqueue(to_js(chunk))
|
|
78
|
+
controller.close()
|
|
79
|
+
|
|
80
|
+
start_proxy = create_proxy(start)
|
|
81
|
+
try:
|
|
82
|
+
stream = js.ReadableStream.new(
|
|
83
|
+
to_js({"start": start_proxy}, dict_converter=Object.fromEntries)
|
|
84
|
+
)
|
|
85
|
+
hdrs = dict(headers or {})
|
|
86
|
+
hdrs.setdefault("Content-Length", str(sum(len(chunk) for chunk in chunks)))
|
|
87
|
+
request = Request(
|
|
88
|
+
f"{BASE_URL}{path}",
|
|
89
|
+
method=method,
|
|
90
|
+
headers=hdrs,
|
|
91
|
+
body=stream,
|
|
92
|
+
)
|
|
93
|
+
return await asgi.fetch(app, request, env or {})
|
|
94
|
+
finally:
|
|
95
|
+
start_proxy.destroy()
|
|
96
|
+
|
|
97
|
+
|
|
49
98
|
def build_multipart(files, boundary="----WebTestBoundary"):
|
|
50
99
|
"""Build a multipart/form-data body from a list of (name, filename, content) tuples."""
|
|
51
100
|
lines = []
|
workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Tests across the FastAPI, Pyodide, and Workers runtime boundaries."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import gzip
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from _client import fetch, fetch_chunks, read_json
|
|
10
|
+
from worker import reset_platform_concurrency
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.asyncio
|
|
14
|
+
async def test_chunked_js_stream_preserves_utf8_and_boundaries(fastapi_app):
|
|
15
|
+
"""A JS ReadableStream survives the JS-to-Python ASGI receive bridge."""
|
|
16
|
+
value = {
|
|
17
|
+
"message": "worker \u0063\u0061\u0066\u00e9 \U0001f680",
|
|
18
|
+
"items": [1, 2, 3],
|
|
19
|
+
}
|
|
20
|
+
body = json.dumps(value, ensure_ascii=False).encode()
|
|
21
|
+
emoji = "\U0001f680".encode()
|
|
22
|
+
emoji_start = body.index(emoji)
|
|
23
|
+
cuts = [7, emoji_start + 1, emoji_start + 3, len(body) - 2]
|
|
24
|
+
chunks = []
|
|
25
|
+
start = 0
|
|
26
|
+
for end in cuts + [len(body)]:
|
|
27
|
+
chunks.append(body[start:end])
|
|
28
|
+
start = end
|
|
29
|
+
|
|
30
|
+
resp = await fetch_chunks(
|
|
31
|
+
fastapi_app,
|
|
32
|
+
"/platform/chunked-json",
|
|
33
|
+
chunks,
|
|
34
|
+
headers={"Content-Type": "application/json"},
|
|
35
|
+
)
|
|
36
|
+
assert resp.status == 200
|
|
37
|
+
data = await read_json(resp)
|
|
38
|
+
assert data["chunk_sizes"] == [len(chunk) for chunk in chunks]
|
|
39
|
+
assert data["sha256"] == hashlib.sha256(body).hexdigest()
|
|
40
|
+
assert data["data"] == value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_concurrent_requests_keep_bindings_and_data_isolated(fastapi_app):
|
|
45
|
+
"""Interleaved requests retain their own env, headers, and bodies."""
|
|
46
|
+
reset_platform_concurrency()
|
|
47
|
+
|
|
48
|
+
async def request(marker):
|
|
49
|
+
resp = await fetch(
|
|
50
|
+
fastapi_app,
|
|
51
|
+
"/platform/concurrent-env",
|
|
52
|
+
env={"marker": marker},
|
|
53
|
+
method="POST",
|
|
54
|
+
headers={"X-Request-Marker": marker},
|
|
55
|
+
body=f"body-{marker}",
|
|
56
|
+
)
|
|
57
|
+
assert resp.status == 200
|
|
58
|
+
return await read_json(resp)
|
|
59
|
+
|
|
60
|
+
first, second = await asyncio.wait_for(
|
|
61
|
+
asyncio.gather(request("first"), request("second")), timeout=5
|
|
62
|
+
)
|
|
63
|
+
assert first == {
|
|
64
|
+
"binding": "first",
|
|
65
|
+
"header": "first",
|
|
66
|
+
"body": "body-first",
|
|
67
|
+
}
|
|
68
|
+
assert second == {
|
|
69
|
+
"binding": "second",
|
|
70
|
+
"header": "second",
|
|
71
|
+
"body": "body-second",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.asyncio
|
|
76
|
+
async def test_multiple_set_cookie_headers_survive_fastapi(fastapi_app):
|
|
77
|
+
"""FastAPI's separate Set-Cookie headers remain distinct through JS Headers."""
|
|
78
|
+
resp = await fetch(fastapi_app, "/platform/multiple-cookies")
|
|
79
|
+
assert resp.status == 200
|
|
80
|
+
cookies = list(resp.headers.getSetCookie())
|
|
81
|
+
assert len(cookies) == 3
|
|
82
|
+
assert cookies[0].startswith("first=1;")
|
|
83
|
+
assert cookies[1].startswith("second=two;")
|
|
84
|
+
assert cookies[2].startswith("dated=3;")
|
|
85
|
+
assert "Wed, 21 Oct 2037 07:28:00 GMT" in cookies[2]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_gzip_middleware_preserves_binary_body(fastapi_app):
|
|
90
|
+
"""GZipMiddleware compresses binary data streamed through a JS Response."""
|
|
91
|
+
expected = bytes(range(256)) * 32
|
|
92
|
+
resp = await fetch(
|
|
93
|
+
fastapi_app,
|
|
94
|
+
"/platform/gzip-binary",
|
|
95
|
+
headers={"Accept-Encoding": "gzip"},
|
|
96
|
+
)
|
|
97
|
+
assert resp.status == 200
|
|
98
|
+
assert resp.headers.get("content-encoding") == "gzip"
|
|
99
|
+
assert gzip.decompress((await resp.bytes()).to_bytes()) == expected
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Binary I/O tests across FastAPI, Pyodide, and workerd."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from _client import build_multipart_bytes, fetch, read_json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
async def test_upload_spills_binary_file_to_virtual_disk(fastapi_app):
|
|
11
|
+
"""UploadFile remains correct after SpooledTemporaryFile rolls to disk."""
|
|
12
|
+
payload = bytes(range(256)) * 5120 # 1.25 MiB, above Starlette's 1 MiB limit.
|
|
13
|
+
body, content_type = build_multipart_bytes(
|
|
14
|
+
[("file", "binary.dat", payload)], boundary="----SpoolBoundary"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
resp = await fetch(
|
|
18
|
+
fastapi_app,
|
|
19
|
+
"/platform/upload-spooled",
|
|
20
|
+
method="POST",
|
|
21
|
+
headers={"Content-Type": content_type},
|
|
22
|
+
body=body,
|
|
23
|
+
)
|
|
24
|
+
assert resp.status == 200
|
|
25
|
+
data = await read_json(resp)
|
|
26
|
+
assert data == {
|
|
27
|
+
"size": len(payload),
|
|
28
|
+
"sha256": hashlib.sha256(payload).hexdigest(),
|
|
29
|
+
"reread_matches": True,
|
|
30
|
+
"rolled_to_disk": True,
|
|
31
|
+
}
|
workers_runtime_sdk-1.8.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""FastAPI lifecycle tests targeting the Workers ASGI streaming boundary."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from _client import fetch, get_json
|
|
7
|
+
from worker import (
|
|
8
|
+
_platform_events,
|
|
9
|
+
_platform_shutdown_complete,
|
|
10
|
+
reset_platform_events,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.asyncio
|
|
15
|
+
async def test_lifespan_state_reaches_request(fastapi_app):
|
|
16
|
+
"""State yielded by FastAPI's lifespan context is copied to HTTP scopes."""
|
|
17
|
+
resp, data = await get_json(fastapi_app, "/platform/lifespan-state")
|
|
18
|
+
assert resp.status == 200
|
|
19
|
+
assert data == {"available": True, "closed": False}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.asyncio
|
|
23
|
+
async def test_stream_finishes_before_cleanup_and_shutdown(fastapi_app):
|
|
24
|
+
"""Streaming, background work, and dependency cleanup precede shutdown."""
|
|
25
|
+
reset_platform_events()
|
|
26
|
+
|
|
27
|
+
resp = await asyncio.wait_for(
|
|
28
|
+
fetch(fastapi_app, "/platform/lifecycle-stream"), timeout=5
|
|
29
|
+
)
|
|
30
|
+
assert resp.status == 200
|
|
31
|
+
assert await asyncio.wait_for(resp.text(), timeout=5) == (
|
|
32
|
+
"chunk-0\nchunk-1\nchunk-2\n"
|
|
33
|
+
)
|
|
34
|
+
await asyncio.wait_for(_platform_shutdown_complete.wait(), timeout=5)
|
|
35
|
+
|
|
36
|
+
assert _platform_events == [
|
|
37
|
+
"lifespan-startup",
|
|
38
|
+
"dependency-open",
|
|
39
|
+
"handler",
|
|
40
|
+
"chunk-0",
|
|
41
|
+
"chunk-1",
|
|
42
|
+
"chunk-2",
|
|
43
|
+
"background-task",
|
|
44
|
+
"dependency-close",
|
|
45
|
+
"lifespan-shutdown",
|
|
46
|
+
]
|
|
@@ -20,6 +20,15 @@ These are relevant for Pyodide/Workers because:
|
|
|
20
20
|
|
|
21
21
|
import pytest
|
|
22
22
|
from _client import fetch, get_json, read_json
|
|
23
|
+
from worker import Default
|
|
24
|
+
|
|
25
|
+
from workers import WorkerEntrypoint
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_asgi_entrypoint_factory():
|
|
29
|
+
assert Default.__name__ == "Default"
|
|
30
|
+
assert issubclass(Default, WorkerEntrypoint)
|
|
31
|
+
|
|
23
32
|
|
|
24
33
|
# -- Path parameters ---------------------------------------------------------
|
|
25
34
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pyodide.ffi import create_proxy
|
|
5
|
+
from worker import Default
|
|
6
|
+
|
|
7
|
+
from workers import Request
|
|
8
|
+
|
|
9
|
+
TIMEOUT_S = 5
|
|
10
|
+
_proxies = []
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.asyncio
|
|
14
|
+
async def test_generated_entrypoint_serves_fastapi_websocket():
|
|
15
|
+
entrypoint = object.__new__(Default)
|
|
16
|
+
entrypoint.env = {}
|
|
17
|
+
request = Request(
|
|
18
|
+
"http://testserver/websocket/echo", headers={"Upgrade": "websocket"}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
response = await entrypoint.fetch(request)
|
|
22
|
+
assert response.status == 101
|
|
23
|
+
|
|
24
|
+
websocket = response.webSocket
|
|
25
|
+
websocket.accept()
|
|
26
|
+
message = asyncio.get_event_loop().create_future()
|
|
27
|
+
|
|
28
|
+
def onmessage(event):
|
|
29
|
+
if not message.done():
|
|
30
|
+
message.set_result(event.data)
|
|
31
|
+
|
|
32
|
+
proxy = create_proxy(onmessage)
|
|
33
|
+
_proxies.append(proxy)
|
|
34
|
+
websocket.addEventListener("message", proxy)
|
|
35
|
+
try:
|
|
36
|
+
websocket.send("hello")
|
|
37
|
+
assert await asyncio.wait_for(message, TIMEOUT_S) == "echo:hello"
|
|
38
|
+
finally:
|
|
39
|
+
websocket.close()
|