|
25 | 25 | import time |
26 | 26 | import traceback |
27 | 27 | import uuid |
28 | | -from collections.abc import Awaitable, Callable, Iterable |
| 28 | +from collections.abc import Awaitable, Callable, Iterable, Mapping |
29 | 29 | from types import FunctionType |
30 | 30 | from typing import Any |
31 | 31 |
|
@@ -111,6 +111,136 @@ def _is_final_query_task_rejection(error: BaseException) -> bool: |
111 | 111 | ) |
112 | 112 |
|
113 | 113 |
|
| 114 | +def _signal_arguments_envelope_from_export( |
| 115 | + signal: Mapping[str, Any], |
| 116 | + *, |
| 117 | + default_codec: str | None, |
| 118 | +) -> dict[str, Any] | None: |
| 119 | + raw_arguments = signal.get("arguments") |
| 120 | + if raw_arguments is None: |
| 121 | + return None |
| 122 | + |
| 123 | + codec = signal.get("payload_codec") |
| 124 | + if not isinstance(codec, str) or codec == "": |
| 125 | + codec = default_codec or serializer.AVRO_CODEC |
| 126 | + |
| 127 | + if isinstance(raw_arguments, str): |
| 128 | + if raw_arguments == "": |
| 129 | + return None |
| 130 | + return {"codec": codec, "blob": raw_arguments} |
| 131 | + |
| 132 | + if not isinstance(raw_arguments, Mapping): |
| 133 | + return None |
| 134 | + |
| 135 | + envelope = dict(raw_arguments) |
| 136 | + if "blob" not in envelope and "external_storage" not in envelope: |
| 137 | + return None |
| 138 | + envelope.setdefault("codec", codec) |
| 139 | + return envelope |
| 140 | + |
| 141 | + |
| 142 | +def _query_history_with_export_signal_arguments( |
| 143 | + history: Any, |
| 144 | + history_export: Any, |
| 145 | + *, |
| 146 | + default_codec: str | None, |
| 147 | +) -> Any: |
| 148 | + # Query-task history can carry compact SignalReceived rows; the full |
| 149 | + # signal payload bytes are still present in the accompanying export. |
| 150 | + if not isinstance(history, list) or not isinstance(history_export, Mapping): |
| 151 | + return history |
| 152 | + |
| 153 | + raw_signals = history_export.get("signals") |
| 154 | + if not isinstance(raw_signals, list): |
| 155 | + return history |
| 156 | + |
| 157 | + export_payloads = history_export.get("payloads") |
| 158 | + export_codec = ( |
| 159 | + export_payloads.get("codec") |
| 160 | + if isinstance(export_payloads, Mapping) |
| 161 | + else None |
| 162 | + ) |
| 163 | + signal_default_codec = default_codec |
| 164 | + if signal_default_codec is None and isinstance(export_codec, str) and export_codec: |
| 165 | + signal_default_codec = export_codec |
| 166 | + |
| 167 | + signals_by_id: dict[str, Mapping[str, Any]] = {} |
| 168 | + signals_by_command_id: dict[str, Mapping[str, Any]] = {} |
| 169 | + signals_by_name: dict[str, list[Mapping[str, Any]]] = {} |
| 170 | + for raw_signal in raw_signals: |
| 171 | + if not isinstance(raw_signal, Mapping): |
| 172 | + continue |
| 173 | + envelope = _signal_arguments_envelope_from_export(raw_signal, default_codec=signal_default_codec) |
| 174 | + if envelope is None: |
| 175 | + continue |
| 176 | + signal_id = raw_signal.get("id") |
| 177 | + if isinstance(signal_id, str) and signal_id: |
| 178 | + signals_by_id[signal_id] = raw_signal |
| 179 | + command_id = raw_signal.get("command_id") |
| 180 | + if isinstance(command_id, str) and command_id: |
| 181 | + signals_by_command_id[command_id] = raw_signal |
| 182 | + name = raw_signal.get("name") |
| 183 | + if isinstance(name, str) and name: |
| 184 | + signals_by_name.setdefault(name, []).append(raw_signal) |
| 185 | + |
| 186 | + if not signals_by_id and not signals_by_command_id and not signals_by_name: |
| 187 | + return history |
| 188 | + |
| 189 | + name_offsets: dict[str, int] = {} |
| 190 | + enriched: list[Any] = [] |
| 191 | + changed = False |
| 192 | + for raw_event in history: |
| 193 | + if not isinstance(raw_event, Mapping): |
| 194 | + enriched.append(raw_event) |
| 195 | + continue |
| 196 | + event_type = raw_event.get("event_type") or raw_event.get("type") |
| 197 | + if event_type != "SignalReceived": |
| 198 | + enriched.append(raw_event) |
| 199 | + continue |
| 200 | + raw_payload = raw_event.get("payload") |
| 201 | + if not isinstance(raw_payload, Mapping): |
| 202 | + enriched.append(dict(raw_event)) |
| 203 | + continue |
| 204 | + if any(raw_payload.get(key) is not None for key in ("value", "input", "arguments")): |
| 205 | + enriched.append(dict(raw_event)) |
| 206 | + continue |
| 207 | + |
| 208 | + signal: Mapping[str, Any] | None = None |
| 209 | + signal_id = raw_payload.get("signal_id") |
| 210 | + if isinstance(signal_id, str) and signal_id: |
| 211 | + signal = signals_by_id.get(signal_id) |
| 212 | + if signal is None: |
| 213 | + command_id = raw_payload.get("workflow_command_id") or raw_event.get("workflow_command_id") |
| 214 | + if isinstance(command_id, str) and command_id: |
| 215 | + signal = signals_by_command_id.get(command_id) |
| 216 | + if signal is None: |
| 217 | + signal_name = raw_payload.get("signal_name") |
| 218 | + if isinstance(signal_name, str) and signal_name: |
| 219 | + candidates = signals_by_name.get(signal_name, []) |
| 220 | + offset = name_offsets.get(signal_name, 0) |
| 221 | + if offset < len(candidates): |
| 222 | + signal = candidates[offset] |
| 223 | + name_offsets[signal_name] = offset + 1 |
| 224 | + if signal is None: |
| 225 | + enriched.append(dict(raw_event)) |
| 226 | + continue |
| 227 | + |
| 228 | + envelope = _signal_arguments_envelope_from_export(signal, default_codec=signal_default_codec) |
| 229 | + if envelope is None: |
| 230 | + enriched.append(dict(raw_event)) |
| 231 | + continue |
| 232 | + |
| 233 | + payload = dict(raw_payload) |
| 234 | + payload["arguments"] = envelope |
| 235 | + payload.setdefault("payload_codec", envelope.get("codec")) |
| 236 | + event = dict(raw_event) |
| 237 | + event["payload"] = payload |
| 238 | + enriched.append(event) |
| 239 | + changed = True |
| 240 | + |
| 241 | + return enriched if changed else history |
| 242 | + |
| 243 | + |
114 | 244 | def _callable_fingerprint_payload(value: object) -> str: |
115 | 245 | if isinstance(value, staticmethod | classmethod): |
116 | 246 | value = value.__func__ |
@@ -948,7 +1078,11 @@ async def _run_query_task_core(self, task: dict[str, Any], *, client: Client | N |
948 | 1078 | return "failed" |
949 | 1079 |
|
950 | 1080 | result_codec = _command_payload_codec(codec) |
951 | | - history = task.get("history_events", []) |
| 1081 | + history = _query_history_with_export_signal_arguments( |
| 1082 | + task.get("history_events", []), |
| 1083 | + task.get("history_export"), |
| 1084 | + default_codec=codec, |
| 1085 | + ) |
952 | 1086 |
|
953 | 1087 | cls = self.workflows.get(wf_type) |
954 | 1088 | if cls is None: |
|
0 commit comments