Skip to content

Commit 7c914a0

Browse files
committed
add operation_timeout option.
1 parent cf9a1e6 commit 7c914a0

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

alibabacloud_oss_v2/_client.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,13 @@ def __init__(
102102
credentials_provider: Optional[CredentialsProvider] = None,
103103
http_client: Optional[Union[HttpClient]] = None,
104104
address_style: Optional[AddressStyle] = None,
105-
readwrite_timeout: Optional[int] = None,
105+
readwrite_timeout: Optional[Union[int, float]] = None,
106106
response_handlers: Optional[List] = None,
107107
response_stream: Optional[bool] = None,
108108
auth_method: Optional[str] = None,
109109
feature_flags: Optional[int] = None,
110110
additional_headers: Optional[List[str]] = None,
111+
operation_timeout: Optional[Union[int, float]] = None,
111112
) -> None:
112113
self.product = product
113114
self.region = region
@@ -124,6 +125,7 @@ def __init__(
124125
self.auth_method = auth_method
125126
self.feature_flags = feature_flags or defaults.FF_DEFAULT
126127
self.additional_headers = additional_headers
128+
self.operation_timeout = operation_timeout
127129

128130

129131
class _InnerOptions:
@@ -188,6 +190,7 @@ def resolve_operation_kwargs(self, options: _Options, **kwargs):
188190
options.http_client = kwargs.get("http_client", options.http_client)
189191
options.readwrite_timeout = kwargs.get("readwrite_timeout", options.readwrite_timeout)
190192
options.auth_method = kwargs.get("auth_method", options.auth_method)
193+
options.operation_timeout = kwargs.get("operation_timeout", None)
191194

192195
def verify_operation(self, op_input: OperationInput, options: _Options) -> None:
193196
"""verify input and options"""
@@ -398,6 +401,11 @@ def _sent_http_request(self, context: SigningContext, options: _Options) -> Http
398401
retryer = options.retryer
399402
max_attempts = self.retry_max_attempts(options)
400403

404+
# operation timeout
405+
dealline = None
406+
if isinstance(options.operation_timeout, (int, float)):
407+
dealline = time.time() + options.operation_timeout
408+
401409
# Mark body
402410
marked_body = _MarkedBody(request.body)
403411
marked_body.mark()
@@ -419,13 +427,21 @@ def _sent_http_request(self, context: SigningContext, options: _Options) -> Http
419427
dealy = retryer.retry_delay(tries, error)
420428
time.sleep(dealy)
421429

430+
# operation timeout
431+
if dealline is not None and (time.time() > dealline):
432+
break
433+
422434
try:
423435
error = None
424436
response = self._sent_http_request_once(context, options)
425437
break
426438
except Exception as e:
427439
error = e
428440

441+
# operation timeout
442+
if dealline is not None and (time.time() > dealline):
443+
break
444+
429445
if marked_body.is_seekable() is False:
430446
break
431447

tests/unit/test_client.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,18 @@ def do_sent_request(op_input: OperationInput, options: Any) -> OperationOutput:
474474
)
475475
self.assertIsInstance(self.save_options.http_client, MockHttpClient)
476476

477+
# "operation_timeout"
478+
self.assertEqual(None, clinet._client._options.operation_timeout)
479+
clinet.invoke_operation(
480+
OperationInput(
481+
op_name='InvokeOperation',
482+
method='GET',
483+
bucket='bucket',
484+
),
485+
operation_timeout=40,
486+
)
487+
self.assertEqual(40, self.save_options.operation_timeout)
488+
477489

478490
def test_invoke_operation_user_agent(self):
479491
self.save_op_context: SigningContext = None
@@ -1162,6 +1174,121 @@ def _do_sent(request: HttpRequest, **kwargs) -> HttpResponse:
11621174
for d in self.save_data:
11631175
self.assertEqual(data, d)
11641176

