cadence-python-client 0.2.1__tar.gz → 0.2.2__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.
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.cursorrules +5 -5
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/Makefile +1 -1
- {cadence_python_client-0.2.1/cadence_python_client.egg-info → cadence_python_client-0.2.2}/PKG-INFO +2 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/fn_signature.py +31 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/retry.py +8 -3
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/yarpc.py +6 -3
- cadence_python_client-0.2.2/cadence/_internal/workflow/active_cluster_selection_policy.py +32 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/context.py +28 -16
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/deterministic_event_loop.py +25 -2
- cadence_python_client-0.2.2/cadence/_internal/workflow/retry_policy.py +62 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/event_dispatcher.py +3 -2
- cadence_python_client-0.2.2/cadence/_internal/workflow/waiter.py +37 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/workflow_engine.py +30 -25
- cadence_python_client-0.2.2/cadence/_internal/workflow/workflow_instance.py +105 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/__init__.py +12 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/common_pb2.py +30 -12
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/common_pb2.pyi +29 -2
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/domain_pb2.py +22 -10
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/domain_pb2.pyi +24 -2
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/error_pb2.py +3 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/error_pb2.pyi +4 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/history_pb2.py +44 -44
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/history_pb2.pyi +6 -2
- cadence_python_client-0.2.2/cadence/api/v1/schedule_pb2.py +61 -0
- cadence_python_client-0.2.2/cadence/api/v1/schedule_pb2.pyi +154 -0
- cadence_python_client-0.2.2/cadence/api/v1/schedule_pb2_grpc.py +24 -0
- cadence_python_client-0.2.2/cadence/api/v1/service_domain_pb2.py +94 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_domain_pb2.pyi +68 -2
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_domain_pb2_grpc.py +88 -0
- cadence_python_client-0.2.2/cadence/api/v1/service_schedule_pb2.py +72 -0
- cadence_python_client-0.2.2/cadence/api/v1/service_schedule_pb2.pyi +163 -0
- cadence_python_client-0.2.2/cadence/api/v1/service_schedule_pb2_grpc.py +409 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_workflow_pb2.py +9 -9
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_workflow_pb2.pyi +4 -2
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_workflow_pb2_grpc.py +2 -2
- cadence_python_client-0.2.2/cadence/api/v1/workflow_pb2.py +91 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/workflow_pb2.pyi +29 -2
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/client.py +55 -7
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/pydantic_data_converter.py +33 -9
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/data_converter.py +18 -6
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/error.py +26 -8
- cadence_python_client-0.2.2/cadence/signal.py +102 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/workflow.py +62 -5
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2/cadence_python_client.egg-info}/PKG-INFO +2 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/SOURCES.txt +15 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/requires.txt +1 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/pyproject.toml +2 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/rpc/test_error.py +2 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/rpc/test_retry.py +1 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/test_fn_signature.py +41 -1
- cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_context_retry_policy.py +53 -0
- cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_deterministic_event_loop.py +190 -0
- cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_retry_policy.py +87 -0
- cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_signal_handling.py +1387 -0
- cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_waiter.py +121 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/data_converter_test.py +8 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/test_client_workflow.py +282 -1
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_registry.py +6 -9
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/test_client.py +87 -1
- cadence_python_client-0.2.2/tests/integration_tests/workflow/test_retry_policy.py +335 -0
- cadence_python_client-0.2.2/tests/integration_tests/workflow/test_signals.py +349 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/uv.lock +11 -0
- cadence_python_client-0.2.1/cadence/_internal/workflow/workflow_instance.py +0 -53
- cadence_python_client-0.2.1/cadence/api/v1/service_domain_pb2.py +0 -76
- cadence_python_client-0.2.1/cadence/api/v1/workflow_pb2.py +0 -89
- cadence_python_client-0.2.1/cadence/signal.py +0 -174
- cadence_python_client-0.2.1/tests/cadence/_internal/workflow/test_deterministic_event_loop.py +0 -96
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/dco.yml +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/pull_request_template.md +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/workflows/ci_checks.yml +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/workflows/python-publish.yml +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/workflows/semantic-pr.yml +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.gitignore +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.gitmodules +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/CONTRIBUTING.md +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/LICENSE +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/NOTICE +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/README.md +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_activity_executor.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_context.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_definition.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_heartbeat.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/error.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/decision_events_iterator.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/history_event_iterator.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/activity_state_machine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/cancellation.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/completion_state_machine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/decision_manager.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/decision_state_machine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/nondeterminism.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/timer_state_machine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/activity.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/common_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/decision_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/decision_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/decision_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/domain_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/error_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/history_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/query_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/query_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/query_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_meta_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_meta_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_meta_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_visibility_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_visibility_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_visibility_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_worker_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_worker_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_worker_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/tasklist_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/tasklist_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/tasklist_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/visibility_pb2.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/visibility_pb2.pyi +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/visibility_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/workflow_pb2_grpc.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/README.md +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_agent_runner.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_handoff.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_model.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_registry.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_tool.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/images/cadence-web-agent-run.jpg +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/openai_activities.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/constants.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/metrics.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/prometheus.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/client_example.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/grpc_usage_example.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/simple_usage_example.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/__init__.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_activity.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_base_task_handler.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_decision.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_decision_task_handler.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_poller.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_registry.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_types.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_worker.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/dependency_links.txt +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/top_level.txt +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/scripts/dev.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/scripts/fix_pyi_imports.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/scripts/generate_proto.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/setup.cfg +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/activity/test_activity_executor.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/activity/test_heartbeat.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_activity_state_machine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_decision_manager.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_nondeterminism.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_timer_state_machine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/test_decision_events_iterator.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/test_history_event_iterator.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/test_workflow_engine.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/common_activities.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/metrics/test_metrics.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/metrics/test_prometheus.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/test_activity.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/test_client.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_base_task_handler.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_decision_task_handler.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_decision_task_handler_integration.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_decision_worker_integration.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_poller.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_task_handler_integration.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_worker.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/conftest.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/conftest.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/docker-compose.yml +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/helper.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/nondeterminism/success.json +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/nondeterminism/test_nondeterministic_workflows.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_activities.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_continue_as_new.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_heartbeat.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_serialization.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_timer.py +0 -0
- {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_workflows.py +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- **Always use `uv` for Python package management**
|
|
5
5
|
- Use `uv run` for running Python commands instead of `python` directly
|
|
6
6
|
- Use `uv sync` for installing dependencies instead of `pip install`
|
|
7
|
-
- Use `uv tool run` for running development tools (pytest,
|
|
7
|
+
- Use `uv tool run` for running development tools (pytest, ruff, etc.); use `uv run mypy` so mypy uses the project environment (e.g. `types-grpcio`).
|
|
8
8
|
- Only use `pip` or `python` directly when specifically required by the tool or documentation
|
|
9
9
|
|
|
10
10
|
## Examples
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# ✅ Correct
|
|
13
13
|
uv run python scripts/generate_proto.py
|
|
14
14
|
uv run python -m pytest tests/
|
|
15
|
-
uv
|
|
15
|
+
uv run mypy cadence/
|
|
16
16
|
uv tool run ruff check
|
|
17
17
|
|
|
18
18
|
# ❌ Avoid
|
|
@@ -37,13 +37,13 @@ pip install -e ".[dev]"
|
|
|
37
37
|
## Code Quality
|
|
38
38
|
- **ALWAYS run linter and type checker after making code changes**
|
|
39
39
|
- Run linter with auto-fix: `uv tool run ruff check --fix`
|
|
40
|
-
- Run type checking: `uv
|
|
41
|
-
- Use `uv tool run ruff check --fix && uv
|
|
40
|
+
- Run type checking: `uv run mypy cadence/`
|
|
41
|
+
- Use `uv tool run ruff check --fix && uv run mypy cadence/` to run both together
|
|
42
42
|
- **Standard workflow**: Make changes → Run linter → Run type checker → Commit
|
|
43
43
|
|
|
44
44
|
## Development Workflow
|
|
45
45
|
1. Make code changes
|
|
46
46
|
2. Run `uv tool run ruff check --fix` (fixes formatting and linting issues)
|
|
47
|
-
3. Run `uv
|
|
47
|
+
3. Run `uv run mypy cadence/` (checks type safety)
|
|
48
48
|
4. Run `uv run python -m pytest` (run tests)
|
|
49
49
|
5. Commit changes
|
{cadence_python_client-0.2.1/cadence_python_client.egg-info → cadence_python_client-0.2.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadence-python-client
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python framework for authoring Cadence workflows and activities
|
|
5
5
|
Author: Cadence
|
|
6
6
|
License: Apache-2.0
|
|
@@ -43,6 +43,7 @@ Requires-Dist: pytest-docker>=3.2.3; extra == "dev"
|
|
|
43
43
|
Requires-Dist: opentelemetry-instrumentation-grpc==0.60b1; extra == "dev"
|
|
44
44
|
Requires-Dist: opentelemetry-sdk>=1.39.1; extra == "dev"
|
|
45
45
|
Requires-Dist: setuptools-scm[simple]>=9.2; extra == "dev"
|
|
46
|
+
Requires-Dist: types-grpcio>=1.0.0.20260408; extra == "dev"
|
|
46
47
|
Provides-Extra: docs
|
|
47
48
|
Requires-Dist: sphinx>=6.0.0; extra == "docs"
|
|
48
49
|
Requires-Dist: sphinx-rtd-theme>=1.2.0; extra == "docs"
|
{cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/fn_signature.py
RENAMED
|
@@ -53,8 +53,20 @@ class FnSignature:
|
|
|
53
53
|
def params_from_payload(
|
|
54
54
|
self, data_converter: DataConverter, payload: Payload
|
|
55
55
|
) -> list[Any]:
|
|
56
|
+
if not self.params:
|
|
57
|
+
return []
|
|
56
58
|
type_hints = [param.type_hint for param in self.params]
|
|
57
|
-
|
|
59
|
+
decoded = _decode_provided_values(data_converter, payload, type_hints)
|
|
60
|
+
for i, param in enumerate(self.params):
|
|
61
|
+
if i < len(decoded):
|
|
62
|
+
continue
|
|
63
|
+
if param.has_default:
|
|
64
|
+
decoded.append(param.default_value)
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"required parameter '{param.name}' (position {i}) not provided in payload"
|
|
68
|
+
)
|
|
69
|
+
return decoded
|
|
58
70
|
|
|
59
71
|
@staticmethod
|
|
60
72
|
def of(fn: Callable) -> "FnSignature":
|
|
@@ -88,3 +100,21 @@ class FnSignature:
|
|
|
88
100
|
return_type = hints.get("return", Any)
|
|
89
101
|
|
|
90
102
|
return FnSignature(params, return_type)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _decode_provided_values(
|
|
106
|
+
data_converter: DataConverter,
|
|
107
|
+
payload: Payload,
|
|
108
|
+
type_hints: Sequence[Type | None],
|
|
109
|
+
) -> list[Any]:
|
|
110
|
+
decoder = getattr(data_converter, "_decode_provided_values", None)
|
|
111
|
+
if callable(decoder):
|
|
112
|
+
return list(decoder(payload, type_hints))
|
|
113
|
+
|
|
114
|
+
counter = getattr(data_converter, "_payload_value_count", None)
|
|
115
|
+
if callable(counter):
|
|
116
|
+
provided_count = int(counter(payload, len(type_hints)))
|
|
117
|
+
return data_converter.from_data(payload, list(type_hints[:provided_count]))
|
|
118
|
+
|
|
119
|
+
# Backward compatibility
|
|
120
|
+
return data_converter.from_data(payload, list(type_hints))
|
|
@@ -60,6 +60,8 @@ class RetryInterceptor(UnaryUnaryClientInterceptor):
|
|
|
60
60
|
) -> Any:
|
|
61
61
|
loop = asyncio.get_running_loop()
|
|
62
62
|
expiration_interval = client_call_details.timeout
|
|
63
|
+
if expiration_interval is None:
|
|
64
|
+
expiration_interval = float("inf")
|
|
63
65
|
start_time = loop.time()
|
|
64
66
|
deadline = start_time + expiration_interval
|
|
65
67
|
|
|
@@ -68,7 +70,9 @@ class RetryInterceptor(UnaryUnaryClientInterceptor):
|
|
|
68
70
|
remaining = deadline - loop.time()
|
|
69
71
|
# Namedtuple methods start with an underscore to avoid conflicts and aren't actually private
|
|
70
72
|
# noinspection PyProtectedMember
|
|
71
|
-
call_details = client_call_details._replace(
|
|
73
|
+
call_details = client_call_details._replace( # type: ignore[attr-defined]
|
|
74
|
+
timeout=remaining
|
|
75
|
+
)
|
|
72
76
|
rpc_call = await continuation(call_details, request)
|
|
73
77
|
try:
|
|
74
78
|
await rpc_call
|
|
@@ -95,8 +99,9 @@ class RetryInterceptor(UnaryUnaryClientInterceptor):
|
|
|
95
99
|
|
|
96
100
|
def is_retryable(err: CadenceRpcError, call_details: ClientCallDetails) -> bool:
|
|
97
101
|
# Handle requests to the passive side, matching the Go and Java Clients
|
|
98
|
-
if
|
|
99
|
-
|
|
102
|
+
if (
|
|
103
|
+
call_details.method == GET_WORKFLOW_HISTORY # type: ignore[comparison-overlap]
|
|
104
|
+
and isinstance(err, EntityNotExistsError)
|
|
100
105
|
):
|
|
101
106
|
return (
|
|
102
107
|
err.active_cluster is not None
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Callable
|
|
1
|
+
from typing import Any, Callable, cast
|
|
2
2
|
|
|
3
3
|
from grpc.aio import Metadata
|
|
4
4
|
from grpc.aio import UnaryUnaryClientInterceptor, ClientCallDetails
|
|
@@ -40,6 +40,9 @@ class YarpcMetadataInterceptor(UnaryUnaryClientInterceptor):
|
|
|
40
40
|
|
|
41
41
|
# Namedtuple methods start with an underscore to avoid conflicts and aren't actually private
|
|
42
42
|
# noinspection PyProtectedMember
|
|
43
|
-
return
|
|
44
|
-
|
|
43
|
+
return cast(
|
|
44
|
+
ClientCallDetails,
|
|
45
|
+
client_call_details._replace( # type: ignore[attr-defined]
|
|
46
|
+
metadata=metadata, timeout=client_call_details.timeout or 60.0
|
|
47
|
+
),
|
|
45
48
|
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Adapt :class:`cadence.workflow.ActiveClusterSelectionPolicy` (TypedDict) to its protobuf wire form."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Mapping, cast
|
|
6
|
+
|
|
7
|
+
from cadence.api.v1 import common_pb2
|
|
8
|
+
from cadence.workflow import ActiveClusterSelectionPolicy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def active_cluster_selection_policy_to_proto(
|
|
12
|
+
policy: ActiveClusterSelectionPolicy | Mapping[str, object] | None,
|
|
13
|
+
) -> common_pb2.ActiveClusterSelectionPolicy | None:
|
|
14
|
+
"""Convert a user active-cluster selection policy to protobuf, or ``None`` if empty.
|
|
15
|
+
|
|
16
|
+
``None`` and an empty mapping both map to ``None``.
|
|
17
|
+
"""
|
|
18
|
+
if policy is None or (isinstance(policy, Mapping) and len(policy) == 0):
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
out = common_pb2.ActiveClusterSelectionPolicy()
|
|
22
|
+
|
|
23
|
+
if (ca := policy.get("cluster_attribute")) is not None:
|
|
24
|
+
ca_map = cast(Mapping[str, str], ca)
|
|
25
|
+
out.cluster_attribute.CopyFrom(
|
|
26
|
+
common_pb2.ClusterAttribute(
|
|
27
|
+
scope=ca_map.get("scope", ""),
|
|
28
|
+
name=ca_map.get("name", ""),
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return out
|
{cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/context.py
RENAMED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from contextlib import contextmanager
|
|
2
|
+
from asyncio import get_running_loop
|
|
2
3
|
from datetime import timedelta
|
|
3
4
|
from math import ceil
|
|
4
|
-
from typing import Iterator, Optional, Any, Unpack, Type, cast
|
|
5
|
+
from typing import Iterator, Optional, Any, Unpack, Type, cast, Callable
|
|
5
6
|
|
|
7
|
+
from cadence._internal.workflow.deterministic_event_loop import DeterministicEventLoop
|
|
8
|
+
from cadence._internal.workflow.retry_policy import retry_policy_to_proto
|
|
6
9
|
from cadence._internal.workflow.statemachine.decision_manager import DecisionManager
|
|
7
10
|
from cadence.api.v1.common_pb2 import ActivityType
|
|
8
11
|
from cadence.api.v1.decision_pb2 import (
|
|
@@ -11,15 +14,18 @@ from cadence.api.v1.decision_pb2 import (
|
|
|
11
14
|
)
|
|
12
15
|
from cadence.api.v1.tasklist_pb2 import TaskList, TaskListKind
|
|
13
16
|
from cadence.data_converter import DataConverter
|
|
14
|
-
from cadence.workflow import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
), # more than 1 hour is recommended to set up options explicitly like heartbeat for self recovery
|
|
20
|
-
schedule_to_start_timeout=timedelta(seconds=10),
|
|
17
|
+
from cadence.workflow import (
|
|
18
|
+
ActivityOptions,
|
|
19
|
+
ResultType,
|
|
20
|
+
WorkflowContext,
|
|
21
|
+
WorkflowInfo,
|
|
21
22
|
)
|
|
22
23
|
|
|
24
|
+
_DEFAULT_ACTIVITY_OPTIONS: ActivityOptions = {
|
|
25
|
+
"schedule_to_close_timeout": timedelta(hours=1),
|
|
26
|
+
"schedule_to_start_timeout": timedelta(seconds=10),
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
|
|
24
30
|
class Context(WorkflowContext):
|
|
25
31
|
def __init__(
|
|
@@ -45,7 +51,7 @@ class Context(WorkflowContext):
|
|
|
45
51
|
*args: Any,
|
|
46
52
|
**kwargs: Unpack[ActivityOptions],
|
|
47
53
|
) -> ResultType:
|
|
48
|
-
opts: ActivityOptions = {**
|
|
54
|
+
opts: ActivityOptions = {**_DEFAULT_ACTIVITY_OPTIONS, **kwargs}
|
|
49
55
|
if "schedule_to_close_timeout" not in opts and (
|
|
50
56
|
"schedule_to_start_timeout" not in opts
|
|
51
57
|
or "start_to_close_timeout" not in opts
|
|
@@ -87,14 +93,13 @@ class Context(WorkflowContext):
|
|
|
87
93
|
schedule_to_start_timeout=_round_to_nearest_second(schedule_to_start),
|
|
88
94
|
start_to_close_timeout=_round_to_nearest_second(start_to_close),
|
|
89
95
|
heartbeat_timeout=_round_to_nearest_second(heartbeat),
|
|
90
|
-
retry_policy=
|
|
96
|
+
retry_policy=retry_policy_to_proto(opts.get("retry_policy")),
|
|
91
97
|
header=None,
|
|
92
98
|
request_local_dispatch=False,
|
|
93
99
|
)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
101
|
+
future = self._decision_manager.schedule_activity(schedule_attributes)
|
|
102
|
+
result_payload = await future
|
|
98
103
|
|
|
99
104
|
result = self.data_converter().from_data(result_payload, [result_type])[0]
|
|
100
105
|
|
|
@@ -103,11 +108,12 @@ class Context(WorkflowContext):
|
|
|
103
108
|
async def start_timer(self, duration: timedelta):
|
|
104
109
|
if duration.total_seconds() <= 0: # shortcut
|
|
105
110
|
return
|
|
106
|
-
|
|
111
|
+
future = self._decision_manager.start_timer(
|
|
107
112
|
StartTimerDecisionAttributes(
|
|
108
113
|
start_to_fire_timeout=duration,
|
|
109
114
|
)
|
|
110
115
|
)
|
|
116
|
+
await future
|
|
111
117
|
|
|
112
118
|
def set_replay_mode(self, replay: bool) -> None:
|
|
113
119
|
"""Set whether the workflow is currently in replay mode."""
|
|
@@ -125,11 +131,17 @@ class Context(WorkflowContext):
|
|
|
125
131
|
"""Get the current replay time in milliseconds."""
|
|
126
132
|
return self._replay_current_time_milliseconds
|
|
127
133
|
|
|
134
|
+
async def wait_condition(self, predicate: Callable[[], bool]) -> None:
|
|
135
|
+
loop = cast(DeterministicEventLoop, get_running_loop())
|
|
136
|
+
await loop.create_waiter(predicate)
|
|
137
|
+
|
|
128
138
|
@contextmanager
|
|
129
139
|
def _activate(self) -> Iterator["Context"]:
|
|
130
140
|
token = WorkflowContext._var.set(self)
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
try:
|
|
142
|
+
yield self
|
|
143
|
+
finally:
|
|
144
|
+
WorkflowContext._var.reset(token)
|
|
133
145
|
|
|
134
146
|
|
|
135
147
|
def _round_to_nearest_second(delta: timedelta) -> timedelta:
|
|
@@ -8,6 +8,8 @@ import threading
|
|
|
8
8
|
from typing import Callable, Any, TypeVar, Coroutine, Awaitable, Generator
|
|
9
9
|
from typing_extensions import Unpack, TypeVarTuple
|
|
10
10
|
|
|
11
|
+
from cadence._internal.workflow.waiter import Waiter
|
|
12
|
+
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
12
14
|
|
|
13
15
|
|
|
@@ -32,6 +34,7 @@ class DeterministicEventLoop(AbstractEventLoop):
|
|
|
32
34
|
self._thread_id: int | None = None # indicate if the event loop is running
|
|
33
35
|
self._debug: bool = False
|
|
34
36
|
self._ready: collections.deque[events.Handle] = collections.deque()
|
|
37
|
+
self._waiters: list[Waiter] = []
|
|
35
38
|
self._stopping: bool = False
|
|
36
39
|
self._closed: bool = False
|
|
37
40
|
|
|
@@ -141,6 +144,13 @@ class DeterministicEventLoop(AbstractEventLoop):
|
|
|
141
144
|
def create_future(self) -> Future[Any]:
|
|
142
145
|
return futures.Future(loop=self)
|
|
143
146
|
|
|
147
|
+
def create_waiter(self, predicate: Callable[[], bool]) -> Waiter:
|
|
148
|
+
"""Register a predicate-driven awaitable."""
|
|
149
|
+
waiter = Waiter(predicate, self)
|
|
150
|
+
if not waiter.poll():
|
|
151
|
+
self._waiters.append(waiter)
|
|
152
|
+
return waiter
|
|
153
|
+
|
|
144
154
|
def _run_once(self) -> None:
|
|
145
155
|
ntodo = len(self._ready)
|
|
146
156
|
for i in range(ntodo):
|
|
@@ -149,6 +159,19 @@ class DeterministicEventLoop(AbstractEventLoop):
|
|
|
149
159
|
continue
|
|
150
160
|
handle._run()
|
|
151
161
|
|
|
162
|
+
# Poll waiters; only stop early if settling one schedules new work,
|
|
163
|
+
# so remaining waiters are not skipped.
|
|
164
|
+
i = 0
|
|
165
|
+
while i < len(self._waiters):
|
|
166
|
+
w = self._waiters[i]
|
|
167
|
+
ready_before = len(self._ready)
|
|
168
|
+
if w.poll():
|
|
169
|
+
del self._waiters[i]
|
|
170
|
+
if len(self._ready) > ready_before:
|
|
171
|
+
return
|
|
172
|
+
else:
|
|
173
|
+
i += 1
|
|
174
|
+
|
|
152
175
|
def _run_forever_setup(self) -> None:
|
|
153
176
|
self._check_closed()
|
|
154
177
|
self._check_running()
|
|
@@ -190,6 +213,7 @@ class DeterministicEventLoop(AbstractEventLoop):
|
|
|
190
213
|
logger.debug("Close %r", self)
|
|
191
214
|
self._closed = True
|
|
192
215
|
self._ready.clear()
|
|
216
|
+
self._waiters.clear()
|
|
193
217
|
|
|
194
218
|
def is_closed(self) -> bool:
|
|
195
219
|
"""Returns True if the event loop was closed."""
|
|
@@ -462,13 +486,12 @@ class DeterministicEventLoop(AbstractEventLoop):
|
|
|
462
486
|
)
|
|
463
487
|
|
|
464
488
|
def call_exception_handler(self, context: dict[str, Any]) -> None:
|
|
465
|
-
# This is called if a task has an unhandled exception. Short term, it's helpful to log these for debugging.
|
|
466
|
-
# Long term, we need some combination of failing decision tasks or workflows based on these errors.
|
|
467
489
|
message = context.get("message")
|
|
468
490
|
if not message:
|
|
469
491
|
message = "Unhandled exception in event loop"
|
|
470
492
|
|
|
471
493
|
exception = context.get("exception")
|
|
494
|
+
|
|
472
495
|
if isinstance(exception, BaseException):
|
|
473
496
|
exc_info = exception
|
|
474
497
|
else:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Adapt :class:`cadence.workflow.RetryPolicy` (TypedDict) to its protobuf wire form."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
from math import ceil
|
|
7
|
+
from typing import Mapping, cast
|
|
8
|
+
|
|
9
|
+
from google.protobuf.duration_pb2 import Duration
|
|
10
|
+
|
|
11
|
+
from cadence.api.v1 import common_pb2
|
|
12
|
+
from cadence.workflow import RetryPolicy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _round_to_whole_seconds(delta: timedelta) -> timedelta:
|
|
16
|
+
"""Ceil-round a ``timedelta`` to whole seconds."""
|
|
17
|
+
return timedelta(seconds=ceil(delta.total_seconds()))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _set_duration_field(target: Duration, delta: timedelta) -> None:
|
|
21
|
+
"""Write ``delta``, ceil-rounded to whole seconds, into a proto ``Duration`` field."""
|
|
22
|
+
d = Duration()
|
|
23
|
+
d.FromTimedelta(_round_to_whole_seconds(delta))
|
|
24
|
+
target.CopyFrom(d)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def retry_policy_to_proto(
|
|
28
|
+
policy: RetryPolicy | Mapping[str, object] | None,
|
|
29
|
+
) -> common_pb2.RetryPolicy | None:
|
|
30
|
+
"""Convert a user retry policy to protobuf, or ``None`` if no policy was provided.
|
|
31
|
+
|
|
32
|
+
``None`` and an empty mapping both map to ``None`` so that the server applies its
|
|
33
|
+
own defaults instead of receiving an explicit empty policy. Durations are ceiled
|
|
34
|
+
to whole seconds to match the server's resolution and the Go/Java SDKs.
|
|
35
|
+
"""
|
|
36
|
+
if policy is None or (isinstance(policy, Mapping) and len(policy) == 0):
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
out = common_pb2.RetryPolicy()
|
|
40
|
+
|
|
41
|
+
if (ii := policy.get("initial_interval")) is not None:
|
|
42
|
+
_set_duration_field(out.initial_interval, cast(timedelta, ii))
|
|
43
|
+
|
|
44
|
+
if (coef := policy.get("backoff_coefficient")) is not None:
|
|
45
|
+
coef_f = cast(float, coef)
|
|
46
|
+
if coef_f < 1.0:
|
|
47
|
+
raise ValueError("backoff_coefficient must be >= 1.0 when provided")
|
|
48
|
+
out.backoff_coefficient = coef_f
|
|
49
|
+
|
|
50
|
+
if (mi := policy.get("maximum_interval")) is not None:
|
|
51
|
+
_set_duration_field(out.maximum_interval, cast(timedelta, mi))
|
|
52
|
+
|
|
53
|
+
if (ma := policy.get("maximum_attempts")) is not None:
|
|
54
|
+
out.maximum_attempts = int(cast(int, ma))
|
|
55
|
+
|
|
56
|
+
if (reasons := policy.get("non_retryable_error_reasons")) is not None:
|
|
57
|
+
out.non_retryable_error_reasons.extend(cast(list[str], reasons))
|
|
58
|
+
|
|
59
|
+
if (ei := policy.get("expiration_interval")) is not None:
|
|
60
|
+
_set_duration_field(out.expiration_interval, cast(timedelta, ei))
|
|
61
|
+
|
|
62
|
+
return out
|
|
@@ -21,7 +21,7 @@ class Action:
|
|
|
21
21
|
class EventDispatcher:
|
|
22
22
|
handlers: dict[Type, Action]
|
|
23
23
|
|
|
24
|
-
def __init__(self, default_id_attr: str) -> None:
|
|
24
|
+
def __init__(self, default_id_attr: str = "") -> None:
|
|
25
25
|
self._default_id_attr = default_id_attr
|
|
26
26
|
self.handlers = {}
|
|
27
27
|
|
|
@@ -32,7 +32,8 @@ class EventDispatcher:
|
|
|
32
32
|
event_type = _find_event_type(func)
|
|
33
33
|
event_id_attr = id_attr if id_attr else self._default_id_attr
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
if event_id_attr:
|
|
36
|
+
_validate_field(func, event_type, event_id_attr)
|
|
36
37
|
if event_type in self.handlers:
|
|
37
38
|
raise ValueError(
|
|
38
39
|
f"Duplicate handler for {event_type}: {func.__qualname__} and {self.handlers[event_type].fn.__qualname__}"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from asyncio import AbstractEventLoop, Future
|
|
2
|
+
from typing import Callable, Any, Generator, Awaitable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Waiter(Awaitable[None]):
|
|
6
|
+
"""Awaitable that resolves when ``predicate()`` becomes truthy."""
|
|
7
|
+
|
|
8
|
+
__slots__ = ("_predicate", "_future")
|
|
9
|
+
|
|
10
|
+
def __init__(self, predicate: Callable[[], bool], loop: AbstractEventLoop) -> None:
|
|
11
|
+
self._predicate = predicate
|
|
12
|
+
self._future: Future[None] = loop.create_future()
|
|
13
|
+
|
|
14
|
+
def __await__(self) -> Generator[Any, None, None]:
|
|
15
|
+
return self._future.__await__()
|
|
16
|
+
|
|
17
|
+
def done(self) -> bool:
|
|
18
|
+
return self._future.done()
|
|
19
|
+
|
|
20
|
+
def exception(self) -> BaseException | None:
|
|
21
|
+
return self._future.exception()
|
|
22
|
+
|
|
23
|
+
def result(self) -> None:
|
|
24
|
+
self._future.result()
|
|
25
|
+
|
|
26
|
+
def poll(self) -> bool:
|
|
27
|
+
"""Re-evaluate the predicate. Returns True when settled (and evictable)."""
|
|
28
|
+
if self._future.done():
|
|
29
|
+
return True
|
|
30
|
+
try:
|
|
31
|
+
if self._predicate():
|
|
32
|
+
self._future.set_result(None)
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
except BaseException as exc:
|
|
36
|
+
self._future.set_exception(exc)
|
|
37
|
+
return True
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import traceback
|
|
3
3
|
from asyncio import CancelledError, InvalidStateError
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from functools import singledispatchmethod
|
|
5
6
|
from typing import List, Optional
|
|
6
7
|
|
|
7
8
|
from cadence._internal.workflow.context import Context
|
|
@@ -12,16 +13,16 @@ from cadence._internal.workflow.deterministic_event_loop import (
|
|
|
12
13
|
)
|
|
13
14
|
from cadence._internal.workflow.statemachine.decision_manager import DecisionManager
|
|
14
15
|
from cadence._internal.workflow.workflow_instance import WorkflowInstance
|
|
15
|
-
from cadence.api.v1 import history
|
|
16
|
-
from cadence.api.v1.common_pb2 import Failure, WorkflowType
|
|
17
16
|
from cadence.api.v1.decision_pb2 import (
|
|
18
17
|
Decision,
|
|
19
18
|
FailWorkflowExecutionDecisionAttributes,
|
|
20
19
|
CompleteWorkflowExecutionDecisionAttributes,
|
|
21
20
|
ContinueAsNewWorkflowExecutionDecisionAttributes,
|
|
22
21
|
)
|
|
22
|
+
from cadence.api.v1.common_pb2 import Failure, WorkflowType
|
|
23
23
|
from cadence.api.v1.history_pb2 import (
|
|
24
24
|
HistoryEvent,
|
|
25
|
+
WorkflowExecutionSignaledEventAttributes,
|
|
25
26
|
WorkflowExecutionStartedEventAttributes,
|
|
26
27
|
)
|
|
27
28
|
from cadence.api.v1.tasklist_pb2 import TaskList
|
|
@@ -162,15 +163,19 @@ class WorkflowEngine:
|
|
|
162
163
|
# Process through state machines (DecisionsHelper now delegates to DecisionManager)
|
|
163
164
|
self._decision_manager.handle_history_event(marker_event)
|
|
164
165
|
|
|
165
|
-
# Phase 2:
|
|
166
|
+
# Phase 2: Apply input events in history order.
|
|
166
167
|
for event in decision_events.input:
|
|
167
168
|
self._apply_input_event(event)
|
|
168
169
|
|
|
169
170
|
# Phase 3: Execute workflow logic
|
|
170
171
|
self._workflow_instance.run_until_yield()
|
|
171
172
|
|
|
172
|
-
#
|
|
173
|
-
|
|
173
|
+
# Signal handler failures fail the decision task, not the workflow.
|
|
174
|
+
if (
|
|
175
|
+
signal_failure := self._workflow_instance.get_signal_failure()
|
|
176
|
+
) is not None:
|
|
177
|
+
raise signal_failure
|
|
178
|
+
|
|
174
179
|
if decision := self._maybe_complete_workflow():
|
|
175
180
|
self._decision_manager.complete_workflow(decision)
|
|
176
181
|
|
|
@@ -228,29 +233,29 @@ class WorkflowEngine:
|
|
|
228
233
|
)
|
|
229
234
|
)
|
|
230
235
|
|
|
231
|
-
def _apply_input_event(self, event:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"event_id": getattr(event, "event_id", None),
|
|
238
|
-
"replay_mode": self._context.is_replay_mode(),
|
|
239
|
-
},
|
|
240
|
-
)
|
|
241
|
-
# start workflow on workflow started event
|
|
242
|
-
if (
|
|
243
|
-
event.WhichOneof("attributes")
|
|
244
|
-
== "workflow_execution_started_event_attributes"
|
|
245
|
-
):
|
|
246
|
-
started_attrs: WorkflowExecutionStartedEventAttributes = (
|
|
247
|
-
event.workflow_execution_started_event_attributes
|
|
248
|
-
)
|
|
249
|
-
if started_attrs and hasattr(started_attrs, "input"):
|
|
250
|
-
self._workflow_instance.start(started_attrs.input)
|
|
236
|
+
def _apply_input_event(self, event: HistoryEvent) -> None:
|
|
237
|
+
attr = event.WhichOneof("attributes")
|
|
238
|
+
if attr is None:
|
|
239
|
+
self._decision_manager.handle_history_event(event)
|
|
240
|
+
return
|
|
241
|
+
self._handle_input_event(getattr(event, attr), event)
|
|
251
242
|
|
|
243
|
+
@singledispatchmethod
|
|
244
|
+
def _handle_input_event(self, attrs: object, event: HistoryEvent) -> None:
|
|
252
245
|
self._decision_manager.handle_history_event(event)
|
|
253
246
|
|
|
247
|
+
@_handle_input_event.register
|
|
248
|
+
def _handle_started_input_event(
|
|
249
|
+
self, attrs: WorkflowExecutionStartedEventAttributes, event: HistoryEvent
|
|
250
|
+
) -> None:
|
|
251
|
+
self._workflow_instance.start(attrs.input)
|
|
252
|
+
|
|
253
|
+
@_handle_input_event.register
|
|
254
|
+
def _handle_signaled_input_event(
|
|
255
|
+
self, attrs: WorkflowExecutionSignaledEventAttributes, event: HistoryEvent
|
|
256
|
+
) -> None:
|
|
257
|
+
self._workflow_instance.handle_signal(attrs.signal_name, attrs.input)
|
|
258
|
+
|
|
254
259
|
|
|
255
260
|
def _failure_from_exception(e: Exception) -> Failure:
|
|
256
261
|
stacktrace = "".join(traceback.format_exception(e))
|