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.
Files changed (191) hide show
  1. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.cursorrules +5 -5
  2. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/Makefile +1 -1
  3. {cadence_python_client-0.2.1/cadence_python_client.egg-info → cadence_python_client-0.2.2}/PKG-INFO +2 -1
  4. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/fn_signature.py +31 -1
  5. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/retry.py +8 -3
  6. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/yarpc.py +6 -3
  7. cadence_python_client-0.2.2/cadence/_internal/workflow/active_cluster_selection_policy.py +32 -0
  8. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/context.py +28 -16
  9. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/deterministic_event_loop.py +25 -2
  10. cadence_python_client-0.2.2/cadence/_internal/workflow/retry_policy.py +62 -0
  11. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/event_dispatcher.py +3 -2
  12. cadence_python_client-0.2.2/cadence/_internal/workflow/waiter.py +37 -0
  13. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/workflow_engine.py +30 -25
  14. cadence_python_client-0.2.2/cadence/_internal/workflow/workflow_instance.py +105 -0
  15. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/__init__.py +12 -0
  16. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/common_pb2.py +30 -12
  17. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/common_pb2.pyi +29 -2
  18. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/domain_pb2.py +22 -10
  19. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/domain_pb2.pyi +24 -2
  20. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/error_pb2.py +3 -1
  21. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/error_pb2.pyi +4 -0
  22. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/history_pb2.py +44 -44
  23. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/history_pb2.pyi +6 -2
  24. cadence_python_client-0.2.2/cadence/api/v1/schedule_pb2.py +61 -0
  25. cadence_python_client-0.2.2/cadence/api/v1/schedule_pb2.pyi +154 -0
  26. cadence_python_client-0.2.2/cadence/api/v1/schedule_pb2_grpc.py +24 -0
  27. cadence_python_client-0.2.2/cadence/api/v1/service_domain_pb2.py +94 -0
  28. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_domain_pb2.pyi +68 -2
  29. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_domain_pb2_grpc.py +88 -0
  30. cadence_python_client-0.2.2/cadence/api/v1/service_schedule_pb2.py +72 -0
  31. cadence_python_client-0.2.2/cadence/api/v1/service_schedule_pb2.pyi +163 -0
  32. cadence_python_client-0.2.2/cadence/api/v1/service_schedule_pb2_grpc.py +409 -0
  33. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_workflow_pb2.py +9 -9
  34. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_workflow_pb2.pyi +4 -2
  35. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_workflow_pb2_grpc.py +2 -2
  36. cadence_python_client-0.2.2/cadence/api/v1/workflow_pb2.py +91 -0
  37. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/workflow_pb2.pyi +29 -2
  38. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/client.py +55 -7
  39. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/pydantic_data_converter.py +33 -9
  40. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/data_converter.py +18 -6
  41. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/error.py +26 -8
  42. cadence_python_client-0.2.2/cadence/signal.py +102 -0
  43. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/workflow.py +62 -5
  44. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2/cadence_python_client.egg-info}/PKG-INFO +2 -1
  45. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/SOURCES.txt +15 -0
  46. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/requires.txt +1 -0
  47. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/pyproject.toml +2 -1
  48. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/rpc/test_error.py +2 -1
  49. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/rpc/test_retry.py +1 -1
  50. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/test_fn_signature.py +41 -1
  51. cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_context_retry_policy.py +53 -0
  52. cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_deterministic_event_loop.py +190 -0
  53. cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_retry_policy.py +87 -0
  54. cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_signal_handling.py +1387 -0
  55. cadence_python_client-0.2.2/tests/cadence/_internal/workflow/test_waiter.py +121 -0
  56. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/data_converter_test.py +8 -1
  57. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/test_client_workflow.py +282 -1
  58. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_registry.py +6 -9
  59. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/test_client.py +87 -1
  60. cadence_python_client-0.2.2/tests/integration_tests/workflow/test_retry_policy.py +335 -0
  61. cadence_python_client-0.2.2/tests/integration_tests/workflow/test_signals.py +349 -0
  62. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/uv.lock +11 -0
  63. cadence_python_client-0.2.1/cadence/_internal/workflow/workflow_instance.py +0 -53
  64. cadence_python_client-0.2.1/cadence/api/v1/service_domain_pb2.py +0 -76
  65. cadence_python_client-0.2.1/cadence/api/v1/workflow_pb2.py +0 -89
  66. cadence_python_client-0.2.1/cadence/signal.py +0 -174
  67. cadence_python_client-0.2.1/tests/cadence/_internal/workflow/test_deterministic_event_loop.py +0 -96
  68. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/dco.yml +0 -0
  69. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/pull_request_template.md +0 -0
  70. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/workflows/ci_checks.yml +0 -0
  71. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/workflows/python-publish.yml +0 -0
  72. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.github/workflows/semantic-pr.yml +0 -0
  73. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.gitignore +0 -0
  74. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/.gitmodules +0 -0
  75. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/CONTRIBUTING.md +0 -0
  76. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/LICENSE +0 -0
  77. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/NOTICE +0 -0
  78. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/README.md +0 -0
  79. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/__init__.py +0 -0
  80. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/__init__.py +0 -0
  81. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/__init__.py +0 -0
  82. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_activity_executor.py +0 -0
  83. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_context.py +0 -0
  84. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_definition.py +0 -0
  85. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/activity/_heartbeat.py +0 -0
  86. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/__init__.py +0 -0
  87. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/rpc/error.py +0 -0
  88. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/__init__.py +0 -0
  89. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/decision_events_iterator.py +0 -0
  90. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/history_event_iterator.py +0 -0
  91. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/__init__.py +0 -0
  92. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/activity_state_machine.py +0 -0
  93. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/cancellation.py +0 -0
  94. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/completion_state_machine.py +0 -0
  95. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/decision_manager.py +0 -0
  96. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/decision_state_machine.py +0 -0
  97. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/nondeterminism.py +0 -0
  98. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/_internal/workflow/statemachine/timer_state_machine.py +0 -0
  99. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/activity.py +0 -0
  100. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/common_pb2_grpc.py +0 -0
  101. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/decision_pb2.py +0 -0
  102. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/decision_pb2.pyi +0 -0
  103. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/decision_pb2_grpc.py +0 -0
  104. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/domain_pb2_grpc.py +0 -0
  105. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/error_pb2_grpc.py +0 -0
  106. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/history_pb2_grpc.py +0 -0
  107. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/query_pb2.py +0 -0
  108. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/query_pb2.pyi +0 -0
  109. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/query_pb2_grpc.py +0 -0
  110. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_meta_pb2.py +0 -0
  111. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_meta_pb2.pyi +0 -0
  112. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_meta_pb2_grpc.py +0 -0
  113. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_visibility_pb2.py +0 -0
  114. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_visibility_pb2.pyi +0 -0
  115. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_visibility_pb2_grpc.py +0 -0
  116. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_worker_pb2.py +0 -0
  117. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_worker_pb2.pyi +0 -0
  118. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/service_worker_pb2_grpc.py +0 -0
  119. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/tasklist_pb2.py +0 -0
  120. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/tasklist_pb2.pyi +0 -0
  121. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/tasklist_pb2_grpc.py +0 -0
  122. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/visibility_pb2.py +0 -0
  123. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/visibility_pb2.pyi +0 -0
  124. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/visibility_pb2_grpc.py +0 -0
  125. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/api/v1/workflow_pb2_grpc.py +0 -0
  126. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/__init__.py +0 -0
  127. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/README.md +0 -0
  128. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/__init__.py +0 -0
  129. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_agent_runner.py +0 -0
  130. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_handoff.py +0 -0
  131. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_model.py +0 -0
  132. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_registry.py +0 -0
  133. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/cadence_tool.py +0 -0
  134. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/images/cadence-web-agent-run.jpg +0 -0
  135. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/contrib/openai/openai_activities.py +0 -0
  136. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/__init__.py +0 -0
  137. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/constants.py +0 -0
  138. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/metrics.py +0 -0
  139. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/metrics/prometheus.py +0 -0
  140. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/__init__.py +0 -0
  141. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/client_example.py +0 -0
  142. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/grpc_usage_example.py +0 -0
  143. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/sample/simple_usage_example.py +0 -0
  144. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/__init__.py +0 -0
  145. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_activity.py +0 -0
  146. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_base_task_handler.py +0 -0
  147. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_decision.py +0 -0
  148. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_decision_task_handler.py +0 -0
  149. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_poller.py +0 -0
  150. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_registry.py +0 -0
  151. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_types.py +0 -0
  152. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence/worker/_worker.py +0 -0
  153. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/dependency_links.txt +0 -0
  154. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/cadence_python_client.egg-info/top_level.txt +0 -0
  155. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/scripts/dev.py +0 -0
  156. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/scripts/fix_pyi_imports.py +0 -0
  157. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/scripts/generate_proto.py +0 -0
  158. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/setup.cfg +0 -0
  159. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/activity/test_activity_executor.py +0 -0
  160. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/activity/test_heartbeat.py +0 -0
  161. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_activity_state_machine.py +0 -0
  162. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_decision_manager.py +0 -0
  163. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_nondeterminism.py +0 -0
  164. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/statemachine/test_timer_state_machine.py +0 -0
  165. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/test_decision_events_iterator.py +0 -0
  166. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/test_history_event_iterator.py +0 -0
  167. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/_internal/workflow/test_workflow_engine.py +0 -0
  168. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/common_activities.py +0 -0
  169. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/metrics/test_metrics.py +0 -0
  170. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/metrics/test_prometheus.py +0 -0
  171. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/test_activity.py +0 -0
  172. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/test_client.py +0 -0
  173. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_base_task_handler.py +0 -0
  174. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_decision_task_handler.py +0 -0
  175. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_decision_task_handler_integration.py +0 -0
  176. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_decision_worker_integration.py +0 -0
  177. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_poller.py +0 -0
  178. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_task_handler_integration.py +0 -0
  179. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/cadence/worker/test_worker.py +0 -0
  180. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/conftest.py +0 -0
  181. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/conftest.py +0 -0
  182. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/docker-compose.yml +0 -0
  183. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/helper.py +0 -0
  184. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/nondeterminism/success.json +0 -0
  185. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/nondeterminism/test_nondeterministic_workflows.py +0 -0
  186. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_activities.py +0 -0
  187. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_continue_as_new.py +0 -0
  188. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_heartbeat.py +0 -0
  189. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_serialization.py +0 -0
  190. {cadence_python_client-0.2.1 → cadence_python_client-0.2.2}/tests/integration_tests/workflow/test_timer.py +0 -0
  191. {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, mypy, ruff, etc.)
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 tool run mypy cadence/
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 tool run mypy cadence/`
41
- - Use `uv tool run ruff check --fix && uv tool run mypy cadence/` to run both together
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 tool run mypy cadence/` (checks type safety)
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
@@ -7,7 +7,7 @@ pr: install generate lint type-check test integration-test
7
7
  # Install dependencies
