|
1 | | -from typing import Dict, Optional, Union |
| 1 | +from typing import Dict, Optional, Union, cast |
2 | 2 | import asyncio |
3 | 3 | import time |
4 | 4 |
|
5 | 5 | from ..client import AsyncSeamHttpClient, SeamHttpClient |
6 | | -from ..exceptions import SeamActionAttemptFailedError, SeamActionAttemptTimeoutError |
| 6 | +from ..exceptions import ( |
| 7 | + SeamActionAttemptFailedError, |
| 8 | + SeamActionAttemptTimeoutError, |
| 9 | + SeamActionAttemptUnknownStatusError, |
| 10 | +) |
7 | 11 | from ..options import SeamInvalidOptionsError |
8 | | -from ..resources import ActionAttempt, SuccessActionAttempt, action_attempt_from_dict |
| 12 | +from ..resources import ( |
| 13 | + ActionAttempt, |
| 14 | + ErrorActionAttempt, |
| 15 | + PendingActionAttempt, |
| 16 | + SuccessActionAttempt, |
| 17 | + action_attempt_from_dict, |
| 18 | +) |
9 | 19 | from ..response import unwrap |
10 | 20 |
|
11 | 21 | TIMEOUT = 5.0 |
|
14 | 24 | WAIT_FOR_ACTION_ATTEMPT_OPTION_KEYS = ("timeout", "polling_interval") |
15 | 25 |
|
16 | 26 |
|
| 27 | +def _status_of(action_attempt: ActionAttempt) -> Optional[str]: |
| 28 | + """Read the status without assuming the attempt carries one. |
| 29 | +
|
| 30 | + An attempt from a newer API deserializes to a DeepAttrDict, where a missing |
| 31 | + key raises rather than reading as None. |
| 32 | + """ |
| 33 | + |
| 34 | + return getattr(action_attempt, "status", None) |
| 35 | + |
| 36 | + |
17 | 37 | def validate_wait_for_action_attempt( |
18 | 38 | value: Optional[Union[bool, Dict[str, float]]], |
19 | 39 | ) -> None: |
@@ -93,20 +113,28 @@ def poll_until_ready( |
93 | 113 | if action_attempt is None: |
94 | 114 | action_attempt = get_action_attempt(client, action_attempt_id) |
95 | 115 |
|
96 | | - while action_attempt.status == "pending": |
| 116 | + while _status_of(action_attempt) == "pending": |
97 | 117 | remaining = deadline - time.monotonic() |
98 | 118 |
|
99 | 119 | if remaining <= 0: |
100 | | - raise SeamActionAttemptTimeoutError(action_attempt, timeout) |
| 120 | + raise SeamActionAttemptTimeoutError( |
| 121 | + cast(PendingActionAttempt, action_attempt), timeout |
| 122 | + ) |
101 | 123 |
|
102 | 124 | time.sleep(min(polling_interval, remaining)) |
103 | 125 |
|
104 | 126 | action_attempt = get_action_attempt(client, action_attempt_id) |
105 | 127 |
|
106 | | - if action_attempt.status == "error": |
107 | | - raise SeamActionAttemptFailedError(action_attempt) |
| 128 | + if _status_of(action_attempt) == "error": |
| 129 | + raise SeamActionAttemptFailedError(cast(ErrorActionAttempt, action_attempt)) |
108 | 130 |
|
109 | | - return action_attempt |
| 131 | + # Neither pending, success, nor error: a status added after this SDK release. |
| 132 | + # Waiting cannot conclude, so say so rather than pass it off as a success. |
| 133 | + status = _status_of(action_attempt) |
| 134 | + if status != "success": |
| 135 | + raise SeamActionAttemptUnknownStatusError(action_attempt, str(status)) |
| 136 | + |
| 137 | + return cast(SuccessActionAttempt, action_attempt) |
110 | 138 |
|
111 | 139 |
|
112 | 140 | def resolve_action_attempt( |
@@ -165,20 +193,28 @@ async def poll_until_ready_async( |
165 | 193 | if action_attempt is None: |
166 | 194 | action_attempt = await get_action_attempt_async(client, action_attempt_id) |
167 | 195 |
|
168 | | - while action_attempt.status == "pending": |
| 196 | + while _status_of(action_attempt) == "pending": |
169 | 197 | remaining = deadline - time.monotonic() |
170 | 198 |
|
171 | 199 | if remaining <= 0: |
172 | | - raise SeamActionAttemptTimeoutError(action_attempt, timeout) |
| 200 | + raise SeamActionAttemptTimeoutError( |
| 201 | + cast(PendingActionAttempt, action_attempt), timeout |
| 202 | + ) |
173 | 203 |
|
174 | 204 | await asyncio.sleep(min(polling_interval, remaining)) |
175 | 205 |
|
176 | 206 | action_attempt = await get_action_attempt_async(client, action_attempt_id) |
177 | 207 |
|
178 | | - if action_attempt.status == "error": |
179 | | - raise SeamActionAttemptFailedError(action_attempt) |
| 208 | + if _status_of(action_attempt) == "error": |
| 209 | + raise SeamActionAttemptFailedError(cast(ErrorActionAttempt, action_attempt)) |
180 | 210 |
|
181 | | - return action_attempt |
| 211 | + # Neither pending, success, nor error: a status added after this SDK release. |
| 212 | + # Waiting cannot conclude, so say so rather than pass it off as a success. |
| 213 | + status = _status_of(action_attempt) |
| 214 | + if status != "success": |
| 215 | + raise SeamActionAttemptUnknownStatusError(action_attempt, str(status)) |
| 216 | + |
| 217 | + return cast(SuccessActionAttempt, action_attempt) |
182 | 218 |
|
183 | 219 |
|
184 | 220 | async def resolve_action_attempt_async( |
|
0 commit comments