workers-runtime-sdk 1.9.3__tar.gz → 1.9.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/AGENTS.md +1 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/CHANGELOG.md +18 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/PKG-INFO +1 -1
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/pyproject.toml +1 -1
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_entropy_import_context.py +34 -5
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/asgi.py +35 -7
- workers_runtime_sdk-1.9.5/src/workers/gateway/__init__.py +5 -0
- workers_runtime_sdk-1.9.5/src/workers/gateway/options.py +30 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/wsgi.py +23 -5
- workers_runtime_sdk-1.9.5/tests/workerd-test/asgi/asgi.wd-test +36 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/tests/test_asgi.py +39 -1
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/worker.py +60 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/pyproject.toml +1 -0
- workers_runtime_sdk-1.9.5/tests/workerd-test/entropy-patches/tests/test_boto3.py +8 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/worker.py +1 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/tests/test_wsgi.py +34 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/worker.py +34 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/wsgi.wd-test +18 -1
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/uv.lock +1 -1
- workers_runtime_sdk-1.9.3/tests/workerd-test/asgi/asgi.wd-test +0 -19
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/.gitignore +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/LICENSE +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/README.md +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_cloudflare_compat_flags.pyi +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_pyodide_entrypoint_helper.pyi +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_package_patches.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_patch_loader.pth +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_patch_loader.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/asgi.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/__init__.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/blob.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/entrypoints.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/fetch.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/formdata.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/py.typed +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/request.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/response.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/rpc.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/types.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/utils.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/workflows.js +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/workers/workflows.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/public/data.json +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/public/hello.txt +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/conftest.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_analytics_engine.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_d1.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_do.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_images.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_kv.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_mtls.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_queue.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_r2.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_ratelimit.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_vectorize.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_version_metadata.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/test_workflow.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/worker_durable_object.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/src/worker_workflow.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/bindings-test/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/conftest.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_bindings.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_django.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_django_databases.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_fastapi.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_flask.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/test_in_workerd.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/django_db_config.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/django_db_helpers.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/test_mysql.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/test_postgresql.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/django-databases/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/src/test_response.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/src/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/web-frameworks-test/flask-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_jsonschema.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_litestar.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_mcp.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_opentelemetry.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/http-client.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/origin.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/tests/test_http_client.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/tests/test_urllib_request.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/http-client/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/worker.js +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/sdk.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/server.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/worker.js +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/worker.py +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/workflow.wd-test +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/workflow/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
|
@@ -16,6 +16,7 @@ This package provides utilities for:
|
|
|
16
16
|
- `_pyodide_entrypoint_helper` and `_cloudflare_compat_flags` are internal values that are imported from `workerd`.
|
|
17
17
|
- `workerd` (https://github.com/cloudflare/workerd) is the runtime that Cloudflare Workers use to run Python code.
|
|
18
18
|
- When using a feature from `workerd`, always check if we can implement it in this package instead to avoid tight coupling with the runtime.
|
|
19
|
+
- Code shared by the ASGI (`workers/asgi.py`) and WSGI (`workers/wsgi.py`) adapters lives in the `workers/gateway/` directory.
|
|
19
20
|
|
|
20
21
|
## Testing
|
|
21
22
|
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.9.5 (2026-10-20)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **runtime-sdk**: Add options argument to asgi and wsgi
|
|
10
|
+
([#289](https://github.com/cloudflare/workers-py/pull/289),
|
|
11
|
+
[`eec1e54`](https://github.com/cloudflare/workers-py/commit/eec1e54fa0348e6c90161d7f1f022344e4e6e867))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## v1.9.4 (2026-10-16)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- **runtime-sdk**: Lazily bound ssl module to make boto3 work
|
|
19
|
+
([#292](https://github.com/cloudflare/workers-py/pull/292),
|
|
20
|
+
[`f3e7851`](https://github.com/cloudflare/workers-py/commit/f3e785144b9e581fc2b1b4cebd0ad5bc86e4550b))
|
|
21
|
+
|
|
22
|
+
|
|
5
23
|
## v1.9.3 (2026-10-06)
|
|
6
24
|
|
|
7
25
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workers-runtime-sdk
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.5
|
|
4
4
|
Summary: Python SDK for Cloudflare Workers
|
|
5
5
|
Project-URL: Homepage, https://github.com/cloudflare/workers-py
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/cloudflare/workers-py/issues
|
{workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/src/_workers_sdk_entropy_import_context.py
RENAMED
|
@@ -149,13 +149,41 @@ class NoSslFinder:
|
|
|
149
149
|
) from None
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def add_lazy_ssl_attribute(module):
|
|
153
|
+
"""
|
|
154
|
+
When `import ssl` fails inside a module, the fallback code would leave the name `ssl`
|
|
155
|
+
unbound. Other packages may still re-export it from there, e.g. botocore does
|
|
156
|
+
`from urllib3.util.ssl_ import ssl`.
|
|
157
|
+
|
|
158
|
+
This is a rare situation, and actually something that needs to be fixed in the package.
|
|
159
|
+
However, boto3 is a very commonly downloaded package in Python, so we handle this case specifically.
|
|
160
|
+
TODO: We can drop this when https://github.com/boto/botocore/pull/3640 is merged.
|
|
161
|
+
"""
|
|
162
|
+
if "ssl" in module.__dict__:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
orig_getattr = module.__dict__.get("__getattr__")
|
|
166
|
+
|
|
167
|
+
def __getattr__(name):
|
|
168
|
+
if name == "ssl":
|
|
169
|
+
import ssl
|
|
170
|
+
|
|
171
|
+
module.ssl = ssl
|
|
172
|
+
return ssl
|
|
173
|
+
if orig_getattr is not None:
|
|
174
|
+
return orig_getattr(name)
|
|
175
|
+
raise AttributeError(f"module {module.__name__!r} has no attribute {name!r}")
|
|
176
|
+
|
|
177
|
+
module.__getattr__ = __getattr__
|
|
178
|
+
|
|
179
|
+
|
|
152
180
|
@contextmanager
|
|
153
|
-
def no_ssl():
|
|
181
|
+
def no_ssl(module):
|
|
154
182
|
"""
|
|
155
183
|
Various packages will call ssl.create_default_context() at top level which uses entropy if they
|
|
156
184
|
can import ssl. By temporarily making importing ssl raise an import error, we exercise the
|
|
157
185
|
workaround code and so avoid the entropy calls. After, we put the ssl module back to the normal
|
|
158
|
-
value.
|
|
186
|
+
value and make `module.ssl` available again in case the workaround code left it unbound.
|
|
159
187
|
"""
|
|
160
188
|
try:
|
|
161
189
|
f = NoSslFinder()
|
|
@@ -166,26 +194,27 @@ def no_ssl():
|
|
|
166
194
|
sys.meta_path.remove(f)
|
|
167
195
|
if ssl:
|
|
168
196
|
sys.modules["ssl"] = ssl
|
|
197
|
+
add_lazy_ssl_attribute(module)
|
|
169
198
|
|
|
170
199
|
|
|
171
200
|
@register_exec_patch("aiohttp.connector")
|
|
172
201
|
@contextmanager
|
|
173
202
|
def aiohttp_connector_context(module):
|
|
174
|
-
with no_ssl():
|
|
203
|
+
with no_ssl(module):
|
|
175
204
|
yield
|
|
176
205
|
|
|
177
206
|
|
|
178
207
|
@register_exec_patch("requests.adapters")
|
|
179
208
|
@contextmanager
|
|
180
209
|
def requests_adapters_context(module):
|
|
181
|
-
with no_ssl():
|
|
210
|
+
with no_ssl(module):
|
|
182
211
|
yield
|
|
183
212
|
|
|
184
213
|
|
|
185
214
|
@register_exec_patch("urllib3.util.ssl_")
|
|
186
215
|
@contextmanager
|
|
187
216
|
def urllib3_util_ssl__context(module):
|
|
188
|
-
with no_ssl():
|
|
217
|
+
with no_ssl(module):
|
|
189
218
|
yield
|
|
190
219
|
|
|
191
220
|
|
|
@@ -2,12 +2,14 @@ import logging
|
|
|
2
2
|
from asyncio import Event, Future, Queue, create_task, ensure_future
|
|
3
3
|
from collections.abc import Awaitable
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from typing import Any
|
|
6
7
|
from urllib.parse import unquote
|
|
7
8
|
|
|
8
9
|
import js
|
|
9
10
|
|
|
10
11
|
from workers import Context, Request, WorkerEntrypoint
|
|
12
|
+
from workers.gateway import BaseOptions
|
|
11
13
|
from workers.utils import _to_js_headers
|
|
12
14
|
|
|
13
15
|
ASGI = {"spec_version": "2.0", "version": "3.0"}
|
|
@@ -16,6 +18,14 @@ logger = logging.getLogger("asgi")
|
|
|
16
18
|
background_tasks = set()
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
@dataclass(frozen=True, kw_only=True)
|
|
22
|
+
class Options(BaseOptions):
|
|
23
|
+
"""Options for the ASGI adapter"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEFAULT_OPTIONS = Options()
|
|
27
|
+
|
|
28
|
+
|
|
19
29
|
def run_in_background(coro: Awaitable[Any]) -> Future:
|
|
20
30
|
fut = ensure_future(coro)
|
|
21
31
|
background_tasks.add(fut)
|
|
@@ -195,10 +205,12 @@ async def process_request( # noqa: PLR0913
|
|
|
195
205
|
# TODO(later): remove this parameter after unvendoring Python SDK from workerd
|
|
196
206
|
ctx: Context | None,
|
|
197
207
|
state: dict[str, Any] | None = None,
|
|
208
|
+
options: Options = DEFAULT_OPTIONS,
|
|
198
209
|
) -> tuple[js.Response, Future[None]]:
|
|
199
210
|
from js import Response, TransformStream
|
|
200
211
|
from pyodide.ffi import create_proxy
|
|
201
212
|
|
|
213
|
+
response_init = options.response_init()
|
|
202
214
|
status = None
|
|
203
215
|
headers = None
|
|
204
216
|
result = Future()
|
|
@@ -262,7 +274,10 @@ async def process_request( # noqa: PLR0913
|
|
|
262
274
|
readable = transform_stream.readable
|
|
263
275
|
writer = transform_stream.writable.getWriter()
|
|
264
276
|
resp = Response.new(
|
|
265
|
-
readable,
|
|
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
|
|
@@ -2,17 +2,27 @@ import io
|
|
|
2
2
|
import logging
|
|
3
3
|
import sys
|
|
4
4
|
from collections.abc import Callable, Iterator
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from typing import Any
|
|
6
7
|
from urllib.parse import unquote, urlsplit
|
|
7
8
|
|
|
8
9
|
import js
|
|
9
10
|
|
|
10
11
|
from workers import Context, Request, WorkerEntrypoint
|
|
12
|
+
from workers.gateway import BaseOptions
|
|
11
13
|
|
|
12
14
|
logger = logging.getLogger("wsgi")
|
|
13
15
|
NULL_BODY_STATUSES = frozenset({101, 103, 204, 205, 304})
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
@dataclass(frozen=True, kw_only=True)
|
|
19
|
+
class Options(BaseOptions):
|
|
20
|
+
"""Options for the WSGI adapter, see ``BaseOptions`` for shared fields."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEFAULT_OPTIONS = Options()
|
|
24
|
+
|
|
25
|
+
|
|
16
26
|
def _wsgi_native_string(value: str) -> str:
|
|
17
27
|
"""Convert a (possibly non-ASCII) Python ``str`` into a WSGI "native string".
|
|
18
28
|
|
|
@@ -217,6 +227,7 @@ def _make_streaming_response(
|
|
|
217
227
|
headers: "list[tuple[str, str]]",
|
|
218
228
|
chunks: "Iterator[bytes]",
|
|
219
229
|
on_close: "Callable[[], None]",
|
|
230
|
+
response_init: dict[str, Any],
|
|
220
231
|
) -> js.Response:
|
|
221
232
|
"""Build a ``js.Response`` whose body is pulled lazily from *chunks*.
|
|
222
233
|
|
|
@@ -232,7 +243,7 @@ def _make_streaming_response(
|
|
|
232
243
|
# WSGI status is e.g. "200 OK"; split into code + reason phrase.
|
|
233
244
|
code_str, _, reason = status.partition(" ")
|
|
234
245
|
code = int(code_str)
|
|
235
|
-
options: dict[str, Any] = {"status": code}
|
|
246
|
+
options: dict[str, Any] = {"status": code, **response_init}
|
|
236
247
|
if reason:
|
|
237
248
|
options["statusText"] = reason
|
|
238
249
|
|
|
@@ -291,6 +302,7 @@ def process_request(
|
|
|
291
302
|
req: "Request | js.Request",
|
|
292
303
|
env: Any,
|
|
293
304
|
body: "bytes | io.IOBase",
|
|
305
|
+
options: Options = DEFAULT_OPTIONS,
|
|
294
306
|
) -> js.Response:
|
|
295
307
|
environ = build_environ(req, env, body)
|
|
296
308
|
|
|
@@ -357,7 +369,11 @@ def process_request(
|
|
|
357
369
|
yield chunk
|
|
358
370
|
|
|
359
371
|
return _make_streaming_response(
|
|
360
|
-
response_state["status"],
|
|
372
|
+
response_state["status"],
|
|
373
|
+
response_state["headers"],
|
|
374
|
+
body_chunks(),
|
|
375
|
+
close_all,
|
|
376
|
+
options.response_init(),
|
|
361
377
|
)
|
|
362
378
|
|
|
363
379
|
|
|
@@ -367,6 +383,8 @@ async def fetch(
|
|
|
367
383
|
env: Any,
|
|
368
384
|
# Accepted for parity with asgi.fetch; WSGI has no use for it.
|
|
369
385
|
ctx: Context | None = None,
|
|
386
|
+
*,
|
|
387
|
+
options: Options | None = None,
|
|
370
388
|
) -> js.Response:
|
|
371
389
|
logger.debug("WSGI request: %s %s", req.method, req.url)
|
|
372
390
|
# Prefer lazily streaming the body through `wsgi.input` (no full buffering);
|
|
@@ -375,17 +393,17 @@ async def fetch(
|
|
|
375
393
|
if body is None:
|
|
376
394
|
body = await _read_body(req)
|
|
377
395
|
try:
|
|
378
|
-
return process_request(app, req, env, body)
|
|
396
|
+
return process_request(app, req, env, body, options or DEFAULT_OPTIONS)
|
|
379
397
|
except Exception:
|
|
380
398
|
logger.exception("WSGI request failed")
|
|
381
399
|
raise
|
|
382
400
|
|
|
383
401
|
|
|
384
|
-
def entrypoint(app: Any) -> type[WorkerEntrypoint]:
|
|
402
|
+
def entrypoint(app: Any, *, options: Options | None = None) -> type[WorkerEntrypoint]:
|
|
385
403
|
"""Create the default Worker entrypoint for a WSGI application."""
|
|
386
404
|
|
|
387
405
|
class Default(WorkerEntrypoint):
|
|
388
406
|
async def fetch(self, request):
|
|
389
|
-
return await fetch(app, request, self.env)
|
|
407
|
+
return await fetch(app, request, self.env, options=options)
|
|
390
408
|
|
|
391
409
|
return Default
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
using Workerd = import "/workerd/workerd.capnp";
|
|
2
|
+
|
|
3
|
+
const unitTests :Workerd.Config = (
|
|
4
|
+
services = [
|
|
5
|
+
( name = "python-asgi",
|
|
6
|
+
worker = (
|
|
7
|
+
modules = [
|
|
8
|
+
(name = "worker.py", pythonModule = embed "worker.py"),
|
|
9
|
+
%PYTHON_MODULES
|
|
10
|
+
],
|
|
11
|
+
bindings = [
|
|
12
|
+
( name = "SELF", service = "python-asgi" ),
|
|
13
|
+
# SELF.fetch does not serialize the response over HTTP, so it cannot
|
|
14
|
+
# observe the runtime encoding the body. ASGI_HTTP goes through the
|
|
15
|
+
# loopback HTTP socket below instead.
|
|
16
|
+
( name = "ASGI_HTTP", service = "asgi-http" ),
|
|
17
|
+
],
|
|
18
|
+
compatibilityDate = "%COMPAT_DATE",
|
|
19
|
+
compatibilityFlags = ["python_workers"],
|
|
20
|
+
)
|
|
21
|
+
),
|
|
22
|
+
( name = "asgi-http",
|
|
23
|
+
external = (
|
|
24
|
+
address = "loopback:asgi-http",
|
|
25
|
+
http = (),
|
|
26
|
+
)
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
sockets = [
|
|
30
|
+
( name = "asgi-http",
|
|
31
|
+
address = "loopback:asgi-http",
|
|
32
|
+
http = (),
|
|
33
|
+
service = "python-asgi",
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
);
|
{workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/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
|
|
{workers_runtime_sdk-1.9.3 → workers_runtime_sdk-1.9.5}/tests/workerd-test/wsgi/tests/test_wsgi.py
RENAMED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import gzip
|
|
1
2
|
import json
|
|
2
3
|
|
|
3
4
|
import js
|
|
4
5
|
import pytest
|
|
5
6
|
from pyodide.ffi import to_js
|
|
6
7
|
from worker import (
|
|
8
|
+
ENCODED_RESPONSE_PAYLOAD,
|
|
7
9
|
STREAMING_CHUNK_SIZE,
|
|
8
10
|
STREAMING_NUM_CHUNKS,
|
|
9
11
|
crash_app,
|
|
@@ -119,3 +121,35 @@ def test_build_environ_handles_js_and_python_requests():
|
|
|
119
121
|
py_env = wsgi.build_environ(py_request, env, b"")
|
|
120
122
|
assert js_env["HTTP_HEADER1"] == py_env["HTTP_HEADER1"] == "Value1"
|
|
121
123
|
assert js_env["HTTP_HEADER2"] == py_env["HTTP_HEADER2"] == "Value2"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_options_validate_encode_body():
|
|
127
|
+
assert wsgi.Options().response_init() == {}
|
|
128
|
+
assert wsgi.Options(encode_body="manual").response_init() == {
|
|
129
|
+
"encodeBody": "manual"
|
|
130
|
+
}
|
|
131
|
+
with pytest.raises(ValueError, match="encode_body"):
|
|
132
|
+
wsgi.Options(encode_body="gzip")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.mark.asyncio
|
|
136
|
+
@pytest.mark.parametrize("mode", ["buffered", "streaming"])
|
|
137
|
+
@pytest.mark.parametrize("encoding", ["gzip", "identity"])
|
|
138
|
+
@pytest.mark.parametrize("encode_body", ["automatic", "manual"])
|
|
139
|
+
async def test_encode_body_option(encode_body, encoding, mode):
|
|
140
|
+
# SELF.fetch does not serialize the response over HTTP, so it cannot
|
|
141
|
+
# observe the runtime encoding the body; WSGI_HTTP goes over a socket.
|
|
142
|
+
response = await env.WSGI_HTTP.fetch(
|
|
143
|
+
f"http://example.com/encoded/{encoding}/{mode}/{encode_body}",
|
|
144
|
+
headers={"accept-encoding": "gzip"},
|
|
145
|
+
)
|
|
146
|
+
assert response.status == 200
|
|
147
|
+
assert response.headers["content-encoding"] == encoding
|
|
148
|
+
|
|
149
|
+
# fetch() decodes the declared Content-Encoding once, like a browser.
|
|
150
|
+
body = await response.bytes()
|
|
151
|
+
if encoding == "gzip" and encode_body == "automatic":
|
|
152
|
+
# The runtime gzips the app's already-gzipped bytes a second time.
|
|
153
|
+
assert gzip.decompress(body) == ENCODED_RESPONSE_PAYLOAD
|
|
154
|
+
else:
|
|
155
|
+
assert body == ENCODED_RESPONSE_PAYLOAD
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import gzip
|
|
2
3
|
import os
|
|
3
4
|
|
|
4
5
|
from pyodide.ffi import run_sync
|
|
@@ -91,6 +92,31 @@ def streaming_app_stack_switch(environ, start_response):
|
|
|
91
92
|
return generate()
|
|
92
93
|
|
|
93
94
|
|
|
95
|
+
ENCODED_RESPONSE_PAYLOAD = b'{"message":"already encoded by the app"}' * 64
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def make_encoded_response_app(encoding, streaming):
|
|
99
|
+
"""WSGI app sending a body already encoded to match Content-Encoding."""
|
|
100
|
+
body = (
|
|
101
|
+
gzip.compress(ENCODED_RESPONSE_PAYLOAD, mtime=0)
|
|
102
|
+
if encoding == "gzip"
|
|
103
|
+
else ENCODED_RESPONSE_PAYLOAD
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def app(environ, start_response):
|
|
107
|
+
start_response(
|
|
108
|
+
"200 OK",
|
|
109
|
+
[("Content-Type", "application/json"), ("Content-Encoding", encoding)],
|
|
110
|
+
)
|
|
111
|
+
if not streaming:
|
|
112
|
+
return [body]
|
|
113
|
+
# Split one gzip member across chunks (inside its header and before
|
|
114
|
+
# its footer).
|
|
115
|
+
return iter((body[:7], body[7:-8], body[-8:]))
|
|
116
|
+
|
|
117
|
+
return app
|
|
118
|
+
|
|
119
|
+
|
|
94
120
|
def crash_app(environ, start_response):
|
|
95
121
|
raise RuntimeError("app crash before response for testing")
|
|
96
122
|
|
|
@@ -115,6 +141,14 @@ class Default(WorkerEntrypoint):
|
|
|
115
141
|
"/stream-stack-switch": streaming_app_stack_switch,
|
|
116
142
|
}.get(path, header_echo_app)
|
|
117
143
|
|
|
144
|
+
if path.startswith("/encoded/"):
|
|
145
|
+
# /encoded/<gzip|identity>/<buffered|streaming>/<automatic|manual>
|
|
146
|
+
_, _, encoding, mode, encode_body = path.split("/")
|
|
147
|
+
app = make_encoded_response_app(encoding, streaming=mode == "streaming")
|
|
148
|
+
return await wsgi.fetch(
|
|
149
|
+
app, request, self.env, options=wsgi.Options(encode_body=encode_body)
|
|
150
|
+
)
|
|
151
|
+
|
|
118
152
|
return await wsgi.fetch(app, request, self.env)
|
|
119
153
|
|
|
120
154
|
async def test(self, ctrl):
|