Skip to content

Commit f0ae5cb

Browse files
committed
fix: Validate the wait_for_action_attempt option
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY
1 parent cb6a798 commit f0ae5cb

4 files changed

Lines changed: 264 additions & 20 deletions

File tree

‎seam/modules/action_attempts.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,49 @@
1010
TIMEOUT = 5.0
1111
POLLING_INTERVAL = 0.5
1212

13+
WAIT_FOR_ACTION_ATTEMPT_OPTION_KEYS = ("timeout", "polling_interval")
14+
15+
16+
def validate_wait_for_action_attempt(
17+
value: Optional[Union[bool, Dict[str, float]]],
18+
) -> None:
19+
if isinstance(value, bool):
20+
return
21+
22+
if isinstance(value, dict):
23+
for key, option in value.items():
24+
if key not in WAIT_FOR_ACTION_ATTEMPT_OPTION_KEYS:
25+
raise SeamInvalidOptionsError(
26+
f"The wait_for_action_attempt option got an unknown key {key!r}, "
27+
'expected "timeout" or "polling_interval"'
28+
)
29+
30+
if isinstance(option, bool) or not isinstance(option, (int, float)):
31+
raise SeamInvalidOptionsError(
32+
f"The wait_for_action_attempt option {key!r} must be a number, "
33+
f"got {type(option).__name__}"
34+
)
35+
36+
return
37+
38+
raise SeamInvalidOptionsError(
39+
"The wait_for_action_attempt option must be a bool or a dict with "
40+
f'"timeout" and "polling_interval" keys, got {type(value).__name__}'
41+
)
42+
43+
44+
def normalize_wait_for_action_attempt(
45+
value: Optional[Union[bool, Dict[str, float]]],
46+
) -> Union[bool, Dict[str, float]]:
47+
"""Resolve None to the default and reject anything but a bool or options dict."""
48+
49+
if value is None:
50+
return True
51+
52+
validate_wait_for_action_attempt(value)
53+
54+
return value
55+
1356

