workers-runtime-sdk 1.6.10__tar.gz → 1.6.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/CHANGELOG.md +17 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/PKG-INFO +2 -2
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/pyproject.toml +1 -1
- workers_runtime_sdk-1.6.12/src/_workers_sdk_package_patches.py +45 -0
- workers_runtime_sdk-1.6.12/src/_workers_sdk_patch_loader.pth +1 -0
- workers_runtime_sdk-1.6.12/src/_workers_sdk_patch_loader.py +16 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/asgi.py +2 -2
- workers_runtime_sdk-1.6.12/tests/web-frameworks-test/fastapi-tests/src/static/app.css +4 -0
- workers_runtime_sdk-1.6.12/tests/web-frameworks-test/fastapi-tests/src/static/data.json +1 -0
- workers_runtime_sdk-1.6.12/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +1 -0
- workers_runtime_sdk-1.6.12/tests/web-frameworks-test/fastapi-tests/src/static/index.html +7 -0
- workers_runtime_sdk-1.6.12/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +117 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/src/worker.py +16 -0
- workers_runtime_sdk-1.6.12/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +21 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +27 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi-ws-disconnect/worker.py +23 -1
- workers_runtime_sdk-1.6.12/tests/workerd-test/wsgi/pyproject.toml +5 -0
- workers_runtime_sdk-1.6.12/tests/workerd-test/wsgi/tests/test_wsgi.py +105 -0
- workers_runtime_sdk-1.6.12/tests/workerd-test/wsgi/worker.py +131 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/wsgi/wsgi.wd-test +4 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/uv.lock +1 -1
- workers_runtime_sdk-1.6.10/src/_workers_sdk_entropy_import_context.pth +0 -1
- workers_runtime_sdk-1.6.10/src/_workers_sdk_entropy_import_context_loader.py +0 -13
- workers_runtime_sdk-1.6.10/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -11
- workers_runtime_sdk-1.6.10/tests/workerd-test/wsgi/pyproject.toml +0 -5
- workers_runtime_sdk-1.6.10/tests/workerd-test/wsgi/worker.py +0 -205
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/.gitignore +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/AGENTS.md +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/README.md +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/_cloudflare_compat_flags.pyi +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/_pyodide_entrypoint_helper.pyi +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/_workers_sdk_entropy_import_context.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/__init__.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/blob.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/entrypoints.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/fetch.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/formdata.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/py.typed +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/request.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/response.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/rpc.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/types.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/utils.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/workflows.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/src/workers/wsgi.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/public/data.json +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/public/hello.txt +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/conftest.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_analytics_engine.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_d1.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_do.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_images.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_kv.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_mtls.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_queue.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_r2.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_ratelimit.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_vectorize.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_version_metadata.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/test_workflow.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/worker_durable_object.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/src/worker_workflow.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/bindings-test/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/conftest.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/test_bindings.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/test_fastapi.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/test_in_workerd.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/test_web_frameworks.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi/asgi.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-abort/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/python-rpc/worker.js +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/python-rpc/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/sdk/pyproject.toml +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/sdk/sdk.wd-test +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/sdk/server.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/sdk/worker.py +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.6.10 → workers_runtime_sdk-1.6.12}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.6.12 (2026-08-11)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **runtime-sdk**: Send empty WebSocket frames instead of dropping them
|
|
10
|
+
([#165](https://github.com/cloudflare/workers-py/pull/165),
|
|
11
|
+
[`a4128ce`](https://github.com/cloudflare/workers-py/commit/a4128cee402aa6f28f01e3e2268241d05d9fd393))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## v1.6.11 (2026-08-10)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- Implements tests for FastAPI StaticFiles and anyio thread patch
|
|
19
|
+
([`a30f53e`](https://github.com/cloudflare/workers-py/commit/a30f53e95edcf42fc93e5e2dc84251121881418b))
|
|
20
|
+
|
|
21
|
+
|
|
5
22
|
## v1.6.10 (2026-08-10)
|
|
6
23
|
|
|
7
24
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: workers-runtime-sdk
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.12
|
|
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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Top level runtime compatibility patches for packages.
|
|
3
|
+
|
|
4
|
+
These patches adapt packages that assume capabilities the workers runtime does
|
|
5
|
+
not provide. Unlike the entropy patches, they are not related to snapshotting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
from _cloudflare.import_patch_manager import (
|
|
11
|
+
register_exec_patch,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@register_exec_patch("anyio.to_thread")
|
|
16
|
+
@contextmanager
|
|
17
|
+
def anyio_to_thread_context(module):
|
|
18
|
+
"""`anyio.to_thread.run_sync` dispatches blocking work to an OS thread, but the
|
|
19
|
+
workers runtime is single threaded, so starting a thread raises
|
|
20
|
+
`RuntimeError: can't start new thread`. Run the callable inline instead.
|
|
21
|
+
|
|
22
|
+
This function is the only caller of `run_sync_in_worker_thread`, which makes it
|
|
23
|
+
the single chokepoint for every blocking call anyio makes on behalf of a
|
|
24
|
+
framework. In starlette/fastapi that covers sync route handlers, sync
|
|
25
|
+
dependencies, `StaticFiles`, `FileResponse`, `UploadFile`, `BackgroundTask` and
|
|
26
|
+
`GZipMiddleware`, as well as `anyio.Path` and `anyio.open_file`.
|
|
27
|
+
|
|
28
|
+
Patching the module attribute is enough because every caller looks it up at
|
|
29
|
+
call time (e.g. `starlette.concurrency` and `anyio._core._fileio` both import
|
|
30
|
+
the module rather than the function).
|
|
31
|
+
|
|
32
|
+
We patch `anyio.to_thread` rather than the backend's
|
|
33
|
+
`run_sync_in_worker_thread` because `anyio._backends._asyncio` is imported
|
|
34
|
+
lazily by `get_async_backend()` during a request, by which point the import
|
|
35
|
+
patch manager is no longer installed.
|
|
36
|
+
"""
|
|
37
|
+
yield
|
|
38
|
+
|
|
39
|
+
async def run_sync(func, *args, **kwargs):
|
|
40
|
+
# Callers pass the function's own arguments positionally and anyio's
|
|
41
|
+
# options (`limiter`, `abandon_on_cancel`, `cancellable`) as keywords.
|
|
42
|
+
# None of the options mean anything without threads, so drop them.
|
|
43
|
+
return func(*args)
|
|
44
|
+
|
|
45
|
+
module.run_sync = run_sync
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import _workers_sdk_patch_loader
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Loader shim for the top level package patches.
|
|
3
|
+
|
|
4
|
+
This module is imported by the .pth file on every Python startup. The patches
|
|
5
|
+
depend on the `_cloudflare` package, which only exists inside the workers
|
|
6
|
+
runtime. When running outside of the workers runtime, `_cloudflare` is not
|
|
7
|
+
available, so we silently skip loading the patches. Skipping is also what we
|
|
8
|
+
want in that case: the patches adapt packages to the workers runtime and should
|
|
9
|
+
not change how they behave under native Python.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import importlib.util
|
|
13
|
+
|
|
14
|
+
if importlib.util.find_spec("_cloudflare") is not None:
|
|
15
|
+
import _workers_sdk_entropy_import_context # noqa: F401
|
|
16
|
+
import _workers_sdk_package_patches # noqa: F401
|
|
@@ -352,12 +352,12 @@ async def process_websocket(app: Any, req: "Request | js.Request") -> js.Respons
|
|
|
352
352
|
if got["type"] == "websocket.send":
|
|
353
353
|
b = got.get("bytes", None)
|
|
354
354
|
s = got.get("text", None)
|
|
355
|
-
if b:
|
|
355
|
+
if b is not None:
|
|
356
356
|
with acquire_js_buffer(b) as jsbytes:
|
|
357
357
|
# Unlike the `Response` constructor, server.send seems to
|
|
358
358
|
# eagerly copy the source buffer
|
|
359
359
|
server.send(jsbytes)
|
|
360
|
-
if s:
|
|
360
|
+
if s is not None:
|
|
361
361
|
server.send(s)
|
|
362
362
|
|
|
363
363
|
else:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"source": "staticfiles", "number": 7}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Hello from StaticFiles
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Tests for FastAPI's native static file APIs (StaticFiles and FileResponse).
|
|
2
|
+
|
|
3
|
+
Unlike ``test_assets.py``, which proxies to the Workers Assets binding, these
|
|
4
|
+
tests exercise Starlette's own filesystem-backed static file serving against
|
|
5
|
+
files bundled into the worker under ``src/static/``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from _client import fetch, get_json, read_json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.asyncio
|
|
13
|
+
async def test_staticfiles_serves_text(fastapi_app):
|
|
14
|
+
"""Mounted StaticFiles app serves a bundled text file."""
|
|
15
|
+
resp = await fetch(fastapi_app, "/static/hello.txt")
|
|
16
|
+
assert resp.status == 200
|
|
17
|
+
text = await resp.text()
|
|
18
|
+
assert "Hello from StaticFiles" in text
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.asyncio
|
|
22
|
+
async def test_staticfiles_serves_html(fastapi_app):
|
|
23
|
+
"""Mounted StaticFiles app serves a bundled HTML file."""
|
|
24
|
+
resp = await fetch(fastapi_app, "/static/index.html")
|
|
25
|
+
assert resp.status == 200
|
|
26
|
+
text = await resp.text()
|
|
27
|
+
assert "Hello from StaticFiles" in text
|
|
28
|
+
assert "<!doctype html>" in text.lower()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_staticfiles_guesses_content_type(fastapi_app):
|
|
33
|
+
"""StaticFiles infers the content type from the file extension."""
|
|
34
|
+
resp = await fetch(fastapi_app, "/static/app.css")
|
|
35
|
+
assert resp.status == 200
|
|
36
|
+
assert "css" in resp.headers.get("content-type")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.asyncio
|
|
40
|
+
async def test_staticfiles_serves_json(fastapi_app):
|
|
41
|
+
"""StaticFiles serves a JSON file that is parseable and correctly typed."""
|
|
42
|
+
resp = await fetch(fastapi_app, "/static/data.json")
|
|
43
|
+
assert resp.status == 200
|
|
44
|
+
assert "application/json" in resp.headers.get("content-type")
|
|
45
|
+
data = await read_json(resp)
|
|
46
|
+
assert data["source"] == "staticfiles"
|
|
47
|
+
assert data["number"] == 7
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_staticfiles_sets_content_length(fastapi_app):
|
|
52
|
+
"""StaticFiles reports the on-disk size and sends exactly that many bytes."""
|
|
53
|
+
resp = await fetch(fastapi_app, "/static/hello.txt")
|
|
54
|
+
assert resp.status == 200
|
|
55
|
+
content_length = resp.headers.get("content-length")
|
|
56
|
+
assert content_length is not None
|
|
57
|
+
body = (await resp.text()).encode()
|
|
58
|
+
assert len(body) == int(content_length)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_staticfiles_sets_validators(fastapi_app):
|
|
63
|
+
"""StaticFiles derives etag and last-modified headers from the file stat."""
|
|
64
|
+
resp = await fetch(fastapi_app, "/static/hello.txt")
|
|
65
|
+
assert resp.status == 200
|
|
66
|
+
assert resp.headers.get("etag")
|
|
67
|
+
assert resp.headers.get("last-modified")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_staticfiles_honours_if_none_match(fastapi_app):
|
|
72
|
+
"""A conditional request matching the etag gets a 304 with no body."""
|
|
73
|
+
first = await fetch(fastapi_app, "/static/hello.txt")
|
|
74
|
+
etag = first.headers.get("etag")
|
|
75
|
+
assert etag
|
|
76
|
+
|
|
77
|
+
second = await fetch(
|
|
78
|
+
fastapi_app, "/static/hello.txt", headers={"if-none-match": etag}
|
|
79
|
+
)
|
|
80
|
+
assert second.status == 304
|
|
81
|
+
assert await second.text() == ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_staticfiles_missing_file_returns_404(fastapi_app):
|
|
86
|
+
"""StaticFiles returns 404 for a file that is not bundled."""
|
|
87
|
+
resp = await fetch(fastapi_app, "/static/nonexistent.txt")
|
|
88
|
+
assert resp.status == 404
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_file_response_serves_file(fastapi_app):
|
|
93
|
+
"""A route returning FileResponse streams the bundled file."""
|
|
94
|
+
resp = await fetch(fastapi_app, "/native-file")
|
|
95
|
+
assert resp.status == 200
|
|
96
|
+
assert "text/plain" in resp.headers.get("content-type")
|
|
97
|
+
text = await resp.text()
|
|
98
|
+
assert "Hello from StaticFiles" in text
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.mark.asyncio
|
|
102
|
+
async def test_file_response_sets_content_length(fastapi_app):
|
|
103
|
+
"""FileResponse reports the on-disk size and sends exactly that many bytes."""
|
|
104
|
+
resp = await fetch(fastapi_app, "/native-file")
|
|
105
|
+
assert resp.status == 200
|
|
106
|
+
content_length = resp.headers.get("content-length")
|
|
107
|
+
assert content_length is not None
|
|
108
|
+
body = (await resp.text()).encode()
|
|
109
|
+
assert len(body) == int(content_length)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.mark.asyncio
|
|
113
|
+
async def test_api_route_still_works(fastapi_app, env):
|
|
114
|
+
"""API routes are unaffected by the StaticFiles mount."""
|
|
115
|
+
resp, data = await get_json(fastapi_app, "/api/hello", env=env)
|
|
116
|
+
assert resp.status == 200
|
|
117
|
+
assert data["message"] == "Hello from FastAPI"
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import importlib.util
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
from fastapi import FastAPI, Request
|
|
7
|
+
from fastapi.responses import FileResponse
|
|
6
8
|
from fastapi.responses import Response as FastAPIResponse
|
|
9
|
+
from fastapi.staticfiles import StaticFiles
|
|
7
10
|
from pyodide.webloop import WebLoop
|
|
8
11
|
|
|
9
12
|
import asgi
|
|
@@ -17,6 +20,8 @@ async def _noop(*args):
|
|
|
17
20
|
WebLoop.shutdown_asyncgens = _noop
|
|
18
21
|
WebLoop.shutdown_default_executor = _noop
|
|
19
22
|
|
|
23
|
+
STATIC_DIR = Path(__file__).parent / "static"
|
|
24
|
+
|
|
20
25
|
app = FastAPI()
|
|
21
26
|
|
|
22
27
|
|
|
@@ -30,6 +35,17 @@ async def health():
|
|
|
30
35
|
return {"ok": True}
|
|
31
36
|
|
|
32
37
|
|
|
38
|
+
@app.get("/native-file")
|
|
39
|
+
async def native_file():
|
|
40
|
+
"""Serve a single bundled file with FastAPI's native FileResponse."""
|
|
41
|
+
return FileResponse(STATIC_DIR / "hello.txt", media_type="text/plain")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Mounted before the catch-all below so `/static/*` is handled by Starlette's
|
|
45
|
+
# own filesystem-backed static file app rather than the Assets binding.
|
|
46
|
+
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
|
47
|
+
|
|
48
|
+
|
|
33
49
|
@app.get("/{path:path}")
|
|
34
50
|
async def frontend(path: str, request: Request):
|
|
35
51
|
"""Proxy static asset requests to the Workers Assets binding."""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fastapi-tests",
|
|
3
|
+
"main": "src/worker.py",
|
|
4
|
+
"compatibility_date": "%COMPAT_DATE",
|
|
5
|
+
"compatibility_flags": ["python_workers"],
|
|
6
|
+
// Only `**/*.txt` and `**/*.html` are bundled as Text by default, so the
|
|
7
|
+
// other `src/static/` fixtures would never reach the Pyodide filesystem for
|
|
8
|
+
// StaticFiles to read. `fallthrough` keeps the default Text rule as well.
|
|
9
|
+
"rules": [
|
|
10
|
+
{
|
|
11
|
+
"type": "Text",
|
|
12
|
+
"globs": ["static/*.css", "static/*.json"],
|
|
13
|
+
"fallthrough": true
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"assets": {
|
|
17
|
+
"directory": "./public/",
|
|
18
|
+
"binding": "ASSETS",
|
|
19
|
+
"run_worker_first": true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -26,6 +26,22 @@ def _listen(ws, event_type):
|
|
|
26
26
|
return fut
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _collect(ws, count):
|
|
30
|
+
"""Future resolved with the payloads of the first `count` messages."""
|
|
31
|
+
fut = asyncio.get_event_loop().create_future()
|
|
32
|
+
messages = []
|
|
33
|
+
|
|
34
|
+
def callback(evt):
|
|
35
|
+
messages.append(evt.data)
|
|
36
|
+
if len(messages) == count and not fut.done():
|
|
37
|
+
fut.set_result(messages)
|
|
38
|
+
|
|
39
|
+
proxy = create_proxy(callback)
|
|
40
|
+
_proxies.append(proxy)
|
|
41
|
+
ws.addEventListener("message", proxy)
|
|
42
|
+
return fut
|
|
43
|
+
|
|
44
|
+
|
|
29
45
|
async def _ws_connect(path):
|
|
30
46
|
# Upgrade through the raw JS binding (as durable-object-websocket's
|
|
31
47
|
# tester.js does): the SDK fetch wrapper routes through pyfetch, whose
|
|
@@ -113,3 +129,14 @@ async def test_binary_frame_arrives_as_asgi_bytes():
|
|
|
113
129
|
data = evt.data
|
|
114
130
|
assert not isinstance(data, str), f"delivered to the app as text: {data!r}"
|
|
115
131
|
assert data.to_bytes() == b"bytes=" + payload
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@pytest.mark.asyncio
|
|
135
|
+
async def test_empty_frames_reach_the_client():
|
|
136
|
+
async with _ws_session("/ws-empty") as ws:
|
|
137
|
+
received = _collect(ws, 3)
|
|
138
|
+
ws.send("go")
|
|
139
|
+
messages = await asyncio.wait_for(received, TIMEOUT_S)
|
|
140
|
+
assert messages[0] == ""
|
|
141
|
+
assert messages[1].to_bytes() == b""
|
|
142
|
+
assert messages[2] == "done"
|
|
@@ -80,15 +80,37 @@ class WSEchoApp:
|
|
|
80
80
|
await send({"type": "websocket.send", "text": reply})
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
class WSEmptyFrameApp:
|
|
84
|
+
"""On each client frame, replies with an empty text frame, an empty binary
|
|
85
|
+
frame, and a sentinel, so a test can tell whether empty frames reach the
|
|
86
|
+
client at all."""
|
|
87
|
+
|
|
88
|
+
async def __call__(self, scope, receive, send):
|
|
89
|
+
if scope["type"] == "lifespan":
|
|
90
|
+
await _drain_lifespan(receive, send)
|
|
91
|
+
return
|
|
92
|
+
message = await receive()
|
|
93
|
+
assert message["type"] == "websocket.connect"
|
|
94
|
+
await send({"type": "websocket.accept"})
|
|
95
|
+
while True:
|
|
96
|
+
message = await receive()
|
|
97
|
+
if message["type"] == "websocket.disconnect":
|
|
98
|
+
return
|
|
99
|
+
await send({"type": "websocket.send", "text": ""})
|
|
100
|
+
await send({"type": "websocket.send", "bytes": b""})
|
|
101
|
+
await send({"type": "websocket.send", "text": "done"})
|
|
102
|
+
|
|
103
|
+
|
|
83
104
|
ws_app = WSWatchApp()
|
|
84
105
|
echo_app = WSEchoApp()
|
|
106
|
+
empty_frame_app = WSEmptyFrameApp()
|
|
85
107
|
|
|
86
108
|
|
|
87
109
|
class Default(WorkerEntrypoint):
|
|
88
110
|
async def fetch(self, request):
|
|
89
111
|
if (request.headers.get("upgrade") or "").lower() == "websocket":
|
|
90
112
|
path = urlsplit(request.url).path
|
|
91
|
-
app = echo_app
|
|
113
|
+
app = {"/ws-echo": echo_app, "/ws-empty": empty_frame_app}.get(path, ws_app)
|
|
92
114
|
return await asgi.websocket(app, request)
|
|
93
115
|
import json
|
|
94
116
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import js
|
|
4
|
+
import pytest
|
|
5
|
+
from pyodide.ffi import to_js
|
|
6
|
+
from worker import (
|
|
7
|
+
STREAMING_CHUNK_SIZE,
|
|
8
|
+
STREAMING_NUM_CHUNKS,
|
|
9
|
+
crash_app,
|
|
10
|
+
example_hdr,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from workers import Request, env, wsgi
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_headers():
|
|
18
|
+
response = await env.SELF.fetch("http://example.com/", headers=to_js(example_hdr))
|
|
19
|
+
assert response.status == 200
|
|
20
|
+
text = await response.text()
|
|
21
|
+
assert text == "Hello, World"
|
|
22
|
+
# Echoed-back headers should be present.
|
|
23
|
+
assert response.headers.get("header1") == "Value1"
|
|
24
|
+
assert response.headers.get("header2") == "Value2"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_echo_body():
|
|
29
|
+
response = await env.SELF.fetch(
|
|
30
|
+
"http://example.com/echo-body",
|
|
31
|
+
method="POST",
|
|
32
|
+
body="hello body",
|
|
33
|
+
)
|
|
34
|
+
assert response.status == 200
|
|
35
|
+
text = await response.text()
|
|
36
|
+
assert text == "hello body"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.asyncio
|
|
40
|
+
async def test_meta():
|
|
41
|
+
response = await env.SELF.fetch("http://example.com/meta?foo=bar&baz=qux")
|
|
42
|
+
assert response.status == 200
|
|
43
|
+
|
|
44
|
+
payload = json.loads(await response.text())
|
|
45
|
+
assert payload["method"] == "GET"
|
|
46
|
+
assert payload["path"] == "/meta"
|
|
47
|
+
assert payload["query"] == "foo=bar&baz=qux"
|
|
48
|
+
assert payload["scheme"] == "http"
|
|
49
|
+
assert payload["has_env"] is True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_cookies():
|
|
54
|
+
response = await env.SELF.fetch("http://example.com/cookies")
|
|
55
|
+
assert response.status == 200
|
|
56
|
+
# `env.SELF.fetch` returns the SDK `FetchResponse`, whose `.headers` is an
|
|
57
|
+
# `http.client.HTTPMessage`. Repeated Set-Cookie headers are preserved as
|
|
58
|
+
# separate entries (see `python_request_headers_preserve_commas`), so use
|
|
59
|
+
# `get_all` to recover the individual values.
|
|
60
|
+
cookies = response.headers.get_all("Set-Cookie")
|
|
61
|
+
assert "a=1" in cookies
|
|
62
|
+
assert "b=2" in cookies
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_streaming():
|
|
67
|
+
response = await env.SELF.fetch("http://example.com/stream")
|
|
68
|
+
assert response.status == 200
|
|
69
|
+
assert response.headers.get("content-type") == "application/octet-stream"
|
|
70
|
+
|
|
71
|
+
reader = response.body.getReader()
|
|
72
|
+
body_bytes = b""
|
|
73
|
+
while True:
|
|
74
|
+
result = await reader.read()
|
|
75
|
+
if result.done:
|
|
76
|
+
break
|
|
77
|
+
body_bytes += result.value.to_bytes()
|
|
78
|
+
|
|
79
|
+
expected_size = STREAMING_CHUNK_SIZE * STREAMING_NUM_CHUNKS
|
|
80
|
+
assert len(body_bytes) == expected_size, (
|
|
81
|
+
f"Expected {expected_size} bytes, got {len(body_bytes)}"
|
|
82
|
+
)
|
|
83
|
+
for i in range(STREAMING_NUM_CHUNKS):
|
|
84
|
+
start = i * STREAMING_CHUNK_SIZE
|
|
85
|
+
end = start + STREAMING_CHUNK_SIZE
|
|
86
|
+
expected_byte = i % 256
|
|
87
|
+
assert all(b == expected_byte for b in body_bytes[start:end])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.asyncio
|
|
91
|
+
async def test_app_exception_is_raised():
|
|
92
|
+
req = js.Request.new("http://example.com/crash-test")
|
|
93
|
+
with pytest.raises(RuntimeError, match="app crash before response for testing"):
|
|
94
|
+
await wsgi.fetch(crash_app, req, env)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_build_environ_handles_js_and_python_requests():
|
|
98
|
+
# Verify `build_environ` handles JS-style and Python-style headers
|
|
99
|
+
# identically, mirroring the asgi `request_to_scope` check.
|
|
100
|
+
js_request = js.Request.new("http://example.com/", headers=to_js(example_hdr))
|
|
101
|
+
py_request = Request("http://example.com/", headers=example_hdr)
|
|
102
|
+
js_env = wsgi.build_environ(js_request, env, b"")
|
|
103
|
+
py_env = wsgi.build_environ(py_request, env, b"")
|
|
104
|
+
assert js_env["HTTP_HEADER1"] == py_env["HTTP_HEADER1"] == "Value1"
|
|
105
|
+
assert js_env["HTTP_HEADER2"] == py_env["HTTP_HEADER2"] == "Value2"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from pyodide.webloop import WebLoop
|
|
7
|
+
|
|
8
|
+
from workers import WorkerEntrypoint, wsgi
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def noop(*args):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# pytest-asyncio relies on these but in Pyodide < 0.29 WebLoop does not implement them
|
|
16
|
+
WebLoop.shutdown_asyncgens = noop
|
|
17
|
+
WebLoop.shutdown_default_executor = noop
|
|
18
|
+
|
|
19
|
+
# Pyodide 0.26.0a2's _cancel_all_tasks calls task.exception() on pending tasks,
|
|
20
|
+
# which raises InvalidStateError under Pyodide's WebLoop.
|
|
21
|
+
# Ignore this error to prevent pytest-asyncio from crashing.
|
|
22
|
+
if sys.version_info < (3, 13):
|
|
23
|
+
asyncio.runners._cancel_all_tasks = lambda loop: None # type: ignore[attr-defined]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# WSGI apps
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def header_echo_app(environ, start_response):
|
|
32
|
+
"""WSGI app that echoes request headers back in the response body and headers."""
|
|
33
|
+
response_headers = [("Content-Type", "text/plain")]
|
|
34
|
+
# Echo each incoming header back out (HTTP_* keys).
|
|
35
|
+
for key, value in environ.items():
|
|
36
|
+
if key.startswith("HTTP_"):
|
|
37
|
+
name = key[len("HTTP_") :].replace("_", "-").title()
|
|
38
|
+
response_headers.append((name, value))
|
|
39
|
+
|
|
40
|
+
start_response("200 OK", response_headers)
|
|
41
|
+
return [b"Hello, World"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def echo_body_app(environ, start_response):
|
|
45
|
+
"""WSGI app that reads the request body and echoes it back."""
|
|
46
|
+
length = int(environ.get("CONTENT_LENGTH") or 0)
|
|
47
|
+
body = environ["wsgi.input"].read(length)
|
|
48
|
+
start_response(
|
|
49
|
+
"200 OK",
|
|
50
|
+
[("Content-Type", "application/octet-stream")],
|
|
51
|
+
)
|
|
52
|
+
return [body]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def echo_meta_app(environ, start_response):
|
|
56
|
+
"""WSGI app that returns selected environ values so the test can assert on them."""
|
|
57
|
+
import json
|
|
58
|
+
|
|
59
|
+
payload = {
|
|
60
|
+
"method": environ["REQUEST_METHOD"],
|
|
61
|
+
"path": environ["PATH_INFO"],
|
|
62
|
+
"query": environ["QUERY_STRING"],
|
|
63
|
+
"scheme": environ["wsgi.url_scheme"],
|
|
64
|
+
"has_env": "workers.env" in environ,
|
|
65
|
+
}
|
|
66
|
+
body = json.dumps(payload).encode()
|
|
67
|
+
start_response("200 OK", [("Content-Type", "application/json")])
|
|
68
|
+
return [body]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cookies_app(environ, start_response):
|
|
72
|
+
"""WSGI app that sets multiple Set-Cookie headers (must not collapse)."""
|
|
73
|
+
start_response(
|
|
74
|
+
"200 OK",
|
|
75
|
+
[
|
|
76
|
+
("Content-Type", "text/plain"),
|
|
77
|
+
("Set-Cookie", "a=1"),
|
|
78
|
+
("Set-Cookie", "b=2"),
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
return [b"cookies"]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
STREAMING_CHUNK_SIZE = 1024
|
|
85
|
+
STREAMING_NUM_CHUNKS = 5
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def streaming_app(environ, start_response):
|
|
89
|
+
"""WSGI app that returns multiple body chunks via a generator."""
|
|
90
|
+
start_response("200 OK", [("Content-Type", "application/octet-stream")])
|
|
91
|
+
|
|
92
|
+
def generate():
|
|
93
|
+
for i in range(STREAMING_NUM_CHUNKS):
|
|
94
|
+
yield bytes([i % 256]) * STREAMING_CHUNK_SIZE
|
|
95
|
+
|
|
96
|
+
return generate()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def crash_app(environ, start_response):
|
|
100
|
+
raise RuntimeError("app crash before response for testing")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
example_hdr = {"Header1": "Value1", "Header2": "Value2"}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Default(WorkerEntrypoint):
|
|
107
|
+
# Each path in this handler serves one of the WSGI apps above; the
|
|
108
|
+
# assertions live in tests/test_wsgi.py.
|
|
109
|
+
async def fetch(self, request):
|
|
110
|
+
from js import URL
|
|
111
|
+
|
|
112
|
+
url = URL.new(request.url)
|
|
113
|
+
path = url.pathname
|
|
114
|
+
|
|
115
|
+
if path == "/echo-body":
|
|
116
|
+
return await wsgi.fetch(echo_body_app, request, self.env)
|
|
117
|
+
elif path == "/meta":
|
|
118
|
+
return await wsgi.fetch(echo_meta_app, request, self.env)
|
|
119
|
+
elif path == "/cookies":
|
|
120
|
+
return await wsgi.fetch(cookies_app, request, self.env)
|
|
121
|
+
elif path == "/stream":
|
|
122
|
+
return await wsgi.fetch(streaming_app, request, self.env)
|
|
123
|
+
|
|
124
|
+
return await wsgi.fetch(header_echo_app, request, self.env)
|
|
125
|
+
|
|
126
|
+
async def test(self, ctrl):
|
|
127
|
+
os.chdir("/session/metadata/tests")
|
|
128
|
+
args = [".", "-vv"]
|
|
129
|
+
if self.env.color:
|
|
130
|
+
args.append("--color=yes")
|
|
131
|
+
assert pytest.main(args) == 0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import _workers_sdk_entropy_import_context_loader
|