workers-runtime-sdk 1.7.0__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/CHANGELOG.md +22 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/PKG-INFO +1 -1
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/pyproject.toml +1 -1
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/asgi.py +59 -14
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/wsgi.py +14 -3
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/_client.py +32 -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_lifecycle.py +46 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/worker.py +111 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +8 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/worker.py +15 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/tests/test_wsgi.py +19 -3
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/worker.py +21 -9
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/uv.lock +1 -1
- workers_runtime_sdk-1.7.0/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -12
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/.gitignore +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/AGENTS.md +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/README.md +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_cloudflare_compat_flags.pyi +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_entropy_import_context.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_package_patches.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.pth +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/_workers_sdk_patch_loader.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/asgi.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/__init__.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/blob.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/entrypoints.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/fetch.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/formdata.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/py.typed +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/request.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/response.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/rpc.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/types.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/utils.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/src/workers/workflows.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/data.json +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/public/hello.txt +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/conftest.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_analytics_engine.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_d1.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_do.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_images.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_kv.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_mtls.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_queue.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_r2.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_ratelimit.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_vectorize.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_version_metadata.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/test_workflow.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_durable_object.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/src/worker_workflow.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/bindings-test/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/conftest.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_bindings.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_fastapi.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_in_workerd.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/test_web_frameworks.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/asgi.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.js +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/sdk.wd-test +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/server.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/worker.py +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.8.0 (2026-08-28)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Defer ASGI lifespan shutdown until request completion
|
|
10
|
+
([`3249184`](https://github.com/cloudflare/workers-py/commit/324918442748a0b87affc6128315ceb190e1b368))
|
|
11
|
+
|
|
12
|
+
- Make wsgi streaming response handlers async
|
|
13
|
+
([#191](https://github.com/cloudflare/workers-py/pull/191),
|
|
14
|
+
[`12168ae`](https://github.com/cloudflare/workers-py/commit/12168aef7599067a910a518f1ce7860a00ae9a58))
|
|
15
|
+
|
|
16
|
+
- **runtime-sdk**: Close the WebSocket transport when the app task ends
|
|
17
|
+
([#166](https://github.com/cloudflare/workers-py/pull/166),
|
|
18
|
+
[`e67e7e7`](https://github.com/cloudflare/workers-py/commit/e67e7e78af7fa6976f1f9966508b9869a6840ce5))
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Add wsgi.entrypoint like asgi.entrypoint
|
|
23
|
+
([#232](https://github.com/cloudflare/workers-py/pull/232),
|
|
24
|
+
[`e044561`](https://github.com/cloudflare/workers-py/commit/e04456174b3f5bc9df8b3f8293ced6d08aaffc0f))
|
|
25
|
+
|
|
26
|
+
|
|
5
27
|
## v1.7.0 (2026-08-24)
|
|
6
28
|
|
|
7
29
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workers-runtime-sdk
|
|
3
|
-
Version: 1.
|
|
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
|
|
@@ -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
|
|
|
@@ -7,7 +7,7 @@ from urllib.parse import unquote, urlsplit
|
|
|
7
7
|
|
|
8
8
|
import js
|
|
9
9
|
|
|
10
|
-
from workers import Context, Request
|
|
10
|
+
from workers import Context, Request, WorkerEntrypoint
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger("wsgi")
|
|
13
13
|
NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
|
|
@@ -262,8 +262,9 @@ def _make_streaming_response(
|
|
|
262
262
|
for proxy in proxies:
|
|
263
263
|
proxy.destroy()
|
|
264
264
|
|
|
265
|
+
# Make proxies async so that it is possible to stack switch inside them.
|
|
265
266
|
@create_proxy
|
|
266
|
-
def pull(controller: Any) -> None:
|
|
267
|
+
async def pull(controller: Any) -> None:
|
|
267
268
|
try:
|
|
268
269
|
chunk = next(chunks, _END)
|
|
269
270
|
except Exception as exc: # noqa: BLE001 - forward app errors to the stream
|
|
@@ -278,7 +279,7 @@ def _make_streaming_response(
|
|
|
278
279
|
controller.enqueue(_to_js_uint8array(chunk))
|
|
279
280
|
|
|
280
281
|
@create_proxy
|
|
281
|
-
def cancel(_reason: Any = None) -> None:
|
|
282
|
+
async def cancel(_reason: Any = None) -> None:
|
|
282
283
|
cleanup()
|
|
283
284
|
|
|
284
285
|
proxies = [pull, cancel]
|
|
@@ -378,3 +379,13 @@ async def fetch(
|
|
|
378
379
|
except Exception:
|
|
379
380
|
logger.exception("WSGI request failed")
|
|
380
381
|
raise
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def entrypoint(app: Any) -> type[WorkerEntrypoint]:
|
|
385
|
+
"""Create the default Worker entrypoint for a WSGI application."""
|
|
386
|
+
|
|
387
|
+
class Default(WorkerEntrypoint):
|
|
388
|
+
async def fetch(self, request):
|
|
389
|
+
return await fetch(app, request, self.env)
|
|
390
|
+
|
|
391
|
+
return Default
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import json as _json
|
|
2
2
|
|
|
3
|
+
import js
|
|
4
|
+
from pyodide.ffi import create_proxy, to_js
|
|
5
|
+
|
|
3
6
|
import asgi
|
|
4
7
|
from workers import Request
|
|
5
8
|
|
|
@@ -63,6 +66,35 @@ def build_multipart_bytes(files, boundary="----WebTestBoundary"):
|
|
|
63
66
|
return bytes(body), f"multipart/form-data; boundary={boundary}"
|
|
64
67
|
|
|
65
68
|
|
|
69
|
+
async def fetch_chunks(app, path, chunks, env=None, method="POST", headers=None):
|
|
70
|
+
"""Send a request body as a JS ReadableStream with explicit chunk boundaries."""
|
|
71
|
+
from js import Object
|
|
72
|
+
|
|
73
|
+
chunks = list(chunks)
|
|
74
|
+
|
|
75
|
+
def start(controller):
|
|
76
|
+
for chunk in chunks:
|
|
77
|
+
controller.enqueue(to_js(chunk))
|
|
78
|
+
controller.close()
|
|
79
|
+
|
|
80
|
+
start_proxy = create_proxy(start)
|
|
81
|
+
try:
|
|
82
|
+
stream = js.ReadableStream.new(
|
|
83
|
+
to_js({"start": start_proxy}, dict_converter=Object.fromEntries)
|
|
84
|
+
)
|
|
85
|
+
hdrs = dict(headers or {})
|
|
86
|
+
hdrs.setdefault("Content-Length", str(sum(len(chunk) for chunk in chunks)))
|
|
87
|
+
request = Request(
|
|
88
|
+
f"{BASE_URL}{path}",
|
|
89
|
+
method=method,
|
|
90
|
+
headers=hdrs,
|
|
91
|
+
body=stream,
|
|
92
|
+
)
|
|
93
|
+
return await asgi.fetch(app, request, env or {})
|
|
94
|
+
finally:
|
|
95
|
+
start_proxy.destroy()
|
|
96
|
+
|
|
97
|
+
|
|
66
98
|
def build_multipart(files, boundary="----WebTestBoundary"):
|
|
67
99
|
"""Build a multipart/form-data body from a list of (name, filename, content) tuples."""
|
|
68
100
|
lines = []
|
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
|
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
|
+
]
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import enum
|
|
3
3
|
import hashlib
|
|
4
4
|
import importlib.util
|
|
5
|
+
import json
|
|
5
6
|
from contextlib import asynccontextmanager
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
@@ -32,6 +33,7 @@ from fastapi.staticfiles import StaticFiles
|
|
|
32
33
|
from pydantic import BaseModel
|
|
33
34
|
from pyodide.webloop import WebLoop
|
|
34
35
|
from starlette.background import BackgroundTask
|
|
36
|
+
from starlette.middleware.gzip import GZipMiddleware
|
|
35
37
|
|
|
36
38
|
import asgi
|
|
37
39
|
|
|
@@ -51,16 +53,40 @@ class PlatformResource:
|
|
|
51
53
|
self.closed = False
|
|
52
54
|
|
|
53
55
|
|
|
56
|
+
_platform_events: list[str] = []
|
|
57
|
+
_platform_shutdown_complete = asyncio.Event()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reset_platform_events():
|
|
61
|
+
_platform_events.clear()
|
|
62
|
+
_platform_shutdown_complete.clear()
|
|
63
|
+
|
|
64
|
+
|
|
54
65
|
@asynccontextmanager
|
|
55
66
|
async def _platform_lifespan(_app):
|
|
56
67
|
resource = PlatformResource()
|
|
68
|
+
_platform_events.append("lifespan-startup")
|
|
57
69
|
try:
|
|
58
70
|
yield {"platform_resource": resource}
|
|
59
71
|
finally:
|
|
60
72
|
resource.closed = True
|
|
73
|
+
_platform_events.append("lifespan-shutdown")
|
|
74
|
+
_platform_shutdown_complete.set()
|
|
61
75
|
|
|
62
76
|
|
|
63
77
|
app = FastAPI(lifespan=_platform_lifespan)
|
|
78
|
+
app.add_middleware(GZipMiddleware, minimum_size=1024)
|
|
79
|
+
|
|
80
|
+
_platform_concurrency_ready = asyncio.Event()
|
|
81
|
+
_platform_concurrency_count = 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def reset_platform_concurrency():
|
|
85
|
+
global _platform_concurrency_count
|
|
86
|
+
|
|
87
|
+
_platform_concurrency_count = 0
|
|
88
|
+
_platform_concurrency_ready.clear()
|
|
89
|
+
|
|
64
90
|
|
|
65
91
|
# --------------------------------------------------------------------------- #
|
|
66
92
|
# Shared mutable state used to verify side-effects (e.g. background tasks).
|
|
@@ -463,6 +489,91 @@ async def platform_upload_spooled(file: UploadFile = File(...)): # noqa: B008
|
|
|
463
489
|
}
|
|
464
490
|
|
|
465
491
|
|
|
492
|
+
@app.post("/platform/chunked-json")
|
|
493
|
+
async def platform_chunked_json(request: Request):
|
|
494
|
+
chunks = [chunk async for chunk in request.stream() if chunk]
|
|
495
|
+
body = b"".join(chunks)
|
|
496
|
+
return {
|
|
497
|
+
"chunk_sizes": [len(chunk) for chunk in chunks],
|
|
498
|
+
"sha256": hashlib.sha256(body).hexdigest(),
|
|
499
|
+
"data": json.loads(body),
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@app.post("/platform/concurrent-env")
|
|
504
|
+
async def platform_concurrent_env(
|
|
505
|
+
request: Request,
|
|
506
|
+
bindings=asgi.env, # noqa: B008
|
|
507
|
+
):
|
|
508
|
+
global _platform_concurrency_count
|
|
509
|
+
|
|
510
|
+
_platform_concurrency_count += 1
|
|
511
|
+
if _platform_concurrency_count == 2:
|
|
512
|
+
_platform_concurrency_ready.set()
|
|
513
|
+
await asyncio.wait_for(_platform_concurrency_ready.wait(), timeout=5)
|
|
514
|
+
body = await request.body()
|
|
515
|
+
return {
|
|
516
|
+
"binding": bindings["marker"],
|
|
517
|
+
"header": request.headers["x-request-marker"],
|
|
518
|
+
"body": body.decode(),
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@app.get("/platform/multiple-cookies")
|
|
523
|
+
async def platform_multiple_cookies():
|
|
524
|
+
response = JSONResponse({"ok": True})
|
|
525
|
+
response.set_cookie("first", "1")
|
|
526
|
+
response.set_cookie("second", "two")
|
|
527
|
+
response.set_cookie("dated", "3", expires="Wed, 21 Oct 2037 07:28:00 GMT")
|
|
528
|
+
return response
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
@app.get("/platform/gzip-binary")
|
|
532
|
+
async def platform_gzip_binary():
|
|
533
|
+
return FastAPIResponse(
|
|
534
|
+
content=(bytes(range(256)) * 32),
|
|
535
|
+
media_type="application/octet-stream",
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _stream_resource():
|
|
540
|
+
resource = PlatformResource()
|
|
541
|
+
_platform_events.append("dependency-open")
|
|
542
|
+
try:
|
|
543
|
+
yield resource
|
|
544
|
+
finally:
|
|
545
|
+
resource.closed = True
|
|
546
|
+
_platform_events.append("dependency-close")
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
async def _platform_background_task():
|
|
550
|
+
await asyncio.sleep(0)
|
|
551
|
+
_platform_events.append("background-task")
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
@app.get("/platform/lifecycle-stream")
|
|
555
|
+
async def platform_lifecycle_stream(
|
|
556
|
+
request: Request,
|
|
557
|
+
resource: PlatformResource = Depends(_stream_resource), # noqa: B008
|
|
558
|
+
):
|
|
559
|
+
lifespan_resource = request.state.platform_resource
|
|
560
|
+
_platform_events.append("handler")
|
|
561
|
+
|
|
562
|
+
async def chunks():
|
|
563
|
+
for index in range(3):
|
|
564
|
+
assert not resource.closed
|
|
565
|
+
assert not lifespan_resource.closed
|
|
566
|
+
_platform_events.append(f"chunk-{index}")
|
|
567
|
+
yield f"chunk-{index}\n"
|
|
568
|
+
await asyncio.sleep(0)
|
|
569
|
+
|
|
570
|
+
return StreamingResponse(
|
|
571
|
+
chunks(),
|
|
572
|
+
media_type="text/plain",
|
|
573
|
+
background=BackgroundTask(_platform_background_task),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
|
|
466
577
|
@app.get("/native-file")
|
|
467
578
|
async def native_file():
|
|
468
579
|
"""Serve a single bundled file with FastAPI's native FileResponse."""
|
|
@@ -159,3 +159,11 @@ async def test_environment_reaches_websocket_scope():
|
|
|
159
159
|
ws.send("env")
|
|
160
160
|
event = await asyncio.wait_for(message, TIMEOUT_S)
|
|
161
161
|
assert event.data == "worker-environment"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.asyncio
|
|
165
|
+
async def test_app_crash_closes_the_transport():
|
|
166
|
+
async with _ws_session("/ws-crash") as ws:
|
|
167
|
+
close = _listen(ws, "close")
|
|
168
|
+
evt = await asyncio.wait_for(close, TIMEOUT_S)
|
|
169
|
+
assert evt.code == 1011
|
|
@@ -133,11 +133,25 @@ class WSEnvApp:
|
|
|
133
133
|
await send({"type": "websocket.send", "text": scope["env"]["marker"]})
|
|
134
134
|
|
|
135
135
|
|
|
136
|
+
class WSCrashApp:
|
|
137
|
+
"""Accepts, then raises: the server must close the transport (1011)."""
|
|
138
|
+
|
|
139
|
+
async def __call__(self, scope, receive, send):
|
|
140
|
+
if scope["type"] == "lifespan":
|
|
141
|
+
await _drain_lifespan(receive, send)
|
|
142
|
+
return
|
|
143
|
+
message = await receive()
|
|
144
|
+
assert message["type"] == "websocket.connect"
|
|
145
|
+
await send({"type": "websocket.accept"})
|
|
146
|
+
raise RuntimeError("websocket app crashed")
|
|
147
|
+
|
|
148
|
+
|
|
136
149
|
ws_app = WSWatchApp()
|
|
137
150
|
echo_app = WSEchoApp()
|
|
138
151
|
empty_frame_app = WSEmptyFrameApp()
|
|
139
152
|
close_app = WSCloseApp()
|
|
140
153
|
env_app = WSEnvApp()
|
|
154
|
+
crash_app = WSCrashApp()
|
|
141
155
|
|
|
142
156
|
|
|
143
157
|
class Default(WorkerEntrypoint):
|
|
@@ -152,6 +166,7 @@ class Default(WorkerEntrypoint):
|
|
|
152
166
|
"/ws-close": close_app,
|
|
153
167
|
"/ws-echo": echo_app,
|
|
154
168
|
"/ws-empty": empty_frame_app,
|
|
169
|
+
"/ws-crash": crash_app,
|
|
155
170
|
}.get(path, ws_app)
|
|
156
171
|
return await asgi.websocket(app, request)
|
|
157
172
|
import json
|
{workers_runtime_sdk-1.7.0 → workers_runtime_sdk-1.8.0}/tests/workerd-test/wsgi/tests/test_wsgi.py
RENAMED
|
@@ -8,9 +8,24 @@ from worker import (
|
|
|
8
8
|
STREAMING_NUM_CHUNKS,
|
|
9
9
|
crash_app,
|
|
10
10
|
example_hdr,
|
|
11
|
+
header_echo_app,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
|
-
from workers import Request, env, wsgi
|
|
14
|
+
from workers import Request, WorkerEntrypoint, env, wsgi
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.asyncio
|
|
18
|
+
async def test_generated_entrypoint_serves_wsgi_app():
|
|
19
|
+
default = wsgi.entrypoint(header_echo_app)
|
|
20
|
+
assert default.__name__ == "Default"
|
|
21
|
+
assert issubclass(default, WorkerEntrypoint)
|
|
22
|
+
|
|
23
|
+
entrypoint = object.__new__(default)
|
|
24
|
+
entrypoint.env = {}
|
|
25
|
+
response = await entrypoint.fetch(js.Request.new("http://example.com/"))
|
|
26
|
+
|
|
27
|
+
assert response.status == 200
|
|
28
|
+
assert await response.text() == "Hello, World"
|
|
14
29
|
|
|
15
30
|
|
|
16
31
|
@pytest.mark.asyncio
|
|
@@ -62,9 +77,10 @@ async def test_cookies():
|
|
|
62
77
|
assert "b=2" in cookies
|
|
63
78
|
|
|
64
79
|
|
|
80
|
+
@pytest.mark.parametrize("endpoint", ("stream", "stream-stack-switch"))
|
|
65
81
|
@pytest.mark.asyncio
|
|
66
|
-
async def test_streaming():
|
|
67
|
-
response = await env.SELF.fetch("http://example.com/
|
|
82
|
+
async def test_streaming(endpoint):
|
|
83
|
+
response = await env.SELF.fetch("http://example.com/" + endpoint)
|
|
68
84
|
assert response.status == 200
|
|
69
85
|
assert response.headers.get("content-type") == "application/octet-stream"
|
|
70
86
|
|