1457
def validate_poll_options(timeout: float, polling_interval: float) -> None:
1558
# Written as negated comparisons so NaN fails both checks.
@@ -69,6 +112,8 @@ def resolve_action_attempt(
69112
action_attempt: ActionAttempt,
70113
wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]],
71114
) -> ActionAttempt:
115+
validate_wait_for_action_attempt(wait_for_action_attempt)
116+
72117
if wait_for_action_attempt is True:
73118
return poll_until_ready(
74119
client=client,
@@ -137,6 +182,8 @@ async def resolve_action_attempt_async(
137182
action_attempt: ActionAttempt,
138183
wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]],
139184
) -> ActionAttempt:
185+
validate_wait_for_action_attempt(wait_for_action_attempt)
186+
140187
if wait_for_action_attempt is True:
141188
return await poll_until_ready_async(
142189
client=client,

‎seam/seam.py‎

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .routes import AsyncRoutes, Routes
88
from .models import AbstractAsyncSeam, AbstractSeam
99
from .client import AsyncSeamHttpClient, SeamHttpClient
10+
from .modules.action_attempts import normalize_wait_for_action_attempt
1011
from .paginator import AsyncSeamPaginator, SeamPaginator
1112

1213

@@ -63,7 +64,7 @@ def __init__(
6364
:type endpoint: Optional[str]
6465
:param wait_for_action_attempt: Controls whether to wait for an
6566
action attempt to complete. Can be a boolean or a dictionary with
66-
'timeout' and 'poll_interval' keys
67+
'timeout' and 'polling_interval' keys
6768
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
6869
:param retries: Configuration for retry behavior on failed requests
6970
:type retries: Optional[httpx_retries.Retry]
@@ -81,14 +82,17 @@ def __init__(
8182
access token format is invalid
8283
"""
8384

84-
self.wait_for_action_attempt = wait_for_action_attempt
8585
auth_headers, endpoint = parse_options(
8686
api_key=api_key,
8787
personal_access_token=personal_access_token,
8888
workspace_id=workspace_id,
8989
endpoint=endpoint,
9090
)
91-
self.defaults = {"wait_for_action_attempt": wait_for_action_attempt}
91+
self.defaults = {
92+
"wait_for_action_attempt": normalize_wait_for_action_attempt(
93+
wait_for_action_attempt
94+
)
95+
}
9296

9397
self.client = SeamHttpClient(
9498
base_url=endpoint,
@@ -103,6 +107,19 @@ def __init__(
103107
# namespaces passes a self the signature does not admit.
104108
Routes.__init__(self, client=self.client, defaults=self.defaults) # type: ignore[arg-type]
105109

110+
@property
111+
def wait_for_action_attempt(self) -> Union[bool, Dict[str, float]]:
112+
"""Default wait behavior for action attempts, shared with every route."""
113+
return self.defaults["wait_for_action_attempt"]
114+
115+
@wait_for_action_attempt.setter
116+
def wait_for_action_attempt(
117+
self, value: Optional[Union[bool, Dict[str, float]]]
118+
) -> None:
119+
self.defaults["wait_for_action_attempt"] = normalize_wait_for_action_attempt(
120+
value
121+
)
122+
106123
def create_paginator(
107124
self, request: Callable, params: Optional[Dict[str, Any]] = None, /
108125
) -> SeamPaginator:
@@ -173,7 +190,7 @@ def from_api_key(
173190
:type endpoint: Optional[str]
174191
:param wait_for_action_attempt: Controls whether to wait for an
175192
action attempt to complete. Can be a boolean or a dictionary with
176-
'timeout' and 'poll_interval' keys
193+
'timeout' and 'polling_interval' keys
177194
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
178195
:return: A new instance of the Seam class authenticated with the
179196
provided API key
@@ -219,7 +236,7 @@ def from_personal_access_token(
219236
:type endpoint: Optional[str]
220237
:param wait_for_action_attempt: Controls whether to wait for an
221238
action attempt to complete. Can be a boolean or a dictionary with
222-
'timeout' and 'poll_interval' keys
239+
'timeout' and 'polling_interval' keys
223240
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
224241
:return: A new instance of the Seam class authenticated with the
225242
provided personal access token
@@ -294,7 +311,7 @@ def __init__(
294311
:type endpoint: Optional[str]
295312
:param wait_for_action_attempt: Controls whether to wait for an
296313
action attempt to complete. Can be a boolean or a dictionary with
297-
'timeout' and 'poll_interval' keys
314+
'timeout' and 'polling_interval' keys
298315
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
299316
:param retries: Configuration for retry behavior on failed requests
300317
:type retries: Optional[httpx_retries.Retry]
@@ -312,14 +329,17 @@ def __init__(
312329
access token format is invalid
313330
"""
314331

315-
self.wait_for_action_attempt = wait_for_action_attempt
316332
auth_headers, endpoint = parse_options(
317333
api_key=api_key,
318334
personal_access_token=personal_access_token,
319335
workspace_id=workspace_id,
320336
endpoint=endpoint,
321337
)
322-
self.defaults = {"wait_for_action_attempt": wait_for_action_attempt}
338+
self.defaults = {
339+
"wait_for_action_attempt": normalize_wait_for_action_attempt(
340+
wait_for_action_attempt
341+
)
342+
}
323343

324344
self.client = AsyncSeamHttpClient(
325345
base_url=endpoint,
@@ -335,6 +355,19 @@ def __init__(
335355
# admit.
336356
AsyncRoutes.__init__(self, client=self.client, defaults=self.defaults) # type: ignore[arg-type]
337357

358+
@property
359+
def wait_for_action_attempt(self) -> Union[bool, Dict[str, float]]:
360+
"""Default wait behavior for action attempts, shared with every route."""
361+
return self.defaults["wait_for_action_attempt"]
362+
363+
@wait_for_action_attempt.setter
364+
def wait_for_action_attempt(
365+
self, value: Optional[Union[bool, Dict[str, float]]]
366+
) -> None:
367+
self.defaults["wait_for_action_attempt"] = normalize_wait_for_action_attempt(
368+
value
369+
)
370+
338371
def create_paginator(
339372
self, request: Callable, params: Optional[Dict[str, Any]] = None, /
340373
) -> AsyncSeamPaginator:
@@ -402,7 +435,7 @@ def from_api_key(
402435
:type endpoint: Optional[str]
403436
:param wait_for_action_attempt: Controls whether to wait for an
404437
action attempt to complete. Can be a boolean or a dictionary with
405-
'timeout' and 'poll_interval' keys
438+
'timeout' and 'polling_interval' keys
406439
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
407440
:return: A new instance of the AsyncSeam class authenticated with the
408441
provided API key
@@ -445,7 +478,7 @@ def from_personal_access_token(
445478
:type endpoint: Optional[str]
446479
:param wait_for_action_attempt: Controls whether to wait for an
447480
action attempt to complete. Can be a boolean or a dictionary with
448-
'timeout' and 'poll_interval' keys
481+
'timeout' and 'polling_interval' keys
449482
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
450483
:return: A new instance of the AsyncSeam class authenticated with the
451484
provided personal access token

‎seam/seam_without_workspace.py‎

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .parse_options import parse_without_workspace_options
77
from .client import AsyncSeamHttpClient, SeamHttpClient
88
from .models import AbstractAsyncSeamWithoutWorkspace, AbstractSeamWithoutWorkspace
9+
from .modules.action_attempts import normalize_wait_for_action_attempt
910
from .routes.workspaces import AsyncWorkspaces, Workspaces
1011

1112

@@ -63,7 +64,7 @@ def __init__(
6364
:type endpoint: Optional[str]
6465
:param wait_for_action_attempt: Controls whether to wait for an
6566
action attempt to complete. Can be a boolean or a dictionary with
66-
'timeout' and 'poll_interval' keys
67+
'timeout' and 'polling_interval' keys
6768
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
6869
:param retries: Configuration for retry behavior on failed requests
6970
:type retries: Optional[httpx_retries.Retry]
@@ -79,7 +80,6 @@ def __init__(
7980
:raises SeamInvalidTokenError: If the provided personal access token format is invalid
8081
"""
8182

82-
self.wait_for_action_attempt = wait_for_action_attempt
8383
auth_headers, endpoint = parse_without_workspace_options(
8484
personal_access_token=personal_access_token,
8585
endpoint=endpoint,
@@ -93,11 +93,28 @@ def __init__(
9393
httpx_options=httpx_options,
9494
)
9595

96-
defaults = {"wait_for_action_attempt": wait_for_action_attempt}
96+
self.defaults = {
97+
"wait_for_action_attempt": normalize_wait_for_action_attempt(
98+
wait_for_action_attempt
99+
)
100+
}
97101

98-
self._workspaces = Workspaces(client=self.client, defaults=defaults)
102+
self._workspaces = Workspaces(client=self.client, defaults=self.defaults)
99103
self.workspaces = WorkspacesProxy(self._workspaces)
100104

105+
@property
106+
def wait_for_action_attempt(self) -> Union[bool, Dict[str, float]]:
107+
"""Default wait behavior for action attempts, shared with every route."""
108+
return self.defaults["wait_for_action_attempt"]
109+
110+
@wait_for_action_attempt.setter
111+
def wait_for_action_attempt(
112+
self, value: Optional[Union[bool, Dict[str, float]]]
113+
) -> None:
114+
self.defaults["wait_for_action_attempt"] = normalize_wait_for_action_attempt(
115+
value
116+
)
117+
101118
@classmethod
102119
def from_personal_access_token(
103120
cls,
@@ -121,7 +138,7 @@ def from_personal_access_token(
121138
:type endpoint: Optional[str]
122139
:param wait_for_action_attempt: Controls whether to wait for an
123140
action attempt to complete. Can be a boolean or a dictionary with
124-
'timeout' and 'poll_interval' keys
141+
'timeout' and 'polling_interval' keys
125142
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
126143
:param retries: Configuration for retry behavior on failed requests
127144
:type retries: Optional[httpx_retries.Retry]
@@ -209,7 +226,7 @@ def __init__(
209226
:type endpoint: Optional[str]
210227
:param wait_for_action_attempt: Controls whether to wait for an
211228
action attempt to complete. Can be a boolean or a dictionary with
212-
'timeout' and 'poll_interval' keys
229+
'timeout' and 'polling_interval' keys
213230
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
214231
:param retries: Configuration for retry behavior on failed requests
215232
:type retries: Optional[httpx_retries.Retry]
@@ -225,7 +242,6 @@ def __init__(
225242
:raises SeamInvalidTokenError: If the provided personal access token format is invalid
226243
"""
227244

228-
self.wait_for_action_attempt = wait_for_action_attempt
229245
auth_headers, endpoint = parse_without_workspace_options(
230246
personal_access_token=personal_access_token,
231247
endpoint=endpoint,
@@ -239,11 +255,28 @@ def __init__(
239255
httpx_options=httpx_options,
240256
)
241257

242-
defaults = {"wait_for_action_attempt": wait_for_action_attempt}
258+
self.defaults = {
259+
"wait_for_action_attempt": normalize_wait_for_action_attempt(
260+
wait_for_action_attempt
261+
)
262+
}
243263

244-
self._workspaces = AsyncWorkspaces(client=self.client, defaults=defaults)
264+
self._workspaces = AsyncWorkspaces(client=self.client, defaults=self.defaults)
245265
self.workspaces = AsyncWorkspacesProxy(self._workspaces)
246266

267+
@property
268+
def wait_for_action_attempt(self) -> Union[bool, Dict[str, float]]:
269+
"""Default wait behavior for action attempts, shared with every route."""
270+
return self.defaults["wait_for_action_attempt"]
271+
272+
@wait_for_action_attempt.setter
273+
def wait_for_action_attempt(
274+
self, value: Optional[Union[bool, Dict[str, float]]]
275+
) -> None:
276+
self.defaults["wait_for_action_attempt"] = normalize_wait_for_action_attempt(
277+
value
278+
)
279+
247280
@classmethod
248281
def from_personal_access_token(
249282
cls,
@@ -264,7 +297,7 @@ def from_personal_access_token(
264297
:type endpoint: Optional[str]
265298
:param wait_for_action_attempt: Controls whether to wait for an
266299
action attempt to complete. Can be a boolean or a dictionary with
267-
'timeout' and 'poll_interval' keys
300+
'timeout' and 'polling_interval' keys
268301
:type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
269302
:param retries: Configuration for retry behavior on failed requests
270303
:type retries: Optional[httpx_retries.Retry]

0 commit comments

Comments
 (0)