workers-runtime-sdk 1.8.5__tar.gz → 1.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/CHANGELOG.md +33 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/PKG-INFO +1 -1
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/pyproject.toml +1 -1
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/entrypoints.py +41 -14
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/utils.py +50 -3
- workers_runtime_sdk-1.9.0/src/workers/workflows.js +91 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_workflow.py +37 -2
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/worker_workflow.py +64 -4
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/test_in_workerd.py +17 -4
- workers_runtime_sdk-1.9.0/tests/workerd-test/workflow/pyproject.toml +5 -0
- workers_runtime_sdk-1.9.0/tests/workerd-test/workflow/worker.js +112 -0
- workers_runtime_sdk-1.9.0/tests/workerd-test/workflow/worker.py +72 -0
- workers_runtime_sdk-1.9.0/tests/workerd-test/workflow/workflow.wd-test +30 -0
- workers_runtime_sdk-1.9.0/tests/workerd-test/workflow/wrangler.jsonc +5 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/uv.lock +1 -1
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/.gitignore +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/AGENTS.md +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/README.md +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/_cloudflare_compat_flags.pyi +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/_pyodide_entrypoint_helper.pyi +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/_workers_sdk_entropy_import_context.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/_workers_sdk_package_patches.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/_workers_sdk_patch_loader.pth +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/_workers_sdk_patch_loader.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/asgi.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/__init__.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/asgi.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/blob.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/fetch.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/formdata.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/py.typed +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/request.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/response.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/rpc.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/types.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/workflows.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/src/workers/wsgi.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/public/data.json +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/public/hello.txt +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/conftest.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_analytics_engine.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_d1.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_do.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_hyperdrive_mysql.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_hyperdrive_postgresql.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_images.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_kv.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_mtls.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_queue.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_r2.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_ratelimit.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_vectorize.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_version_metadata.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/worker_durable_object.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/conftest.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/test_bindings.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/test_fastapi.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/test_flask.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/test_web_frameworks.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/_client.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/conftest.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/__init__.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/api_urls.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/auth_backend.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/drf_auth.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/drf_serializers.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/drf_throttling.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/drf_views.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/forms.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/middleware.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/__init__.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/templatetags/custom_tags.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/urls.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/django_app/views.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/templates/base.html +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/templates/context_test.html +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/templates/csrf_form.html +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/templates/hello.html +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_async_utilities.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_auth.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_caching.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_cors.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_csrf.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_drf_auth_permissions.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_drf_content_negotiation.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_drf_serializers.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_drf_throttling.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_drf_versioning.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_drf_views.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_file_uploads.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_forms.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_middleware.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_pagination.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_request_response.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_sessions.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_signals.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_streaming.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_templates.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_url_routing.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/test_views_basic.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/src/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/django-async/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/public/data.json +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/public/index.html +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/public/style.css +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/_client.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/conftest.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/static/app.css +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/static/data.json +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/static/hello.txt +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/static/index.html +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_assets.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_error_handling.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_platform_boundaries.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_platform_io.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_platform_lifecycle.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_response_types.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_routing.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_staticfiles.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_sync_handlers.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/test_websocket.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/src/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/fastapi-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/flask-tests/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/flask-tests/src/test_response.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/flask-tests/src/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/web-frameworks-test/flask-tests/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi/asgi.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi/tests/test_asgi.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi-ws-disconnect/asgi-ws-disconnect.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi-ws-disconnect/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi-ws-disconnect/tests/test_ws_disconnect.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi-ws-disconnect/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/asgi-ws-disconnect/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object/durable-object.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-abort/durable-object-abort.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-abort/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-abort/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-abort/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-inheritance/durable-object-inheritance.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-inheritance/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-inheritance/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-inheritance/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-websocket/durable-object-websocket.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-websocket/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-websocket/tester.js +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-websocket/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/durable-object-websocket/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/entropy-patches.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_aiohttp_websocket.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_langsmith.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_litestar.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_mcp.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_numpy.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_opentelemetry.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_pydantic.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_rust_packages.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/tests/test_ssl_avoidance.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/entropy-patches/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/http-client.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/origin.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/tests/test_http_client.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/tests/test_urllib_request.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/http-client/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/python-rpc/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/python-rpc/python-rpc.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/python-rpc/worker.js +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/python-rpc/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/python-rpc/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/sdk/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/sdk/sdk.wd-test +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/sdk/server.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/sdk/tests/test_sdk.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/sdk/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/sdk/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/wsgi/pyproject.toml +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/wsgi/tests/test_wsgi.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/wsgi/worker.py +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/wsgi/wrangler.jsonc +0 -0
- {workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/workerd-test/wsgi/wsgi.wd-test +0 -0
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.9.0 (2026-09-18)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **runtime-sdk**: Make NonRetryableError raised in a workflow step non-retryable
|
|
10
|
+
([`1c1980a`](https://github.com/cloudflare/workers-py/commit/1c1980afabaa27d39bf18d3fe19c7385c2890fa6))
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **runtime-sdk**: Add helpers to import JavaScript modules shipped with the SDK
|
|
15
|
+
([`caa9364`](https://github.com/cloudflare/workers-py/commit/caa9364f38a111d5496f81f6bc23ebb88d191928))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## v1.8.6 (2026-09-18)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- **runtime-sdk**: Convert workflow step results with python_to_rpc
|
|
23
|
+
([`4220da8`](https://github.com/cloudflare/workers-py/commit/4220da80451073afd7140e091811a8dd4b879aa5))
|
|
24
|
+
|
|
25
|
+
- **runtime-sdk**: Don't crash in _from_js_error on messages without a traceback
|
|
26
|
+
([`a9159df`](https://github.com/cloudflare/workers-py/commit/a9159df2ede8f1a2ff420b8bac1d0e0199506e86))
|
|
27
|
+
|
|
28
|
+
- **runtime-sdk**: Key workflow step memoisation by closure instead of step name
|
|
29
|
+
([`610d18f`](https://github.com/cloudflare/workers-py/commit/610d18f7ce350bbd40cd20312bb26334f67923f5))
|
|
30
|
+
|
|
31
|
+
- **runtime-sdk**: Raise a TypeError for invalid legacy `depends` entries
|
|
32
|
+
([`10a2bcc`](https://github.com/cloudflare/workers-py/commit/10a2bccbe91d06e8c0fb187a661bb78a4d236342))
|
|
33
|
+
|
|
34
|
+
- **runtime-sdk**: Recover Python exception types under enhanced error serialization
|
|
35
|
+
([`53be6db`](https://github.com/cloudflare/workers-py/commit/53be6db4a8ff62e03b2eb4e4896d45d6dd5d748b))
|
|
36
|
+
|
|
37
|
+
|
|
5
38
|
## v1.8.5 (2026-09-17)
|
|
6
39
|
|
|
7
40
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workers-runtime-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Python SDK for Cloudflare Workers
|
|
5
5
|
Project-URL: Homepage, https://github.com/cloudflare/workers-py
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/cloudflare/workers-py/issues
|
|
@@ -17,7 +17,11 @@ from .rpc import (
|
|
|
17
17
|
python_from_rpc,
|
|
18
18
|
python_to_rpc,
|
|
19
19
|
)
|
|
20
|
-
from .utils import
|
|
20
|
+
from .utils import (
|
|
21
|
+
_from_js_error,
|
|
22
|
+
_is_js_instance,
|
|
23
|
+
import_sdk_javascript_module_async,
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
if TYPE_CHECKING:
|
|
23
27
|
from js import DurableObjectState, Env, ExecutionContext
|
|
@@ -116,7 +120,7 @@ class _WorkflowStepWrapper:
|
|
|
116
120
|
func, depends, implicit
|
|
117
121
|
)
|
|
118
122
|
results = await self._gather_results(results_future_list, concurrent)
|
|
119
|
-
return await _do_call(self, step_name, config, func, *results)
|
|
123
|
+
return await _do_call(self, step_name, wrapper, config, func, *results)
|
|
120
124
|
|
|
121
125
|
wrapper._step_name = step_name
|
|
122
126
|
self.step_closures[step_name] = wrapper
|
|
@@ -157,7 +161,13 @@ class _WorkflowStepWrapper:
|
|
|
157
161
|
if p.name == "ctx":
|
|
158
162
|
results_future_list.append(p)
|
|
159
163
|
else:
|
|
160
|
-
|
|
164
|
+
dep = depends[curr]
|
|
165
|
+
if not hasattr(dep, "_step_name"):
|
|
166
|
+
raise TypeError(
|
|
167
|
+
f"'depends' entry for parameter {p.name!r} is not a "
|
|
168
|
+
"function decorated with step.do"
|
|
169
|
+
)
|
|
170
|
+
results_future_list.append(dep)
|
|
161
171
|
curr += 1
|
|
162
172
|
|
|
163
173
|
return results_future_list
|
|
@@ -194,17 +204,17 @@ class _WorkflowStepWrapper:
|
|
|
194
204
|
)
|
|
195
205
|
|
|
196
206
|
async def _resolve_dependency(self, dep):
|
|
197
|
-
if
|
|
207
|
+
if isinstance(dep, inspect.Parameter) and dep.name == "ctx":
|
|
198
208
|
return dep
|
|
199
|
-
elif dep
|
|
200
|
-
return self._memoized_dependencies[dep
|
|
201
|
-
elif dep
|
|
202
|
-
return await self._in_flight[dep
|
|
209
|
+
elif dep in self._memoized_dependencies:
|
|
210
|
+
return self._memoized_dependencies[dep]
|
|
211
|
+
elif dep in self._in_flight:
|
|
212
|
+
return await self._in_flight[dep]
|
|
203
213
|
|
|
204
214
|
return await dep()
|
|
205
215
|
|
|
206
216
|
|
|
207
|
-
async def _do_call(entrypoint, name, config, callback, *results):
|
|
217
|
+
async def _do_call(entrypoint, name, key, config, callback, *results):
|
|
208
218
|
async def _callback(ctx=None):
|
|
209
219
|
# deconstruct the actual ctx object
|
|
210
220
|
resolved_results = tuple(
|
|
@@ -217,7 +227,8 @@ async def _do_call(entrypoint, name, config, callback, *results):
|
|
|
217
227
|
|
|
218
228
|
if inspect.iscoroutine(result):
|
|
219
229
|
result = await result
|
|
220
|
-
|
|
230
|
+
# The step result crosses the RPC boundary back to the Workflows engine
|
|
231
|
+
return python_to_rpc(result)
|
|
221
232
|
|
|
222
233
|
async def _closure():
|
|
223
234
|
try:
|
|
@@ -233,13 +244,13 @@ async def _do_call(entrypoint, name, config, callback, *results):
|
|
|
233
244
|
raise _from_js_error(exc) from exc
|
|
234
245
|
|
|
235
246
|
task = create_task(_closure())
|
|
236
|
-
entrypoint._in_flight[
|
|
247
|
+
entrypoint._in_flight[key] = task
|
|
237
248
|
|
|
238
249
|
try:
|
|
239
250
|
result = await task
|
|
240
|
-
entrypoint._memoized_dependencies[
|
|
251
|
+
entrypoint._memoized_dependencies[key] = result
|
|
241
252
|
finally:
|
|
242
|
-
|
|
253
|
+
entrypoint._in_flight.pop(key, None)
|
|
243
254
|
|
|
244
255
|
return result
|
|
245
256
|
|
|
@@ -287,6 +298,22 @@ def _wrap_class(cls):
|
|
|
287
298
|
return cls
|
|
288
299
|
|
|
289
300
|
|
|
301
|
+
_workflows_js_module = None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
async def _wrap_js_workflow_step(js_step):
|
|
305
|
+
"""
|
|
306
|
+
Wrap the JS `WorkflowStep` stub with `wrapWorkflowStep` from the SDK's `workflows.js`.
|
|
307
|
+
|
|
308
|
+
The wrapper makes a Python `NonRetryableError` raised inside a step reach the Workflows
|
|
309
|
+
engine as a JS error the engine recognises as non-retryable (see `workflows.js`).
|
|
310
|
+
"""
|
|
311
|
+
global _workflows_js_module # noqa: PLW0603
|
|
312
|
+
if _workflows_js_module is None:
|
|
313
|
+
_workflows_js_module = await import_sdk_javascript_module_async("workflows.js")
|
|
314
|
+
return _workflows_js_module.wrapWorkflowStep(js_step)
|
|
315
|
+
|
|
316
|
+
|
|
290
317
|
def _wrap_workflow_step(cls):
|
|
291
318
|
run_fn = cls.__dict__.get("run")
|
|
292
319
|
if run_fn is None:
|
|
@@ -297,7 +324,7 @@ def _wrap_workflow_step(cls):
|
|
|
297
324
|
if event is not None:
|
|
298
325
|
event = python_from_rpc(event)
|
|
299
326
|
if step is not None:
|
|
300
|
-
step = _WorkflowStepWrapper(step)
|
|
327
|
+
step = _WorkflowStepWrapper(await _wrap_js_workflow_step(step))
|
|
301
328
|
|
|
302
329
|
result = run_fn(self, event, step, *args, **kwargs)
|
|
303
330
|
|
|
@@ -126,6 +126,25 @@ def import_from_javascript(module_name: str) -> Any:
|
|
|
126
126
|
raise
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
# Directory, relative to the worker bundle root, that `pywrangler sync` vendors Python packages
|
|
130
|
+
# into. wrangler registers `.js`/`.mjs` files found under `python_modules/workers/` as ES modules
|
|
131
|
+
# (everything else in `python_modules/` is opaque data), which is what lets them be imported via
|
|
132
|
+
# `import_from_javascript`.
|
|
133
|
+
_SDK_JS_MODULE_PREFIX = "python_modules/workers/"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def import_sdk_javascript_module_async(name: str) -> Any:
|
|
137
|
+
"""
|
|
138
|
+
Asynchronous function to import an sdk js module
|
|
139
|
+
|
|
140
|
+
This does not rely on JSPI, so it also works with Pyodide 0.26.0a2.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
return await _pyodide_entrypoint_helper.doAnImport(_SDK_JS_MODULE_PREFIX + name)
|
|
144
|
+
except JsException as e:
|
|
145
|
+
raise ImportError(f"Failed to import '{name}': {e}") from e
|
|
146
|
+
|
|
147
|
+
|
|
129
148
|
@contextmanager
|
|
130
149
|
def patch_env(
|
|
131
150
|
d: dict[str, Any] | Sequence[tuple[str, Any]] | None = None, **kwds: dict[str, Any]
|
|
@@ -144,14 +163,42 @@ def _to_python_exception(exc: JsException) -> Exception:
|
|
|
144
163
|
return exc
|
|
145
164
|
|
|
146
165
|
|
|
166
|
+
_NON_RETRYABLE_ERROR_NAME = "NonRetryableError"
|
|
167
|
+
|
|
168
|
+
|
|
147
169
|
def _from_js_error(exc: JsException) -> Exception:
|
|
148
170
|
# convert into Python exception after a full round trip
|
|
149
171
|
# Python - JS - Python
|
|
150
|
-
|
|
172
|
+
message = exc.message or ""
|
|
173
|
+
|
|
174
|
+
# A NonRetryableError raised inside a workflow step is translated by the
|
|
175
|
+
# runtime into a JS error named "NonRetryableError" before it reaches the
|
|
176
|
+
# Workflows engine, which is how the engine knows not to retry the step.
|
|
177
|
+
# When the engine rethrows it back to us the name either survives, or is
|
|
178
|
+
# folded into the message as a prefix.
|
|
179
|
+
if getattr(exc, "name", None) == _NON_RETRYABLE_ERROR_NAME:
|
|
180
|
+
return NonRetryableError(message)
|
|
181
|
+
if message == _NON_RETRYABLE_ERROR_NAME:
|
|
182
|
+
return NonRetryableError()
|
|
183
|
+
if message.startswith(_NON_RETRYABLE_ERROR_NAME + ": "):
|
|
184
|
+
return NonRetryableError(message[len(_NON_RETRYABLE_ERROR_NAME) + 2 :])
|
|
185
|
+
|
|
186
|
+
# A Python exception that escaped to JS is a Pyodide `PythonError` whose
|
|
187
|
+
# message is the formatted traceback. Depending on how it was serialized
|
|
188
|
+
# over RPC it either keeps its name or arrives as a plain `Error` with
|
|
189
|
+
# "PythonError: " folded into the message
|
|
190
|
+
if getattr(exc, "name", None) != "PythonError" and not message.startswith(
|
|
191
|
+
"PythonError"
|
|
192
|
+
):
|
|
151
193
|
return _to_python_exception(exc)
|
|
152
194
|
|
|
153
|
-
# extract the Python exception type from the traceback
|
|
154
|
-
|
|
195
|
+
# extract the Python exception type from the last line of the traceback. The
|
|
196
|
+
# message may have been stripped down to just "PythonError" when crossing an RPC
|
|
197
|
+
# boundary, in which case there is no traceback to inspect.
|
|
198
|
+
lines = message.rstrip().split("\n")
|
|
199
|
+
if len(lines) < 2:
|
|
200
|
+
return _to_python_exception(exc)
|
|
201
|
+
error_message_last_line = lines[-1]
|
|
155
202
|
if error_message_last_line.startswith("TypeError"):
|
|
156
203
|
return TypeError(error_message_last_line)
|
|
157
204
|
elif error_message_last_line.startswith("ValueError"):
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// JavaScript helpers for Python Workflows
|
|
2
|
+
|
|
3
|
+
const NON_RETRYABLE_ERROR_NAME = "NonRetryableError";
|
|
4
|
+
|
|
5
|
+
function isPythonError(e) {
|
|
6
|
+
return (
|
|
7
|
+
e instanceof Error &&
|
|
8
|
+
e.constructor?.name === "PythonError" &&
|
|
9
|
+
typeof e.type === "string"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Extract the message the user passed to the Python exception from the
|
|
14
|
+
// traceback stored in `PythonError.message`.
|
|
15
|
+
//
|
|
16
|
+
// The last non-empty line of a formatted traceback is
|
|
17
|
+
// `<qualified.ExceptionType>: <message>`, or just `<qualified.ExceptionType>`
|
|
18
|
+
// when the exception was raised without a message. For example, given
|
|
19
|
+
//
|
|
20
|
+
// Traceback (most recent call last):
|
|
21
|
+
// File "/session/metadata/worker.py", line 19, in non_retryable
|
|
22
|
+
// raise NonRetryableError("do not retry")
|
|
23
|
+
// workers.workflows.NonRetryableError: do not retry
|
|
24
|
+
//
|
|
25
|
+
// this returns "do not retry", and given
|
|
26
|
+
//
|
|
27
|
+
// Traceback (most recent call last):
|
|
28
|
+
// File "/session/metadata/worker.py", line 34, in no_message
|
|
29
|
+
// raise NonRetryableError()
|
|
30
|
+
// workers.workflows.NonRetryableError
|
|
31
|
+
//
|
|
32
|
+
// it returns "".
|
|
33
|
+
function pythonExceptionMessage(e) {
|
|
34
|
+
const lines = e.message.split("\n").filter((line) => line.trim() !== "");
|
|
35
|
+
const last = lines.at(-1) ?? "";
|
|
36
|
+
const sep = last.indexOf(": ");
|
|
37
|
+
return sep === -1 ? "" : last.slice(sep + 2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Wraps a Python step callback passed to `WorkflowStep.do()`.
|
|
41
|
+
//
|
|
42
|
+
// If the error thrown is a Python NonRetryable error, translate it into a JS
|
|
43
|
+
// error that the engine will recognize as non retryable. Leave other errors
|
|
44
|
+
// alone.
|
|
45
|
+
export function wrapWorkflowStepCallback(pyCallback) {
|
|
46
|
+
return async function (...args) {
|
|
47
|
+
try {
|
|
48
|
+
return await pyCallback(...args);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (isPythonError(e) && e.type === NON_RETRYABLE_ERROR_NAME) {
|
|
51
|
+
const err = new Error(pythonExceptionMessage(e));
|
|
52
|
+
err.name = NON_RETRYABLE_ERROR_NAME;
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Wraps the `WorkflowStep` RPC stub passed to a Python
|
|
61
|
+
// `WorkflowEntrypoint.run()` so that the callback given to `step.do()` goes
|
|
62
|
+
// through `wrapWorkflowStepCallback`.
|
|
63
|
+
//
|
|
64
|
+
// Because the wrapped `do` returns the stub's own promise, a Python callback
|
|
65
|
+
// passed to it keeps exactly the lifetime it would have had without the
|
|
66
|
+
// wrapper.
|
|
67
|
+
export function wrapWorkflowStep(step) {
|
|
68
|
+
// RPC stubs are callable, so `typeof step` is 'function'.
|
|
69
|
+
if (
|
|
70
|
+
step === null ||
|
|
71
|
+
(typeof step !== "object" && typeof step !== "function")
|
|
72
|
+
) {
|
|
73
|
+
return step;
|
|
74
|
+
}
|
|
75
|
+
return new Proxy(step, {
|
|
76
|
+
apply(target, thisArg, args) {
|
|
77
|
+
return Reflect.apply(target, thisArg, args);
|
|
78
|
+
},
|
|
79
|
+
get(target, prop) {
|
|
80
|
+
if (prop !== "do") {
|
|
81
|
+
return Reflect.get(target, prop);
|
|
82
|
+
}
|
|
83
|
+
return function (name, ...rest) {
|
|
84
|
+
const args = rest.map((arg) =>
|
|
85
|
+
typeof arg === "function" ? wrapWorkflowStepCallback(arg) : arg
|
|
86
|
+
);
|
|
87
|
+
return target.do(name, ...args);
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
{workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/test_workflow.py
RENAMED
|
@@ -149,7 +149,17 @@ async def test_step_retry_config(env):
|
|
|
149
149
|
async def test_non_retryable_error(env):
|
|
150
150
|
instance = await env.MY_WORKFLOW.create({"params": {"mode": "non_retryable"}})
|
|
151
151
|
status = await _poll(instance)
|
|
152
|
-
assert status["status"] == "
|
|
152
|
+
assert status["status"] == "complete", f"unexpected status: {dict(status)!r}"
|
|
153
|
+
out = status["output"]
|
|
154
|
+
# The step would succeed on attempt 2, so `retried` means the engine ignored
|
|
155
|
+
# the NonRetryableError.
|
|
156
|
+
assert out["retried"] is False, f"step was retried: {out!r}"
|
|
157
|
+
# ...and the error must come back to `run()` as a Python NonRetryableError.
|
|
158
|
+
assert out["caught"] == "NonRetryableError", out
|
|
159
|
+
# The message is preserved end to end in workerd, but miniflare currently
|
|
160
|
+
# truncates error messages crossing from the Python worker into its
|
|
161
|
+
# Workflows engine at the first ": " so accept an empty message here.
|
|
162
|
+
assert out["message"] in ("do not retry", ""), out
|
|
153
163
|
|
|
154
164
|
|
|
155
165
|
async def test_error_handling_catch(env):
|
|
@@ -159,11 +169,36 @@ async def test_error_handling_catch(env):
|
|
|
159
169
|
# Per the docs, a step error propagates to run() and is catchable with
|
|
160
170
|
# `except Exception`. Neither the concrete type nor the original message is
|
|
161
171
|
# guaranteed to survive the RPC layer, so we assert the reliable contract:
|
|
162
|
-
# the error was caught and a message was produced
|
|
172
|
+
# the error was caught and a message was produced, and that the SDK's own
|
|
173
|
+
# error translation did not blow up (e.g. IndexError while parsing it).
|
|
163
174
|
assert status["output"]["caught"] is not None
|
|
175
|
+
assert status["output"]["caught"] != "IndexError"
|
|
164
176
|
assert status["output"]["message"]
|
|
165
177
|
|
|
166
178
|
|
|
179
|
+
async def test_duplicate_step_names(env):
|
|
180
|
+
instance = await env.MY_WORKFLOW.create(
|
|
181
|
+
{"params": {"mode": "duplicate_step_names"}}
|
|
182
|
+
)
|
|
183
|
+
status = await _poll(instance)
|
|
184
|
+
assert status["status"] == "complete", f"unexpected status: {dict(status)!r}"
|
|
185
|
+
assert status["output"]["concurrent"] == [1, 2]
|
|
186
|
+
assert status["output"]["uses"] == 20
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
async def test_step_output_conversion(env):
|
|
190
|
+
instance = await env.MY_WORKFLOW.create(
|
|
191
|
+
{"params": {"mode": "step_output_conversion"}}
|
|
192
|
+
)
|
|
193
|
+
status = await _poll(instance)
|
|
194
|
+
assert status["status"] == "complete", f"unexpected status: {dict(status)!r}"
|
|
195
|
+
out = status["output"]
|
|
196
|
+
assert out["when_is_datetime"] is True
|
|
197
|
+
assert out["year"] == 2026
|
|
198
|
+
assert out["nothing_is_none"] is True
|
|
199
|
+
assert out["nested_nothing_is_none"] is True
|
|
200
|
+
|
|
201
|
+
|
|
167
202
|
# The tests below pass pre-converted (to_js) objects, the legacy pattern from the
|
|
168
203
|
# Workflows docs, to ensure existing code keeps working now that the binding
|
|
169
204
|
# auto-converts plain Python objects.
|
{workers_runtime_sdk-1.8.5 → workers_runtime_sdk-1.9.0}/tests/bindings-test/src/worker_workflow.py
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import datetime
|
|
2
3
|
|
|
3
4
|
from workers import WorkflowEntrypoint
|
|
@@ -19,6 +20,8 @@ class TestWorkflow(WorkflowEntrypoint):
|
|
|
19
20
|
"retry": self._retry,
|
|
20
21
|
"non_retryable": self._non_retryable,
|
|
21
22
|
"catch_error": self._catch_error,
|
|
23
|
+
"duplicate_step_names": self._duplicate_step_names,
|
|
24
|
+
"step_output_conversion": self._step_output_conversion,
|
|
22
25
|
}
|
|
23
26
|
handler = handlers.get(mode)
|
|
24
27
|
if handler is None:
|
|
@@ -106,14 +109,71 @@ class TestWorkflow(WorkflowEntrypoint):
|
|
|
106
109
|
return await flaky()
|
|
107
110
|
|
|
108
111
|
async def _non_retryable(self, event, step):
|
|
112
|
+
# Fails on the first attempt with NonRetryableError and would succeed on a
|
|
113
|
+
# second attempt. If the engine honours NonRetryableError the step fails
|
|
114
|
+
# once and `run()` sees a NonRetryableError; if the engine retries, the step
|
|
115
|
+
# completes with `attempt == 2`.
|
|
109
116
|
@step.do(
|
|
110
117
|
"non-retryable-step",
|
|
111
|
-
config={"retries": {"limit":
|
|
118
|
+
config={"retries": {"limit": 3, "delay": 0, "backoff": "constant"}},
|
|
112
119
|
)
|
|
113
|
-
async def boom():
|
|
114
|
-
|
|
120
|
+
async def boom(ctx):
|
|
121
|
+
if int(ctx["attempt"]) < 2:
|
|
122
|
+
raise NonRetryableError("do not retry")
|
|
123
|
+
return {"retried": True, "attempt": int(ctx["attempt"])}
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
result = await boom()
|
|
127
|
+
except NonRetryableError as exc:
|
|
128
|
+
return {
|
|
129
|
+
"retried": False,
|
|
130
|
+
"caught": "NonRetryableError",
|
|
131
|
+
"message": str(exc),
|
|
132
|
+
}
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
return {"retried": False, "caught": type(exc).__name__, "message": str(exc)}
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
async def _duplicate_step_names(self, event, step):
|
|
138
|
+
# The engine disambiguates repeated step names with a counter, so two
|
|
139
|
+
# steps sharing a name must not share memoised results or in-flight tasks.
|
|
140
|
+
@step.do("dup")
|
|
141
|
+
async def first():
|
|
142
|
+
return 1
|
|
143
|
+
|
|
144
|
+
@step.do("dup")
|
|
145
|
+
async def second():
|
|
146
|
+
return 2
|
|
147
|
+
|
|
148
|
+
# Implicit dependencies resolve by step name, so `dup` refers to the most
|
|
149
|
+
# recently registered closure (`second`) and must return its result.
|
|
150
|
+
@step.do()
|
|
151
|
+
async def uses(dup):
|
|
152
|
+
return dup * 10
|
|
153
|
+
|
|
154
|
+
# Run both same-named steps concurrently so they are in flight together.
|
|
155
|
+
concurrent = list(await asyncio.gather(first(), second()))
|
|
156
|
+
return {"concurrent": concurrent, "uses": await uses()}
|
|
157
|
+
|
|
158
|
+
async def _step_output_conversion(self, event, step):
|
|
159
|
+
@step.do("produce")
|
|
160
|
+
async def produce():
|
|
161
|
+
return {
|
|
162
|
+
"when": datetime.datetime(2026, 1, 2, 3, 4, 5),
|
|
163
|
+
"nothing": None,
|
|
164
|
+
"nested": {"nothing": None},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@step.do()
|
|
168
|
+
async def consume(produce):
|
|
169
|
+
return {
|
|
170
|
+
"when_is_datetime": isinstance(produce["when"], datetime.datetime),
|
|
171
|
+
"year": produce["when"].year,
|
|
172
|
+
"nothing_is_none": produce["nothing"] is None,
|
|
173
|
+
"nested_nothing_is_none": produce["nested"]["nothing"] is None,
|
|
174
|
+
}
|
|
115
175
|
|
|
116
|
-
return await
|
|
176
|
+
return await consume()
|
|
117
177
|
|
|
118
178
|
async def _catch_error(self, event, step):
|
|
119
179
|
@step.do(
|
|
@@ -66,14 +66,27 @@ def embed(dir: Path, root: Path, level: int = 0):
|
|
|
66
66
|
module_path = path.absolute().relative_to(module_path_root)
|
|
67
67
|
embed_path = path.absolute().relative_to(root)
|
|
68
68
|
if path.suffix == ".py":
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
module_type = "pythonModule"
|
|
70
|
+
elif _is_sdk_js_module(path.relative_to(dir)):
|
|
71
|
+
module_type = "esModule"
|
|
72
72
|
else:
|
|
73
|
-
|
|
73
|
+
module_type = "data"
|
|
74
|
+
modules.append(
|
|
75
|
+
f'(name = "{module_path}", {module_type} = embed "{embed_path}")'
|
|
76
|
+
)
|
|
74
77
|
return modules
|
|
75
78
|
|
|
76
79
|
|
|
80
|
+
def _is_sdk_js_module(path_in_vendor_dir: Path) -> bool:
|
|
81
|
+
"""Mirror wrangler: `.js`/`.mjs` files under `python_modules/workers/` are ES modules.
|
|
82
|
+
|
|
83
|
+
Other vendored packages may ship `.js` assets that are not valid ES modules, so wrangler
|
|
84
|
+
only applies this rule to the SDK's own `workers/` package. Everything else stays `data`.
|
|
85
|
+
"""
|
|
86
|
+
in_sdk_package = path_in_vendor_dir.parts[:1] == ("workers",)
|
|
87
|
+
return in_sdk_package and path_in_vendor_dir.suffix in (".js", ".mjs")
|
|
88
|
+
|
|
89
|
+
|
|
77
90
|
@pytest.fixture(scope="module")
|
|
78
91
|
def bundle_cache_dir(tmp_path_factory):
|
|
79
92
|
yield tmp_path_factory.mktemp("bundle_cache")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Copyright (c) 2026 Cloudflare, Inc.
|
|
2
|
+
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
|
|
3
|
+
// https://opensource.org/licenses/Apache-2.0
|
|
4
|
+
import { RpcTarget } from 'cloudflare:workers';
|
|
5
|
+
|
|
6
|
+
import * as assert from 'node:assert';
|
|
7
|
+
|
|
8
|
+
// A stand-in for the Workflows engine's `WorkflowStep`. Like the real engine it runs the step
|
|
9
|
+
// callback over RPC, and it records the error it receives when the callback fails so the test
|
|
10
|
+
// can assert on what the engine would have seen.
|
|
11
|
+
class Context extends RpcTarget {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.errors = [];
|
|
15
|
+
this.sleeps = [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async do(name, ...rest) {
|
|
19
|
+
// do(name, callback) or do(name, config, callback)
|
|
20
|
+
const callback = rest.at(-1);
|
|
21
|
+
try {
|
|
22
|
+
return await callback({
|
|
23
|
+
step: { name, count: 1 },
|
|
24
|
+
attempt: 1,
|
|
25
|
+
config: {},
|
|
26
|
+
});
|
|
27
|
+
} catch (e) {
|
|
28
|
+
this.errors.push({ step: name, name: e.name, message: e.message });
|
|
29
|
+
// The engine rethrows so the workflow's `run()` can handle the error.
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async sleep(name, duration) {
|
|
35
|
+
this.sleeps.push({ name, duration });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The engine treats a step error as non-retryable when
|
|
40
|
+
// `error.name === 'NonRetryableError' || error.message.startsWith('NonRetryableError')`
|
|
41
|
+
// (the name is folded into the message when the error is tunneled over RPC without enhanced
|
|
42
|
+
// error serialization).
|
|
43
|
+
function isNonRetryable(e) {
|
|
44
|
+
return (
|
|
45
|
+
e.name === 'NonRetryableError' || e.message.startsWith('NonRetryableError')
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function nonRetryableMessage(e) {
|
|
50
|
+
return e.name === 'NonRetryableError'
|
|
51
|
+
? e.message
|
|
52
|
+
: e.message.replace(/^NonRetryableError(: )?/, '');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
async test(ctrl, env) {
|
|
57
|
+
const step = new Context();
|
|
58
|
+
const result = await env.PythonWorkflow.run(
|
|
59
|
+
{ payload: { foo: 'bar' } },
|
|
60
|
+
step
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// --- What the engine saw -------------------------------------------------------------
|
|
64
|
+
const byStep = Object.fromEntries(step.errors.map((e) => [e.step, e]));
|
|
65
|
+
assert.deepStrictEqual(Object.keys(byStep).sort(), [
|
|
66
|
+
'non_retryable',
|
|
67
|
+
'non_retryable_no_message',
|
|
68
|
+
'type_error',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// A Python NonRetryableError must arrive as a non-retryable JS error carrying the
|
|
72
|
+
// Python message, not as a generic PythonError with a traceback.
|
|
73
|
+
assert.ok(isNonRetryable(byStep.non_retryable), byStep.non_retryable);
|
|
74
|
+
assert.strictEqual(nonRetryableMessage(byStep.non_retryable), 'do not retry');
|
|
75
|
+
assert.ok(
|
|
76
|
+
isNonRetryable(byStep.non_retryable_no_message),
|
|
77
|
+
byStep.non_retryable_no_message
|
|
78
|
+
);
|
|
79
|
+
assert.strictEqual(
|
|
80
|
+
nonRetryableMessage(byStep.non_retryable_no_message),
|
|
81
|
+
''
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Other Python exceptions are left alone: they arrive as Pyodide's PythonError with the
|
|
85
|
+
// traceback as the message. Depending on the compatibility date the error is serialized
|
|
86
|
+
// either as `{name: 'PythonError', message: '<traceback>'}` (enhanced error
|
|
87
|
+
// serialization) or as `{name: 'Error', message: 'PythonError: <traceback>'}`.
|
|
88
|
+
assert.ok(!isNonRetryable(byStep.type_error), byStep.type_error);
|
|
89
|
+
assert.match(
|
|
90
|
+
`${byStep.type_error.name}: ${byStep.type_error.message}`,
|
|
91
|
+
/PythonError: Traceback/
|
|
92
|
+
);
|
|
93
|
+
assert.match(byStep.type_error.message, /TypeError: intentional type error/);
|
|
94
|
+
|
|
95
|
+
// --- What the Python workflow saw once the engine rethrew ----------------------------
|
|
96
|
+
assert.deepStrictEqual(result.non_retryable, {
|
|
97
|
+
caught: 'NonRetryableError',
|
|
98
|
+
message: 'do not retry',
|
|
99
|
+
});
|
|
100
|
+
assert.deepStrictEqual(result.non_retryable_no_message, {
|
|
101
|
+
caught: 'NonRetryableError',
|
|
102
|
+
message: '',
|
|
103
|
+
});
|
|
104
|
+
assert.strictEqual(result.type_error.caught, 'TypeError');
|
|
105
|
+
assert.match(result.type_error.message, /intentional type error/);
|
|
106
|
+
|
|
107
|
+
// The wrapped `step` still behaves like the real one otherwise.
|
|
108
|
+
assert.deepStrictEqual(result.ok, { attempt: 1, payload: { foo: 'bar' } });
|
|
109
|
+
assert.strictEqual(result.slept, true);
|
|
110
|
+
assert.deepStrictEqual(step.sleeps, [{ name: 'nap', duration: '1 second' }]);
|
|
111
|
+
},
|
|
112
|
+
};
|