8
8
  install:
9
9
  @echo "Installing dependencies..."
10
- uv sync --extra dev
10
+ uv sync --all-extras
11
11
 
12
12
  # Generate idl files
13
13
  generate:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cadence-python-client
3
- Version: 0.2.1
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"
@@ -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
- return data_converter.from_data(payload, type_hints)
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(timeout=remaining)
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 call_details.method == GET_WORKFLOW_HISTORY and isinstance(
99
- err, EntityNotExistsError
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 client_call_details._replace(
44
- metadata=metadata, timeout=client_call_details.timeout or 60.0
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
@@ -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 WorkflowContext, WorkflowInfo, ResultType, ActivityOptions
15
-
16
- default_activity_options = ActivityOptions(
17
- schedule_to_close_timeout=timedelta(
18
- hours=1
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 = {**default_activity_options, **kwargs}
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=None,
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
- result_payload = await self._decision_manager.schedule_activity(
96
- schedule_attributes
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
- await self._decision_manager.start_timer(
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
- yield self
132
- WorkflowContext._var.reset(token)
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
- _validate_field(func, event_type, event_id_attr)
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: Process regular input events
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
- # If the workflow function returned (or threw an exception), we're done
173
- # If it completed early (or late), the nondeterminism tracking will catch that
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: history.HistoryEvent) -> None:
232
- logger.debug(
233
- "Processing history event",
234
- extra={
235
- "workflow_id": self._context.info().workflow_id,
236
- "event_type": getattr(event, "event_type", "unknown"),
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))