1177+
def test_invoke_operation_retryable_with_operation_timeout(self):
1178+
self.save_request: List[HttpRequest] = None
1179+
self.save_data: List[Any] = None
1180+
1181+
def _do_sent(request: HttpRequest, **kwargs) -> HttpResponse:
1182+
self.save_request.append(request)
1183+
self.save_data.append(_read_body(request.body))
1184+
self.assertIsInstance(request.body, io_utils.TeeIterator)
1185+
raise exceptions.ServiceError(
1186+
status_code=500,
1187+
code='InternalError',
1188+
request_id='id-1234',
1189+
message='Please contact the server administrator, oss@service.aliyun.com.',
1190+
ec='',
1191+
timestamp='',
1192+
request_target=''
1193+
)
1194+
1195+
cfg = config.Config(
1196+
region='cn-hangzhou',
1197+
credentials_provider=credentials.AnonymousCredentialsProvider(),
1198+
)
1199+
clinet = client.Client(cfg)
1200+
1201+
1202+
# str, no operation_timeout
1203+
self.save_request = []
1204+
self.save_data = []
1205+
data = 'hello world'
1206+
1207+
with mock.patch.object(clinet._client._options.http_client, 'send', new= _do_sent) as _:
1208+
try:
1209+
clinet.put_object(models.PutObjectRequest(
1210+
bucket='bucket',
1211+
key='key',
1212+
body=data
1213+
))
1214+
self.fail('should not here')
1215+
except exceptions.OperationError as err:
1216+
self.assertIn("InternalError", str(err))
1217+
1218+
self.assertEqual(3, len(self.save_request))
1219+
self.assertEqual(3, len(self.save_data))
1220+
for d in self.save_data:
1221+
self.assertEqual(data.encode(), d)
1222+
1223+
# str, with 0 operation_timeout
1224+
self.save_request = []
1225+
self.save_data = []
1226+
data = 'hello world'
1227+
1228+
with mock.patch.object(clinet._client._options.http_client, 'send', new= _do_sent) as _:
1229+
try:
1230+
clinet.put_object(models.PutObjectRequest(
1231+
bucket='bucket',
1232+
key='key',
1233+
body=data
1234+
), operation_timeout=0)
1235+
self.fail('should not here')
1236+
except exceptions.OperationError as err:
1237+
self.assertIn("InternalError", str(err))
1238+
1239+
self.assertEqual(1, len(self.save_request))
1240+
self.assertEqual(1, len(self.save_data))
1241+
for d in self.save_data:
1242+
self.assertEqual(data.encode(), d)
1243+
1244+
# str, with 10s operation_timeout
1245+
self.save_request = []
1246+
self.save_data = []
1247+
data = 'hello world'
1248+
1249+
with mock.patch.object(clinet._client._options.http_client, 'send', new= _do_sent) as _:
1250+
try:
1251+
clinet.put_object(models.PutObjectRequest(
1252+
bucket='bucket',
1253+
key='key',
1254+
body=data
1255+
), operation_timeout=10)
1256+
self.fail('should not here')
1257+
except exceptions.OperationError as err:
1258+
self.assertIn("InternalError", str(err))
1259+
1260+
self.assertEqual(3, len(self.save_request))
1261+
self.assertEqual(3, len(self.save_data))
1262+
for d in self.save_data:
1263+
self.assertEqual(data.encode(), d)
1264+
1265+
# str, with 4s operation_timeout 2
1266+
cfg = config.Config(
1267+
region='cn-hangzhou',
1268+
credentials_provider=credentials.AnonymousCredentialsProvider(),
1269+
retryer=retry.StandardRetryer(backoff_delayer=retry.FixedDelayBackoff(2.5))
1270+
)
1271+
clinet = client.Client(cfg)
1272+
self.save_request = []
1273+
self.save_data = []
1274+
data = 'hello world'
1275+
1276+
with mock.patch.object(clinet._client._options.http_client, 'send', new= _do_sent) as _:
1277+
try:
1278+
clinet.put_object(models.PutObjectRequest(
1279+
bucket='bucket',
1280+
key='key',
1281+
body=data
1282+
), operation_timeout=4)
1283+
self.fail('should not here')
1284+
except exceptions.OperationError as err:
1285+
self.assertIn("InternalError", str(err))
1286+
1287+
self.assertEqual(2, len(self.save_request))
1288+
self.assertEqual(2, len(self.save_data))
1289+
for d in self.save_data:
1290+
self.assertEqual(data.encode(), d)
1291+
11651292
def test_invoke_operation_addressing_mode(self):
11661293
""" """
11671294
self.save_op_context: SigningContext = None

0 commit comments

Comments
 (0)