workers-runtime-sdk 1.9.4__tar.gz → 1.9.6__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.9.4 → workers_runtime_sdk-1.9.6}/AGENTS.md +1 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/CHANGELOG.md +18 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/PKG-INFO +1 -1
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/pyproject.toml +1 -1
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/asgi.py +35 -7
- workers_runtime_sdk-1.9.6/src/workers/gateway/__init__.py +5 -0
- workers_runtime_sdk-1.9.6/src/workers/gateway/options.py +30 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/wsgi.py +44 -11
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/flask-tests/pyproject.toml +7 -1
- workers_runtime_sdk-1.9.6/tests/web-frameworks-test/flask-tests/src/test_stream_with_context.py +70 -0
- workers_runtime_sdk-1.9.6/tests/workerd-test/asgi/asgi.wd-test +36 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi/tests/test_asgi.py +39 -1
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi/worker.py +60 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +6 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/tests/test_sdk.py +1 -2
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/wsgi/tests/test_wsgi.py +86 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/wsgi/worker.py +71 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/wsgi/wsgi.wd-test +18 -1
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/uv.lock +1 -1
- workers_runtime_sdk-1.9.4/tests/workerd-test/asgi/asgi.wd-test +0 -19
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/.gitignore +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/LICENSE +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/README.md +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/_cloudflare_compat_flags.pyi +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/_pyodide_entrypoint_helper.pyi +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/_workers_sdk_entropy_import_context.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/_workers_sdk_package_patches.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/_workers_sdk_patch_loader.pth +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/_workers_sdk_patch_loader.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/asgi.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/__init__.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/blob.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/entrypoints.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/fetch.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/formdata.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/py.typed +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/request.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/response.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/rpc.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/types.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/utils.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/workflows.js +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/src/workers/workflows.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/public/data.json +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/public/hello.txt +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/conftest.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_analytics_engine.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_d1.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_do.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_images.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_kv.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_mtls.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_queue.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_r2.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_ratelimit.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_vectorize.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_version_metadata.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/test_workflow.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/worker_durable_object.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/src/worker_workflow.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/bindings-test/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/conftest.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/test_bindings.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/test_django.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/test_django_databases.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/test_fastapi.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/test_flask.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/test_in_workerd.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/src/django_db_config.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/src/django_db_helpers.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/src/test_mysql.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/src/test_postgresql.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/django-databases/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/flask-tests/src/test_response.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/flask-tests/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/web-frameworks-test/flask-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi-ws-disconnect/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-abort/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_boto3.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_jsonschema.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_litestar.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_mcp.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_opentelemetry.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/http-client.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/origin.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/tests/test_http_client.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/tests/test_urllib_request.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/http-client/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/python-rpc/worker.js +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/python-rpc/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/sdk.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/server.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/workflow/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/workflow/worker.js +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/workflow/worker.py +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/workflow/workflow.wd-test +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/workflow/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/wsgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
|
@@ -16,6 +16,7 @@ This package provides utilities for:
|
|
|
16
16
|
- `_pyodide_entrypoint_helper` and `_cloudflare_compat_flags` are internal values that are imported from `workerd`.
|
|
17
17
|
- `workerd` (https://github.com/cloudflare/workerd) is the runtime that Cloudflare Workers use to run Python code.
|
|
18
18
|
- When using a feature from `workerd`, always check if we can implement it in this package instead to avoid tight coupling with the runtime.
|
|
19
|
+
- Code shared by the ASGI (`workers/asgi.py`) and WSGI (`workers/wsgi.py`) adapters lives in the `workers/gateway/` directory.
|
|
19
20
|
|
|
20
21
|
## Testing
|
|
21
22
|
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.9.6 (2026-11-04)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **runtime-sdk**: Fix contextvars not being properly shared in WSGI streaming response
|
|
10
|
+
([#288](https://github.com/cloudflare/workers-py/pull/288),
|
|
11
|
+
[`7cc6cbd`](https://github.com/cloudflare/workers-py/commit/7cc6cbd9253cc581dbb53e9254aa063b643f1183))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## v1.9.5 (2026-10-20)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- **runtime-sdk**: Add options argument to asgi and wsgi
|
|
19
|
+
([#289](https://github.com/cloudflare/workers-py/pull/289),
|
|
20
|
+
[`eec1e54`](https://github.com/cloudflare/workers-py/commit/eec1e54fa0348e6c90161d7f1f022344e4e6e867))
|
|
21
|
+
|
|
22
|
+
|
|
5
23
|
## v1.9.4 (2026-10-16)
|
|
6
24
|
|
|
7
25
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workers-runtime-sdk
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.6
|
|
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
|
|
@@ -2,12 +2,14 @@ import logging
|
|
|
2
2
|
from asyncio import Event, Future, Queue, create_task, ensure_future
|
|
3
3
|
from collections.abc import Awaitable
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from typing import Any
|
|
6
7
|
from urllib.parse import unquote
|
|
7
8
|
|
|
8
9
|
import js
|
|
9
10
|
|
|
10
11
|
from workers import Context, Request, WorkerEntrypoint
|
|
12
|
+
from workers.gateway import BaseOptions
|
|
11
13
|
from workers.utils import _to_js_headers
|
|
12
14
|
|
|
13
15
|
ASGI = {"spec_version": "2.0", "version": "3.0"}
|
|
@@ -16,6 +18,14 @@ logger = logging.getLogger("asgi")
|
|
|
16
18
|
background_tasks = set()
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
@dataclass(frozen=True, kw_only=True)
|
|
22
|
+
class Options(BaseOptions):
|
|
23
|
+
"""Options for the ASGI adapter"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEFAULT_OPTIONS = Options()
|
|
27
|
+
|
|
28
|
+
|
|
19
29
|
def run_in_background(coro: Awaitable[Any]) -> Future:
|
|
20
30
|
fut = ensure_future(coro)
|
|
21
31
|
background_tasks.add(fut)
|
|
@@ -195,10 +205,12 @@ async def process_request( # noqa: PLR0913
|
|
|
195
205
|
# TODO(later): remove this parameter after unvendoring Python SDK from workerd
|
|
196
206
|
ctx: Context | None,
|
|
197
207
|
state: dict[str, Any] | None = None,
|
|
208
|
+
options: Options = DEFAULT_OPTIONS,
|
|
198
209
|
) -> tuple[js.Response, Future[None]]:
|
|
199
210
|
from js import Response, TransformStream
|
|
200
211
|
from pyodide.ffi import create_proxy
|
|
201
212
|
|
|
213
|
+
response_init = options.response_init()
|
|
202
214
|
status = None
|
|
203
215
|
headers = None
|
|
204
216
|
result = Future()
|
|
@@ -262,7 +274,10 @@ async def process_request( # noqa: PLR0913
|
|
|
262
274
|
readable = transform_stream.readable
|
|
263
275
|
writer = transform_stream.writable.getWriter()
|
|
264
276
|
resp = Response.new(
|
|
265
|
-
readable,
|
|
277
|
+
readable,
|
|
278
|
+
headers=_to_js_headers(headers),
|
|
279
|
+
status=status,
|
|
280
|
+
**response_init,
|
|
266
281
|
)
|
|
267
282
|
result.set_result(resp)
|
|
268
283
|
with acquire_js_buffer(body) as jsbytes:
|
|
@@ -271,7 +286,10 @@ async def process_request( # noqa: PLR0913
|
|
|
271
286
|
# 101/103/204/205/304 must not carry a body per the Fetch spec.
|
|
272
287
|
# https://fetch.spec.whatwg.org/#null-body-status
|
|
273
288
|
resp = Response.new(
|
|
274
|
-
None,
|
|
289
|
+
None,
|
|
290
|
+
headers=_to_js_headers(headers),
|
|
291
|
+
status=status,
|
|
292
|
+
**response_init,
|
|
275
293
|
)
|
|
276
294
|
result.set_result(resp)
|
|
277
295
|
finished_response.set()
|
|
@@ -281,7 +299,10 @@ async def process_request( # noqa: PLR0913
|
|
|
281
299
|
buf = px.getBuffer()
|
|
282
300
|
px.destroy()
|
|
283
301
|
resp = Response.new(
|
|
284
|
-
buf.data,
|
|
302
|
+
buf.data,
|
|
303
|
+
headers=_to_js_headers(headers),
|
|
304
|
+
status=status,
|
|
305
|
+
**response_init,
|
|
285
306
|
)
|
|
286
307
|
result.set_result(resp)
|
|
287
308
|
finished_response.set()
|
|
@@ -436,12 +457,19 @@ async def process_websocket(
|
|
|
436
457
|
|
|
437
458
|
|
|
438
459
|
async def fetch(
|
|
439
|
-
app: Any,
|
|
460
|
+
app: Any,
|
|
461
|
+
req: "Request | js.Request",
|
|
462
|
+
env: Any,
|
|
463
|
+
ctx: Context | None = None,
|
|
464
|
+
*,
|
|
465
|
+
options: Options | None = None,
|
|
440
466
|
) -> js.Response:
|
|
441
467
|
logger.debug("ASGI request: %s %s", req.method, req.url)
|
|
442
468
|
shutdown, state = await start_application(app)
|
|
443
469
|
try:
|
|
444
|
-
result, request_task = await process_request(
|
|
470
|
+
result, request_task = await process_request(
|
|
471
|
+
app, req, env, ctx, state=state, options=options or DEFAULT_OPTIONS
|
|
472
|
+
)
|
|
445
473
|
except Exception:
|
|
446
474
|
logger.exception("ASGI request failed")
|
|
447
475
|
await shutdown()
|
|
@@ -469,14 +497,14 @@ async def websocket(
|
|
|
469
497
|
return await process_websocket(app, req, env)
|
|
470
498
|
|
|
471
499
|
|
|
472
|
-
def entrypoint(app: Any) -> type[WorkerEntrypoint]:
|
|
500
|
+
def entrypoint(app: Any, *, options: Options | None = None) -> type[WorkerEntrypoint]:
|
|
473
501
|
"""Create the default Worker entrypoint for an ASGI application."""
|
|
474
502
|
|
|
475
503
|
class Default(WorkerEntrypoint):
|
|
476
504
|
async def fetch(self, request):
|
|
477
505
|
if (request.headers.get("upgrade") or "").lower() == "websocket":
|
|
478
506
|
return await websocket(app, request, self.env)
|
|
479
|
-
return await fetch(app, request, self.env, self.ctx)
|
|
507
|
+
return await fetch(app, request, self.env, self.ctx, options=options)
|
|
480
508
|
|
|
481
509
|
return Default
|
|
482
510
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Literal, get_args
|
|
3
|
+
|
|
4
|
+
EncodeBody = Literal["automatic", "manual"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True, kw_only=True)
|
|
8
|
+
class BaseOptions:
|
|
9
|
+
"""Options shared by the ASGI and WSGI adapters.
|
|
10
|
+
|
|
11
|
+
Downstream adapters should inherit from this class to define their own options.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Whether to encode the body according to the ``Content-Encoding`` header
|
|
15
|
+
# See https://developers.cloudflare.com/workers/runtime-apis/response/#the-encodebody-option
|
|
16
|
+
encode_body: EncodeBody = "automatic"
|
|
17
|
+
|
|
18
|
+
def __post_init__(self) -> None:
|
|
19
|
+
if self.encode_body not in get_args(EncodeBody):
|
|
20
|
+
raise ValueError(
|
|
21
|
+
f"encode_body must be one of {get_args(EncodeBody)}, "
|
|
22
|
+
f"got {self.encode_body!r}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def response_init(self) -> dict[str, Any]:
|
|
26
|
+
"""Extra keyword arguments for ``Response.new``."""
|
|
27
|
+
init: dict[str, Any] = {}
|
|
28
|
+
if self.encode_body != "automatic":
|
|
29
|
+
init["encodeBody"] = self.encode_body
|
|
30
|
+
return init
|
|
@@ -1,18 +1,29 @@
|
|
|
1
|
+
import contextvars
|
|
1
2
|
import io
|
|
2
3
|
import logging
|
|
3
4
|
import sys
|
|
4
5
|
from collections.abc import Callable, Iterator
|
|
6
|
+
from dataclasses import dataclass
|
|
5
7
|
from typing import Any
|
|
6
8
|
from urllib.parse import unquote, urlsplit
|
|
7
9
|
|
|
8
10
|
import js
|
|
9
11
|
|
|
10
12
|
from workers import Context, Request, WorkerEntrypoint
|
|
13
|
+
from workers.gateway import BaseOptions
|
|
11
14
|
|
|
12
15
|
logger = logging.getLogger("wsgi")
|
|
13
16
|
NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
@dataclass(frozen=True, kw_only=True)
|
|
20
|
+
class Options(BaseOptions):
|
|
21
|
+
"""Options for the WSGI adapter, see ``BaseOptions`` for shared fields."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DEFAULT_OPTIONS = Options()
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
def _wsgi_native_string(value: str) -> str:
|
|
17
28
|
"""Convert a (possibly non-ASCII) Python ``str`` into a WSGI "native string".
|
|
18
29
|
|
|
@@ -212,11 +223,20 @@ def _close_iterable(iterable: Any) -> None:
|
|
|
212
223
|
_END = object()
|
|
213
224
|
|
|
214
225
|
|
|
226
|
+
def _iter_in_context(
|
|
227
|
+
context: contextvars.Context, iterator: "Iterator[Any]"
|
|
228
|
+
) -> "Iterator[Any]":
|
|
229
|
+
"""Make sure every chunk is produced in the given context."""
|
|
230
|
+
while (chunk := context.run(next, iterator, _END)) is not _END:
|
|
231
|
+
yield chunk
|
|
232
|
+
|
|
233
|
+
|
|
215
234
|
def _make_streaming_response(
|
|
216
235
|
status: str,
|
|
217
236
|
headers: "list[tuple[str, str]]",
|
|
218
237
|
chunks: "Iterator[bytes]",
|
|
219
238
|
on_close: "Callable[[], None]",
|
|
239
|
+
response_init: dict[str, Any],
|
|
220
240
|
) -> js.Response:
|
|
221
241
|
"""Build a ``js.Response`` whose body is pulled lazily from *chunks*.
|
|
222
242
|
|
|
@@ -232,7 +252,7 @@ def _make_streaming_response(
|
|
|
232
252
|
# WSGI status is e.g. "200 OK"; split into code + reason phrase.
|
|
233
253
|
code_str, _, reason = status.partition(" ")
|
|
234
254
|
code = int(code_str)
|
|
235
|
-
options: dict[str, Any] = {"status": code}
|
|
255
|
+
options: dict[str, Any] = {"status": code, **response_init}
|
|
236
256
|
if reason:
|
|
237
257
|
options["statusText"] = reason
|
|
238
258
|
|
|
@@ -291,6 +311,7 @@ def process_request(
|
|
|
291
311
|
req: "Request | js.Request",
|
|
292
312
|
env: Any,
|
|
293
313
|
body: "bytes | io.IOBase",
|
|
314
|
+
options: Options = DEFAULT_OPTIONS,
|
|
294
315
|
) -> js.Response:
|
|
295
316
|
environ = build_environ(req, env, body)
|
|
296
317
|
|
|
@@ -321,15 +342,21 @@ def process_request(
|
|
|
321
342
|
response_state["headers"] = response_headers
|
|
322
343
|
return write
|
|
323
344
|
|
|
324
|
-
|
|
325
|
-
|
|
345
|
+
# Make sure a single context is used per response.
|
|
346
|
+
# Otherwise, stack switching could cause context variables set by the app
|
|
347
|
+
# to be lost when the iterable is consumed.
|
|
348
|
+
context = contextvars.copy_context()
|
|
349
|
+
result = context.run(app, environ, start_response)
|
|
350
|
+
result_iter = _iter_in_context(context, context.run(iter, result))
|
|
326
351
|
|
|
327
352
|
def close_all() -> None:
|
|
328
|
-
_close_iterable(result)
|
|
329
353
|
try:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
354
|
+
context.run(_close_iterable, result)
|
|
355
|
+
finally:
|
|
356
|
+
try:
|
|
357
|
+
environ["wsgi.input"].close()
|
|
358
|
+
except Exception: # noqa: BLE001 - best-effort cleanup
|
|
359
|
+
logger.exception("Failed to close wsgi.input")
|
|
333
360
|
|
|
334
361
|
# WSGI apps must call start_response before yielding the first body chunk,
|
|
335
362
|
# but some defer it until the first non-empty chunk is produced. Pull that
|
|
@@ -357,7 +384,11 @@ def process_request(
|
|
|
357
384
|
yield chunk
|
|
358
385
|
|
|
359
386
|
return _make_streaming_response(
|
|
360
|
-
response_state["status"],
|
|
387
|
+
response_state["status"],
|
|
388
|
+
response_state["headers"],
|
|
389
|
+
body_chunks(),
|
|
390
|
+
close_all,
|
|
391
|
+
options.response_init(),
|
|
361
392
|
)
|
|
362
393
|
|
|
363
394
|
|
|
@@ -367,6 +398,8 @@ async def fetch(
|
|
|
367
398
|
env: Any,
|
|
368
399
|
# Accepted for parity with asgi.fetch; WSGI has no use for it.
|
|
369
400
|
ctx: Context | None = None,
|
|
401
|
+
*,
|
|
402
|
+
options: Options | None = None,
|
|
370
403
|
) -> js.Response:
|
|
371
404
|
logger.debug("WSGI request: %s %s", req.method, req.url)
|
|
372
405
|
# Prefer lazily streaming the body through `wsgi.input` (no full buffering);
|
|
@@ -375,17 +408,17 @@ async def fetch(
|
|
|
375
408
|
if body is None:
|
|
376
409
|
body = await _read_body(req)
|
|
377
410
|
try:
|
|
378
|
-
return process_request(app, req, env, body)
|
|
411
|
+
return process_request(app, req, env, body, options or DEFAULT_OPTIONS)
|
|
379
412
|
except Exception:
|
|
380
413
|
logger.exception("WSGI request failed")
|
|
381
414
|
raise
|
|
382
415
|
|
|
383
416
|
|
|
384
|
-
def entrypoint(app: Any) -> type[WorkerEntrypoint]:
|
|
417
|
+
def entrypoint(app: Any, *, options: Options | None = None) -> type[WorkerEntrypoint]:
|
|
385
418
|
"""Create the default Worker entrypoint for a WSGI application."""
|
|
386
419
|
|
|
387
420
|
class Default(WorkerEntrypoint):
|
|
388
421
|
async def fetch(self, request):
|
|
389
|
-
return await fetch(app, request, self.env)
|
|
422
|
+
return await fetch(app, request, self.env, options=options)
|
|
390
423
|
|
|
391
424
|
return Default
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
name = "flask-tests"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
requires-python = ">=3.12"
|
|
5
|
-
dependencies = [
|
|
5
|
+
dependencies = [
|
|
6
|
+
"flask",
|
|
7
|
+
"pytest",
|
|
8
|
+
"pytest-asyncio<1.2.0",
|
|
9
|
+
"testlib",
|
|
10
|
+
"workers-runtime-sdk",
|
|
11
|
+
]
|
|
6
12
|
|
|
7
13
|
[tool.uv.sources]
|
|
8
14
|
testlib = { path = "../packages/testlib" }
|
workers_runtime_sdk-1.9.6/tests/web-frameworks-test/flask-tests/src/test_stream_with_context.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import js
|
|
4
|
+
import pytest
|
|
5
|
+
from flask import (
|
|
6
|
+
Flask,
|
|
7
|
+
Response,
|
|
8
|
+
has_request_context,
|
|
9
|
+
request,
|
|
10
|
+
stream_with_context,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from workers import wsgi
|
|
14
|
+
|
|
15
|
+
app = Flask(__name__)
|
|
16
|
+
# Request values whose stream_with_context generator has run its cleanup.
|
|
17
|
+
closed: list[str] = []
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.get("/stream")
|
|
21
|
+
def stream():
|
|
22
|
+
@stream_with_context
|
|
23
|
+
def body():
|
|
24
|
+
value = request.args["value"]
|
|
25
|
+
try:
|
|
26
|
+
yield "flask:"
|
|
27
|
+
yield request.args["value"]
|
|
28
|
+
finally:
|
|
29
|
+
closed.append(value)
|
|
30
|
+
|
|
31
|
+
return Response(body(), content_type="text/plain")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def fetch(value):
|
|
35
|
+
req = js.Request.new(f"http://example.com/stream?value={value}")
|
|
36
|
+
return await wsgi.fetch(app, req, {})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture(autouse=True)
|
|
40
|
+
def clear_closed():
|
|
41
|
+
closed.clear()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.asyncio
|
|
45
|
+
async def test_stream_with_context():
|
|
46
|
+
response = await fetch("stream-body")
|
|
47
|
+
assert response.status == 200
|
|
48
|
+
# The request context pushed while priming the body must stay in the
|
|
49
|
+
# response's own context instead of leaking into the caller's.
|
|
50
|
+
assert not has_request_context()
|
|
51
|
+
assert await response.text() == "flask:stream-body"
|
|
52
|
+
assert closed == ["stream-body"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.asyncio
|
|
56
|
+
async def test_concurrent_stream_with_context():
|
|
57
|
+
responses = await asyncio.gather(fetch("first"), fetch("second"))
|
|
58
|
+
bodies = await asyncio.gather(*(response.text() for response in responses))
|
|
59
|
+
assert bodies == ["flask:first", "flask:second"]
|
|
60
|
+
assert sorted(closed) == ["first", "second"]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_cancelled_stream_with_context():
|
|
65
|
+
response = await fetch("cancel")
|
|
66
|
+
reader = response.body.getReader()
|
|
67
|
+
first = await reader.read()
|
|
68
|
+
assert first.value.to_bytes() == b"flask:"
|
|
69
|
+
await reader.cancel()
|
|
70
|
+
assert closed == ["cancel"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
using Workerd = import "/workerd/workerd.capnp";
|
|
2
|
+
|
|
3
|
+
const unitTests :Workerd.Config = (
|
|
4
|
+
services = [
|
|
5
|
+
( name = "python-asgi",
|
|
6
|
+
worker = (
|
|
7
|
+
modules = [
|
|
8
|
+
(name = "worker.py", pythonModule = embed "worker.py"),
|
|
9
|
+
%PYTHON_MODULES
|
|
10
|
+
],
|
|
11
|
+
bindings = [
|
|
12
|
+
( name = "SELF", service = "python-asgi" ),
|
|
13
|
+
# SELF.fetch does not serialize the response over HTTP, so it cannot
|
|
14
|
+
# observe the runtime encoding the body. ASGI_HTTP goes through the
|
|
15
|
+
# loopback HTTP socket below instead.
|
|
16
|
+
( name = "ASGI_HTTP", service = "asgi-http" ),
|
|
17
|
+
],
|
|
18
|
+
compatibilityDate = "%COMPAT_DATE",
|
|
19
|
+
compatibilityFlags = ["python_workers"],
|
|
20
|
+
)
|
|
21
|
+
),
|
|
22
|
+
( name = "asgi-http",
|
|
23
|
+
external = (
|
|
24
|
+
address = "loopback:asgi-http",
|
|
25
|
+
http = (),
|
|
26
|
+
)
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
sockets = [
|
|
30
|
+
( name = "asgi-http",
|
|
31
|
+
address = "loopback:asgi-http",
|
|
32
|
+
http = (),
|
|
33
|
+
service = "python-asgi",
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
);
|
{workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/asgi/tests/test_asgi.py
RENAMED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import gzip
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
|
|
5
6
|
import js
|
|
6
7
|
import pytest
|
|
7
8
|
from pyodide.ffi import to_js
|
|
8
|
-
from worker import
|
|
9
|
+
from worker import (
|
|
10
|
+
ENCODED_RESPONSE_PAYLOAD,
|
|
11
|
+
STREAMING_CHUNK_SIZE,
|
|
12
|
+
STREAMING_NUM_CHUNKS,
|
|
13
|
+
example_hdr,
|
|
14
|
+
)
|
|
9
15
|
|
|
10
16
|
import asgi
|
|
11
17
|
from workers import Request, env
|
|
@@ -474,3 +480,35 @@ async def test_lifespan_preack_crash_is_logged_and_treated_as_unsupported():
|
|
|
474
480
|
)
|
|
475
481
|
finally:
|
|
476
482
|
_remove_handler(handler)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def test_options_validate_encode_body():
|
|
486
|
+
assert asgi.Options().response_init() == {}
|
|
487
|
+
assert asgi.Options(encode_body="manual").response_init() == {
|
|
488
|
+
"encodeBody": "manual"
|
|
489
|
+
}
|
|
490
|
+
with pytest.raises(ValueError, match="encode_body"):
|
|
491
|
+
asgi.Options(encode_body="gzip")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@pytest.mark.asyncio
|
|
495
|
+
@pytest.mark.parametrize("mode", ["buffered", "streaming"])
|
|
496
|
+
@pytest.mark.parametrize("encoding", ["gzip", "identity"])
|
|
497
|
+
@pytest.mark.parametrize("encode_body", ["automatic", "manual"])
|
|
498
|
+
async def test_encode_body_option(encode_body, encoding, mode):
|
|
499
|
+
# SELF.fetch does not serialize the response over HTTP, so it cannot
|
|
500
|
+
# observe the runtime encoding the body; ASGI_HTTP goes over a socket.
|
|
501
|
+
response = await env.ASGI_HTTP.fetch(
|
|
502
|
+
f"http://example.com/encoded/{encoding}/{mode}/{encode_body}",
|
|
503
|
+
headers={"accept-encoding": "gzip"},
|
|
504
|
+
)
|
|
505
|
+
assert response.status == 200
|
|
506
|
+
assert response.headers["content-encoding"] == encoding
|
|
507
|
+
|
|
508
|
+
# fetch() decodes the declared Content-Encoding once, like a browser.
|
|
509
|
+
body = await response.bytes()
|
|
510
|
+
if encoding == "gzip" and encode_body == "automatic":
|
|
511
|
+
# The runtime gzips the app's already-gzipped bytes a second time.
|
|
512
|
+
assert gzip.decompress(body) == ENCODED_RESPONSE_PAYLOAD
|
|
513
|
+
else:
|
|
514
|
+
assert body == ENCODED_RESPONSE_PAYLOAD
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import gzip
|
|
1
2
|
import os
|
|
2
3
|
|
|
3
4
|
from testlib.entrypoint import run_pytest
|
|
4
5
|
|
|
5
6
|
import asgi
|
|
6
7
|
from workers import WorkerEntrypoint
|
|
8
|
+
from workers.asgi import Options
|
|
7
9
|
|
|
8
10
|
# ---------------------------------------------------------------------------
|
|
9
11
|
# ASGI apps
|
|
@@ -238,6 +240,54 @@ class MultiCookieApp:
|
|
|
238
240
|
await send({"type": "http.response.body", "body": b"ok"})
|
|
239
241
|
|
|
240
242
|
|
|
243
|
+
ENCODED_RESPONSE_PAYLOAD = b'{"message":"already encoded by the app"}' * 64
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class EncodedResponseApp:
|
|
247
|
+
"""Sends a body the app has already encoded to match Content-Encoding."""
|
|
248
|
+
|
|
249
|
+
def __init__(self, encoding, streaming):
|
|
250
|
+
self.encoding = encoding
|
|
251
|
+
self.streaming = streaming
|
|
252
|
+
self.body = (
|
|
253
|
+
gzip.compress(ENCODED_RESPONSE_PAYLOAD, mtime=0)
|
|
254
|
+
if encoding == "gzip"
|
|
255
|
+
else ENCODED_RESPONSE_PAYLOAD
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def __call__(self, scope, receive, send):
|
|
259
|
+
if scope["type"] == "lifespan":
|
|
260
|
+
message = await receive()
|
|
261
|
+
if message["type"] == "lifespan.startup":
|
|
262
|
+
await send({"type": "lifespan.startup.complete"})
|
|
263
|
+
return
|
|
264
|
+
await receive()
|
|
265
|
+
await send(
|
|
266
|
+
{
|
|
267
|
+
"type": "http.response.start",
|
|
268
|
+
"status": 200,
|
|
269
|
+
"headers": [
|
|
270
|
+
(b"content-type", b"application/json"),
|
|
271
|
+
(b"content-encoding", self.encoding.encode()),
|
|
272
|
+
],
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
if not self.streaming:
|
|
276
|
+
await send({"type": "http.response.body", "body": self.body})
|
|
277
|
+
return
|
|
278
|
+
# Split one gzip member across chunks (inside its header and before
|
|
279
|
+
# its footer) and finish with an empty terminal chunk.
|
|
280
|
+
chunks = (self.body[:7], self.body[7:-8], self.body[-8:], b"")
|
|
281
|
+
for i, chunk in enumerate(chunks):
|
|
282
|
+
await send(
|
|
283
|
+
{
|
|
284
|
+
"type": "http.response.body",
|
|
285
|
+
"body": chunk,
|
|
286
|
+
"more_body": i < len(chunks) - 1,
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
241
291
|
app = HeaderEchoApp()
|
|
242
292
|
sse_app = SSEApp()
|
|
243
293
|
streaming_app = StreamingApp()
|
|
@@ -267,6 +317,16 @@ class Default(WorkerEntrypoint):
|
|
|
267
317
|
)
|
|
268
318
|
elif path == "/multi-cookie":
|
|
269
319
|
return await asgi.fetch(multi_cookie_app, request, self.env, self.ctx)
|
|
320
|
+
elif path.startswith("/encoded/"):
|
|
321
|
+
# /encoded/<gzip|identity>/<buffered|streaming>/<automatic|manual>
|
|
322
|
+
_, _, encoding, mode, encode_body = path.split("/")
|
|
323
|
+
return await asgi.fetch(
|
|
324
|
+
EncodedResponseApp(encoding, streaming=mode == "streaming"),
|
|
325
|
+
request,
|
|
326
|
+
self.env,
|
|
327
|
+
self.ctx,
|
|
328
|
+
options=Options(encode_body=encode_body),
|
|
329
|
+
)
|
|
270
330
|
|
|
271
331
|
return await asgi.fetch(app, request, self.env, self.ctx)
|
|
272
332
|
|
|
@@ -99,10 +99,16 @@ async def _ws_session(path):
|
|
|
99
99
|
# Without this the payload arrives as a Blob, which cannot be read
|
|
100
100
|
# synchronously in the message callback.
|
|
101
101
|
ws.binaryType = "arraybuffer"
|
|
102
|
+
# The app runs on this test's event loop, which pytest-asyncio closes when
|
|
103
|
+
# the test ends, cancelling an app still awaiting the disconnect. Its
|
|
104
|
+
# server socket then never closes, and workerd keeps the request open
|
|
105
|
+
# waiting for it, so wait for the server's close before returning.
|
|
106
|
+
closed = _listen(ws, "close")
|
|
102
107
|
try:
|
|
103
108
|
yield ws
|
|
104
109
|
finally:
|
|
105
110
|
ws.close()
|
|
111
|
+
await asyncio.wait_for(closed, TIMEOUT_S)
|
|
106
112
|
|
|
107
113
|
|
|
108
114
|
@pytest.mark.asyncio
|
{workers_runtime_sdk-1.9.4 → workers_runtime_sdk-1.9.6}/tests/workerd-test/sdk/tests/test_sdk.py
RENAMED
|
@@ -498,8 +498,7 @@ async def test_response_unit_tests():
|
|
|
498
498
|
response_json = Response.json([1, 2, 3])
|
|
499
499
|
assert await response_json.text() == "[1, 2, 3]"
|
|
500
500
|
assert (
|
|
501
|
-
repr(response_json)
|
|
502
|
-
== "Response(status=200, status_text='OK', content_type='application/json')"
|
|
501
|
+
repr(response_json) == "Response(status=200, content_type='application/json')"
|
|
503
502
|
)
|
|
504
503
|
|
|
505
504
|
response_json = Response.from_json([1, 2, 3])
|