1212
1313from __future__ import annotations
1414
15+ from datetime import timedelta
1516from typing import Any , Optional , cast
1617
18+ from durabletask .task import RetryPolicy
19+
1720from ..internal .compat .token_source import TokenSource
1821
1922
23+ class HttpRetryOptions (RetryPolicy ):
24+ """Durable failure retries for the initial HTTP request, not Location polls.
25+
26+ Durations use ``timedelta``, as in :class:`durabletask.task.RetryPolicy`.
27+ ``max_number_of_attempts`` includes the initial attempt. Backoff defaults to
28+ 1, the maximum retry interval to six days, and the retry timeout to unbounded.
29+ An omitted or empty ``status_codes_to_retry`` selects every non-2xx
30+ response; a nonempty list selects exactly those codes (even 202).
31+ Transport and other activity failures are retried regardless of this list.
32+ Exhaustion fails ``call_http`` rather than returning a selected response.
33+ """
34+
35+ def __init__ (self , * ,
36+ first_retry_interval : timedelta ,
37+ max_number_of_attempts : int ,
38+ backoff_coefficient : float | None = 1.0 ,
39+ max_retry_interval : timedelta | None = timedelta (days = 6 ),
40+ retry_timeout : timedelta | None = None ,
41+ status_codes_to_retry : Optional [list [int ]] = None ) -> None :
42+ super ().__init__ (
43+ first_retry_interval = first_retry_interval ,
44+ max_number_of_attempts = max_number_of_attempts ,
45+ backoff_coefficient = backoff_coefficient ,
46+ max_retry_interval = max_retry_interval ,
47+ retry_timeout = retry_timeout )
48+ self ._status_codes_to_retry = (
49+ list (status_codes_to_retry ) if status_codes_to_retry is not None else None )
50+
51+ @property
52+ def status_codes_to_retry (self ) -> Optional [list [int ]]:
53+ """Return a copy of the selected response codes, or ``None``."""
54+ return (list (self ._status_codes_to_retry )
55+ if self ._status_codes_to_retry is not None else None )
56+
57+ def to_json (self ) -> dict [str , Any ]:
58+ """Snapshot the policy into a JSON-serializable wire payload."""
59+ millisecond = timedelta (milliseconds = 1 )
60+ return {
61+ "firstRetryIntervalInMilliseconds" : self .first_retry_interval / millisecond ,
62+ "maxNumberOfAttempts" : self .max_number_of_attempts ,
63+ "backoffCoefficient" : self .backoff_coefficient ,
64+ "maxRetryIntervalInMilliseconds" : (
65+ self .max_retry_interval / millisecond if self .max_retry_interval is not None else None ),
66+ "retryTimeoutInMilliseconds" : (
67+ self .retry_timeout / millisecond if self .retry_timeout is not None else None ),
68+ "statusCodesToRetry" : self .status_codes_to_retry ,
69+ }
70+
71+ @classmethod
72+ def from_json (cls , value : dict [str , Any ]) -> "HttpRetryOptions" :
73+ """Reconstruct a snapshotted policy from its wire payload."""
74+ max_interval = value ["maxRetryIntervalInMilliseconds" ]
75+ timeout = value ["retryTimeoutInMilliseconds" ]
76+ return cls (
77+ first_retry_interval = timedelta (milliseconds = value ["firstRetryIntervalInMilliseconds" ]),
78+ max_number_of_attempts = value ["maxNumberOfAttempts" ],
79+ backoff_coefficient = value ["backoffCoefficient" ],
80+ max_retry_interval = timedelta (milliseconds = max_interval ) if max_interval is not None else None ,
81+ retry_timeout = timedelta (milliseconds = timeout ) if timeout is not None else None ,
82+ status_codes_to_retry = value .get ("statusCodesToRetry" ))
83+
84+
2085class DurableHttpRequest :
2186 """Data structure representing a durable HTTP request."""
2287
@@ -25,12 +90,14 @@ def __init__(self,
2590 uri : str ,
2691 content : Optional [str ] = None ,
2792 headers : Optional [dict [str , str ]] = None ,
28- token_source : Optional [TokenSource ] = None ):
93+ token_source : Optional [TokenSource ] = None ,
94+ retry_options : Optional [HttpRetryOptions ] = None ):
2995 self ._method = method
3096 self ._uri = uri
3197 self ._content = content
3298 self ._headers = headers
3399 self ._token_source = token_source
100+ self ._retry_options = retry_options .to_json () if retry_options is not None else None
34101
35102 @property
36103 def method (self ) -> str :
@@ -57,6 +124,12 @@ def token_source(self) -> Optional[TokenSource]:
57124 """Get the source of the OAuth token to add to the request."""
58125 return self ._token_source
59126
127+ @property
128+ def retry_options (self ) -> Optional [HttpRetryOptions ]:
129+ """Get a copy of the policy captured when this request was created."""
130+ return (HttpRetryOptions .from_json (self ._retry_options )
131+ if self ._retry_options is not None else None )
132+
60133 def to_json (self ) -> dict [str , Any ]:
61134 """Convert this request into a JSON-serializable dictionary."""
62135 json_dict : dict [str , Any ] = {"method" : self ._method , "uri" : self ._uri }
@@ -66,6 +139,9 @@ def to_json(self) -> dict[str, Any]:
66139 json_dict ["headers" ] = dict (self ._headers )
67140 if self ._token_source is not None :
68141 json_dict ["tokenSource" ] = self ._token_source .to_json ()
142+ retry_options = self .retry_options
143+ if retry_options is not None :
144+ json_dict ["retryOptions" ] = retry_options .to_json ()
69145 return json_dict
70146
71147
0 commit comments