cli2 3.3.25__tar.gz → 3.3.29__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli2-3.3.25/cli2.egg-info → cli2-3.3.29}/PKG-INFO +1 -1
- {cli2-3.3.25 → cli2-3.3.29}/cli2/client.py +112 -54
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_client.py +73 -38
- {cli2-3.3.25 → cli2-3.3.29/cli2.egg-info}/PKG-INFO +1 -1
- {cli2-3.3.25 → cli2-3.3.29}/setup.py +1 -1
- {cli2-3.3.25 → cli2-3.3.29}/MANIFEST.in +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/README.rst +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/classifiers.txt +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/__init__.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/argument.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/asyncio.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/cli.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/colors.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/command.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/configuration.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/decorators.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/display.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/entry_point.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/example_client.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/example_client_complex.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/example_nesting.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/example_obj.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/group.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/logging.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/node.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/overrides.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/sphinx.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/table.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_cli.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_command.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_configuration.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_decorators.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_display.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_entry_point.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_group.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_inject.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_node.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2/test_table.py +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2.egg-info/SOURCES.txt +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2.egg-info/dependency_links.txt +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2.egg-info/entry_points.txt +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2.egg-info/requires.txt +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/cli2.egg-info/top_level.txt +0 -0
- {cli2-3.3.25 → cli2-3.3.29}/setup.cfg +0 -0
|
@@ -305,6 +305,11 @@ class Paginator:
|
|
|
305
305
|
break
|
|
306
306
|
page += 1
|
|
307
307
|
|
|
308
|
+
async def first(self):
|
|
309
|
+
""" Return first item """
|
|
310
|
+
async for item in self:
|
|
311
|
+
return item
|
|
312
|
+
|
|
308
313
|
|
|
309
314
|
class Field:
|
|
310
315
|
"""
|
|
@@ -1027,12 +1032,13 @@ class Handler:
|
|
|
1027
1032
|
self.tries = self.tries_default if tries is None else tries
|
|
1028
1033
|
self.backoff = self.backoff_default if backoff is None else backoff
|
|
1029
1034
|
|
|
1030
|
-
async def __call__(self, client, response, tries, mask):
|
|
1035
|
+
async def __call__(self, client, response, tries, mask, log):
|
|
1031
1036
|
if isinstance(response, Exception):
|
|
1032
1037
|
if tries >= self.tries:
|
|
1033
1038
|
raise response
|
|
1034
1039
|
# httpx session is rendered unusable after a TransportError
|
|
1035
1040
|
if isinstance(response, httpx.TransportError):
|
|
1041
|
+
log.warn('reconnect', exception=str(response))
|
|
1036
1042
|
await client.client_reset()
|
|
1037
1043
|
return
|
|
1038
1044
|
|
|
@@ -1048,13 +1054,25 @@ class Handler:
|
|
|
1048
1054
|
if tries >= self.tries:
|
|
1049
1055
|
raise RetriesExceededError(client, response, tries, mask)
|
|
1050
1056
|
|
|
1057
|
+
seconds = tries * self.backoff
|
|
1058
|
+
kwargs = dict(
|
|
1059
|
+
status_code=response.status_code,
|
|
1060
|
+
tries=tries,
|
|
1061
|
+
sleep=seconds,
|
|
1062
|
+
)
|
|
1063
|
+
key, value = client.response_log_data(response, mask)
|
|
1064
|
+
if value:
|
|
1065
|
+
kwargs[key] = value
|
|
1066
|
+
|
|
1051
1067
|
if response.status_code in self.retokens:
|
|
1052
1068
|
if tries:
|
|
1053
1069
|
# our authentication is just not working, no need to retry
|
|
1054
1070
|
raise TokenGetError(client, response, tries, mask)
|
|
1071
|
+
log.warn('retoken', **kwargs)
|
|
1055
1072
|
await client.token_reset()
|
|
1056
1073
|
|
|
1057
|
-
|
|
1074
|
+
log.info('retry', **kwargs)
|
|
1075
|
+
await asyncio.sleep(seconds)
|
|
1058
1076
|
|
|
1059
1077
|
|
|
1060
1078
|
class ClientError(Exception):
|
|
@@ -1079,27 +1097,31 @@ class ResponseError(ClientError):
|
|
|
1079
1097
|
:param exc: httpx.HTTPStatusError
|
|
1080
1098
|
"""
|
|
1081
1099
|
output = [msg]
|
|
1100
|
+
key, value = self.client.request_log_data(
|
|
1101
|
+
self.response.request,
|
|
1102
|
+
self.mask,
|
|
1103
|
+
)
|
|
1082
1104
|
request_msg = ' '.join([
|
|
1083
|
-
self.response.request.method,
|
|
1105
|
+
str(self.response.request.method),
|
|
1084
1106
|
str(self.response.request.url),
|
|
1085
1107
|
])
|
|
1086
1108
|
|
|
1087
1109
|
output.append(
|
|
1088
1110
|
f'{colors.reset}{colors.bold}{request_msg}{colors.reset}',
|
|
1089
1111
|
)
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
self.mask,
|
|
1093
|
-
)
|
|
1094
|
-
if request:
|
|
1095
|
-
output.append(display.render(request))
|
|
1112
|
+
if value:
|
|
1113
|
+
output.append(display.render(value))
|
|
1096
1114
|
|
|
1115
|
+
key, value = self.client.response_log_data(self.response, self.mask)
|
|
1097
1116
|
output.append(
|
|
1098
|
-
|
|
1117
|
+
''.join([
|
|
1118
|
+
colors.bold,
|
|
1119
|
+
f'HTTP {self.response.status_code}',
|
|
1120
|
+
colors.reset,
|
|
1121
|
+
])
|
|
1099
1122
|
)
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
output.append(display.render(response))
|
|
1123
|
+
if value:
|
|
1124
|
+
output.append(display.render(value))
|
|
1103
1125
|
|
|
1104
1126
|
return '\n'.join(output)
|
|
1105
1127
|
|
|
@@ -1217,7 +1239,10 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1217
1239
|
"""
|
|
1218
1240
|
client = httpx.AsyncClient(*self._client_args, **self._client_kwargs)
|
|
1219
1241
|
if self.token and not self.token_getting:
|
|
1220
|
-
|
|
1242
|
+
try:
|
|
1243
|
+
self.client_token_apply(client)
|
|
1244
|
+
except NotImplementedError:
|
|
1245
|
+
pass
|
|
1221
1246
|
return client
|
|
1222
1247
|
|
|
1223
1248
|
@client.setter
|
|
@@ -1228,27 +1253,34 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1228
1253
|
def client(self):
|
|
1229
1254
|
self._client = None
|
|
1230
1255
|
|
|
1231
|
-
async def
|
|
1232
|
-
|
|
1256
|
+
async def send(self, request, handler, mask, retries=True, semaphore=None,
|
|
1257
|
+
log=None, auth=None, follow_redirects=None):
|
|
1233
1258
|
"""
|
|
1234
1259
|
Internal request method
|
|
1235
1260
|
"""
|
|
1236
1261
|
semaphore = semaphore or self.semaphore
|
|
1237
1262
|
tries = 0
|
|
1238
1263
|
|
|
1264
|
+
async def _send():
|
|
1265
|
+
return await self.client.send(
|
|
1266
|
+
request,
|
|
1267
|
+
auth=auth,
|
|
1268
|
+
follow_redirects=follow_redirects,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1239
1271
|
async def _request():
|
|
1240
1272
|
if semaphore:
|
|
1241
1273
|
async with semaphore:
|
|
1242
|
-
return await
|
|
1243
|
-
return await
|
|
1274
|
+
return await _send()
|
|
1275
|
+
return await _send()
|
|
1244
1276
|
|
|
1245
1277
|
while retries or tries > 1:
|
|
1246
1278
|
try:
|
|
1247
1279
|
response = await _request()
|
|
1248
1280
|
except Exception as exc:
|
|
1249
|
-
await handler(self, exc, tries, mask)
|
|
1281
|
+
await handler(self, exc, tries, mask, log)
|
|
1250
1282
|
else:
|
|
1251
|
-
if response := await handler(self, response, tries, mask):
|
|
1283
|
+
if response := await handler(self, response, tries, mask, log):
|
|
1252
1284
|
return response
|
|
1253
1285
|
|
|
1254
1286
|
tries += 1
|
|
@@ -1275,7 +1307,7 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1275
1307
|
pass
|
|
1276
1308
|
self.token_getting = False
|
|
1277
1309
|
|
|
1278
|
-
|
|
1310
|
+
def client_token_apply(self, client):
|
|
1279
1311
|
"""
|
|
1280
1312
|
Actually provision self.client with self.token.
|
|
1281
1313
|
|
|
@@ -1317,9 +1349,19 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1317
1349
|
"""
|
|
1318
1350
|
raise NotImplementedError()
|
|
1319
1351
|
|
|
1320
|
-
async def request(
|
|
1321
|
-
|
|
1322
|
-
|
|
1352
|
+
async def request(
|
|
1353
|
+
# base arguments
|
|
1354
|
+
self, method, url,
|
|
1355
|
+
*,
|
|
1356
|
+
# cli2 arguments
|
|
1357
|
+
handler=None, quiet=False, accepts=None, refuses=None, tries=None,
|
|
1358
|
+
backoff=None, retries=True, semaphore=None, mask=None,
|
|
1359
|
+
# httpx arguments
|
|
1360
|
+
content=None, data=None, files=None, json=None, params=None,
|
|
1361
|
+
headers=None, cookies=None, auth=httpx.USE_CLIENT_DEFAULT,
|
|
1362
|
+
follow_redirects=httpx.USE_CLIENT_DEFAULT,
|
|
1363
|
+
timeout=httpx.USE_CLIENT_DEFAULT, extensions=None,
|
|
1364
|
+
):
|
|
1323
1365
|
"""
|
|
1324
1366
|
Request method
|
|
1325
1367
|
|
|
@@ -1388,46 +1430,71 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1388
1430
|
else:
|
|
1389
1431
|
handler = self.handler
|
|
1390
1432
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1433
|
+
request = self.client.build_request(
|
|
1434
|
+
method=method,
|
|
1435
|
+
url=url,
|
|
1436
|
+
content=content,
|
|
1437
|
+
data=data,
|
|
1438
|
+
files=files,
|
|
1439
|
+
json=json,
|
|
1440
|
+
params=params,
|
|
1441
|
+
headers=headers,
|
|
1442
|
+
cookies=cookies,
|
|
1443
|
+
timeout=timeout,
|
|
1444
|
+
extensions=extensions,
|
|
1445
|
+
)
|
|
1394
1446
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1447
|
+
log = self.logger.bind(method=method, url=str(request.url))
|
|
1448
|
+
if not quiet or self.debug:
|
|
1449
|
+
key, value = self.request_log_data(request, mask, quiet)
|
|
1450
|
+
kwargs = dict()
|
|
1451
|
+
if value:
|
|
1452
|
+
kwargs[key] = value
|
|
1453
|
+
log.debug('request', **kwargs)
|
|
1454
|
+
|
|
1455
|
+
response = await self.send(
|
|
1456
|
+
request,
|
|
1398
1457
|
handler=handler,
|
|
1399
1458
|
retries=retries,
|
|
1400
1459
|
semaphore=semaphore,
|
|
1401
1460
|
mask=mask,
|
|
1402
|
-
|
|
1461
|
+
log=log,
|
|
1462
|
+
auth=auth,
|
|
1463
|
+
follow_redirects=follow_redirects,
|
|
1403
1464
|
)
|
|
1404
1465
|
|
|
1405
1466
|
_log = dict(status_code=response.status_code)
|
|
1406
1467
|
if not quiet or self.debug:
|
|
1407
|
-
self.response_log_data(response, mask
|
|
1468
|
+
key, value = self.response_log_data(response, mask)
|
|
1469
|
+
if value:
|
|
1470
|
+
_log[key] = value
|
|
1408
1471
|
|
|
1409
1472
|
log.info('response', **_log)
|
|
1410
1473
|
|
|
1411
1474
|
return response
|
|
1412
1475
|
|
|
1413
|
-
def response_log_data(self, response, mask
|
|
1476
|
+
def response_log_data(self, response, mask):
|
|
1414
1477
|
try:
|
|
1415
|
-
data =
|
|
1416
|
-
key = 'json'
|
|
1478
|
+
data = response.json()
|
|
1417
1479
|
except json.JSONDecodeError:
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
return
|
|
1480
|
+
if response.content:
|
|
1481
|
+
return 'content', self.mask_content(response.content, mask)
|
|
1482
|
+
else:
|
|
1483
|
+
if data:
|
|
1484
|
+
return 'json', self.mask_data(data, mask)
|
|
1485
|
+
return None, None
|
|
1424
1486
|
|
|
1425
1487
|
def request_log_data(self, request, mask, quiet=False):
|
|
1426
1488
|
content = request.content.decode()
|
|
1489
|
+
if not content:
|
|
1490
|
+
return None, None
|
|
1491
|
+
|
|
1427
1492
|
try:
|
|
1428
|
-
|
|
1493
|
+
data = json.loads(content)
|
|
1429
1494
|
except json.JSONDecodeError:
|
|
1430
1495
|
pass
|
|
1496
|
+
else:
|
|
1497
|
+
return 'json', self.mask_data(data, mask)
|
|
1431
1498
|
|
|
1432
1499
|
parsed = parse_qs(content)
|
|
1433
1500
|
if parsed:
|
|
@@ -1435,18 +1502,9 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1435
1502
|
key: value[0] if len(value) == 1 else value
|
|
1436
1503
|
for key, value in parsed.items()
|
|
1437
1504
|
}
|
|
1438
|
-
return self.mask_data(data, mask)
|
|
1439
|
-
|
|
1440
|
-
return self.mask_content(content, mask)
|
|
1441
|
-
|
|
1442
|
-
def kwargs_log_data(self, kwargs, mask, quiet=False):
|
|
1443
|
-
_log = kwargs.copy()
|
|
1444
|
-
if 'content' in kwargs:
|
|
1445
|
-
_log['content'] = self.mask_content(kwargs['content'], mask)
|
|
1446
|
-
for key in ('json', 'data'):
|
|
1447
|
-
if key in kwargs:
|
|
1448
|
-
_log[key] = self.mask_data(kwargs[key], mask)
|
|
1449
|
-
return _log
|
|
1505
|
+
return 'data', self.mask_data(data, mask)
|
|
1506
|
+
|
|
1507
|
+
return 'content', self.mask_content(content, mask)
|
|
1450
1508
|
|
|
1451
1509
|
def mask_content(self, content, mask=None):
|
|
1452
1510
|
"""
|
|
@@ -15,9 +15,9 @@ class HandlerSentinel(cli2.Handler):
|
|
|
15
15
|
super().__init__(*args, **kwargs)
|
|
16
16
|
self.calls = []
|
|
17
17
|
|
|
18
|
-
async def __call__(self, client, response, tries, mask):
|
|
18
|
+
async def __call__(self, client, response, tries, mask, log):
|
|
19
19
|
self.calls.append((client, response.status_code, tries))
|
|
20
|
-
return await super().__call__(client, response, tries, mask)
|
|
20
|
+
return await super().__call__(client, response, tries, mask, log)
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@pytest.fixture
|
|
@@ -154,7 +154,7 @@ async def test_error_remote(httpx_mock):
|
|
|
154
154
|
global raised
|
|
155
155
|
raised = True
|
|
156
156
|
raise httpx.RemoteProtocolError('foo')
|
|
157
|
-
client.client.
|
|
157
|
+
client.client.send = raises
|
|
158
158
|
old_client = client.client
|
|
159
159
|
response = await client.get('http://lol')
|
|
160
160
|
assert client.client is not old_client
|
|
@@ -186,12 +186,14 @@ async def test_factory(httpx_mock, client_class):
|
|
|
186
186
|
async def test_client_handler(httpx_mock, client_class):
|
|
187
187
|
class Client(client_class):
|
|
188
188
|
def client_factory(self):
|
|
189
|
-
|
|
189
|
+
client = super().client_factory()
|
|
190
|
+
client.send = mock.Mock()
|
|
191
|
+
return client
|
|
190
192
|
|
|
191
193
|
client = Client(handler=HandlerSentinel())
|
|
192
194
|
|
|
193
195
|
# test response retry
|
|
194
|
-
client.client.
|
|
196
|
+
client.client.send.side_effect = [
|
|
195
197
|
_response(status_code=500),
|
|
196
198
|
_response(status_code=200),
|
|
197
199
|
]
|
|
@@ -203,7 +205,7 @@ async def test_client_handler(httpx_mock, client_class):
|
|
|
203
205
|
]
|
|
204
206
|
|
|
205
207
|
# test TransportError retry
|
|
206
|
-
client.client.
|
|
208
|
+
client.client.send.side_effect = [
|
|
207
209
|
httpx.TransportError("foo"),
|
|
208
210
|
_response(status_code=200),
|
|
209
211
|
]
|
|
@@ -216,23 +218,30 @@ async def test_client_handler(httpx_mock, client_class):
|
|
|
216
218
|
|
|
217
219
|
@pytest.mark.asyncio
|
|
218
220
|
async def test_handler(client_class):
|
|
221
|
+
log = mock.Mock()
|
|
219
222
|
client = client_class()
|
|
220
223
|
client.client_reset = mock.AsyncMock()
|
|
221
224
|
client.token_reset = mock.AsyncMock()
|
|
222
225
|
handler = cli2.Handler(accepts=[201], refuses=[218], retokens=[418])
|
|
223
226
|
|
|
224
227
|
response = httpx.Response(status_code=201)
|
|
225
|
-
result = await handler(client, response, 0, [])
|
|
228
|
+
result = await handler(client, response, 0, [], log)
|
|
226
229
|
assert result == response
|
|
227
230
|
|
|
228
231
|
response = httpx.Response(status_code=200)
|
|
229
|
-
result = await handler(client, response, 0, [])
|
|
232
|
+
result = await handler(client, response, 0, [], log)
|
|
233
|
+
log.info.assert_called_once_with(
|
|
234
|
+
'retry', status_code=200, tries=0, sleep=.0
|
|
235
|
+
)
|
|
230
236
|
assert not result
|
|
231
237
|
|
|
232
238
|
response = httpx.Response(status_code=200, content='[2]')
|
|
233
239
|
response.request = httpx.Request('POST', '/', json=[1])
|
|
234
240
|
with pytest.raises(cli2.RetriesExceededError) as exc:
|
|
235
|
-
await handler(client, response, handler.tries + 1, [])
|
|
241
|
+
await handler(client, response, handler.tries + 1, [], log)
|
|
242
|
+
log.info.assert_called_once_with(
|
|
243
|
+
'retry', status_code=200, tries=0, sleep=.0
|
|
244
|
+
)
|
|
236
245
|
|
|
237
246
|
msg = 'Unacceptable response <Response [200 OK]> after 31 tries\n\x1b[0m\x1b[1mPOST /\x1b[0m\n-\x1b[37m \x1b[39;49;00m1\x1b[37m\x1b[39;49;00m\n\n\x1b[1mHTTP 200\x1b[0m\n-\x1b[37m \x1b[39;49;00m2\x1b[37m\x1b[39;49;00m\n' # noqa
|
|
238
247
|
assert str(exc.value) == msg
|
|
@@ -240,7 +249,7 @@ async def test_handler(client_class):
|
|
|
240
249
|
response = httpx.Response(status_code=200)
|
|
241
250
|
response.request = httpx.Request('GET', '/')
|
|
242
251
|
with pytest.raises(cli2.RetriesExceededError) as exc:
|
|
243
|
-
await handler(client, response, handler.tries + 1, [])
|
|
252
|
+
await handler(client, response, handler.tries + 1, [], log)
|
|
244
253
|
|
|
245
254
|
msg = 'Unacceptable response <Response [200 OK]> after 31 tries\n\x1b[0m\x1b[1mGET /\x1b[0m\n\x1b[1mHTTP 200\x1b[0m' # noqa
|
|
246
255
|
assert str(exc.value) == msg
|
|
@@ -248,31 +257,38 @@ async def test_handler(client_class):
|
|
|
248
257
|
response = httpx.Response(status_code=218)
|
|
249
258
|
response.request = httpx.Request('POST', '/')
|
|
250
259
|
with pytest.raises(cli2.RefusedResponseError):
|
|
251
|
-
await handler(client, response, 1, [])
|
|
260
|
+
await handler(client, response, 1, [], log)
|
|
252
261
|
|
|
253
262
|
response = httpx.Response(status_code=418)
|
|
254
263
|
response.request = httpx.Request('POST', '/')
|
|
255
264
|
with pytest.raises(cli2.TokenGetError):
|
|
256
|
-
await handler(client, response, 1, [])
|
|
265
|
+
await handler(client, response, 1, [], log)
|
|
257
266
|
|
|
258
267
|
assert not client.client_reset.await_count
|
|
259
|
-
result = await handler(client, httpx.TransportError('foo'), 0, [])
|
|
268
|
+
result = await handler(client, httpx.TransportError('foo'), 0, [], log)
|
|
269
|
+
log.warn.assert_called_once_with('reconnect', exception='foo')
|
|
260
270
|
assert not result
|
|
261
271
|
assert client.client_reset.await_count == 1
|
|
262
272
|
|
|
263
273
|
with pytest.raises(httpx.TransportError) as exc:
|
|
264
|
-
await handler(
|
|
274
|
+
await handler(
|
|
275
|
+
client, httpx.TransportError('x'), handler.tries + 1, [], log
|
|
276
|
+
)
|
|
265
277
|
|
|
266
278
|
response = httpx.Response(status_code=418)
|
|
267
279
|
assert not client.token_reset.await_count
|
|
268
|
-
|
|
280
|
+
log.warn.reset_mock()
|
|
281
|
+
result = await handler(client, response, 0, [], log)
|
|
282
|
+
log.warn.assert_called_once_with(
|
|
283
|
+
'retoken', status_code=418, tries=0, sleep=.0
|
|
284
|
+
)
|
|
269
285
|
assert not result
|
|
270
286
|
assert client.token_reset.await_count == 1
|
|
271
287
|
|
|
272
288
|
handler = cli2.Handler(accepts=[], refuses=[222])
|
|
273
289
|
|
|
274
290
|
response = httpx.Response(status_code=123)
|
|
275
|
-
result = await handler(client, response, 0, [])
|
|
291
|
+
result = await handler(client, response, 0, [], log)
|
|
276
292
|
assert result == response
|
|
277
293
|
|
|
278
294
|
|
|
@@ -285,16 +301,14 @@ async def test_retry(httpx_mock, client_class):
|
|
|
285
301
|
return self.return_token
|
|
286
302
|
|
|
287
303
|
def client_factory(self):
|
|
288
|
-
|
|
304
|
+
client = super().client_factory()
|
|
305
|
+
client.send = mock.Mock()
|
|
306
|
+
return client
|
|
289
307
|
|
|
290
|
-
client = Client(
|
|
291
|
-
retry_request=[500],
|
|
292
|
-
recreate_client=[400],
|
|
293
|
-
backoff=0.1,
|
|
294
|
-
)
|
|
308
|
+
client = Client()
|
|
295
309
|
|
|
296
310
|
current_client = client.client
|
|
297
|
-
client.client.
|
|
311
|
+
client.client.send.side_effect = [
|
|
298
312
|
_response(status_code=500),
|
|
299
313
|
_response(status_code=500),
|
|
300
314
|
_response(status_code=200),
|
|
@@ -348,6 +362,12 @@ async def test_pagination_initialize(httpx_mock):
|
|
|
348
362
|
client = PaginatedClient(base_url='http://lol')
|
|
349
363
|
assert await client.paginate('/').list() == [dict(a=1), dict(a=2)]
|
|
350
364
|
|
|
365
|
+
httpx_mock.add_response(url='http://lol/?page=1', json=dict(
|
|
366
|
+
total_pages=2,
|
|
367
|
+
items=[dict(a=1)],
|
|
368
|
+
))
|
|
369
|
+
assert await client.paginate('/').first() == dict(a=1)
|
|
370
|
+
|
|
351
371
|
|
|
352
372
|
@pytest.mark.asyncio
|
|
353
373
|
async def test_token_get(httpx_mock):
|
|
@@ -768,7 +788,7 @@ def test_datetime_default_fmt():
|
|
|
768
788
|
)
|
|
769
789
|
async def test_mask_logs(key):
|
|
770
790
|
client = Client(mask=['scrt', 'password'])
|
|
771
|
-
client.client = mock.AsyncMock()
|
|
791
|
+
client.client.send = mock.AsyncMock()
|
|
772
792
|
|
|
773
793
|
client.logger = mock.Mock()
|
|
774
794
|
response = httpx.Response(
|
|
@@ -777,9 +797,12 @@ async def test_mask_logs(key):
|
|
|
777
797
|
)
|
|
778
798
|
data = dict(foo='bar', password='secret')
|
|
779
799
|
response.request = httpx.Request('POST', '/', **{key: data})
|
|
780
|
-
client.client.
|
|
800
|
+
client.client.send.return_value = response
|
|
781
801
|
await client.post('/', **{key: data})
|
|
782
|
-
client.logger.bind.assert_called_once_with(
|
|
802
|
+
client.logger.bind.assert_called_once_with(
|
|
803
|
+
method='POST',
|
|
804
|
+
url='http://lol/',
|
|
805
|
+
)
|
|
783
806
|
log = client.logger.bind.return_value
|
|
784
807
|
log.debug.assert_called_once_with(
|
|
785
808
|
'request',
|
|
@@ -816,7 +839,7 @@ async def test_mask_exceptions(client_class):
|
|
|
816
839
|
@pytest.mark.asyncio
|
|
817
840
|
async def test_request_mask():
|
|
818
841
|
client = Client(mask=['password'])
|
|
819
|
-
client.client = mock.AsyncMock()
|
|
842
|
+
client.client.send = mock.AsyncMock()
|
|
820
843
|
|
|
821
844
|
client.logger = mock.Mock()
|
|
822
845
|
response = httpx.Response(
|
|
@@ -825,9 +848,12 @@ async def test_request_mask():
|
|
|
825
848
|
)
|
|
826
849
|
data = dict(foo='bar', password='secret')
|
|
827
850
|
response.request = httpx.Request('POST', '/', json=data)
|
|
828
|
-
client.client.
|
|
851
|
+
client.client.send.return_value = response
|
|
829
852
|
await client.post('/', json=data, mask=['scrt'])
|
|
830
|
-
client.logger.bind.assert_called_once_with(
|
|
853
|
+
client.logger.bind.assert_called_once_with(
|
|
854
|
+
method='POST',
|
|
855
|
+
url='http://lol/'
|
|
856
|
+
)
|
|
831
857
|
log = client.logger.bind.return_value
|
|
832
858
|
log.debug.assert_called_once_with(
|
|
833
859
|
'request',
|
|
@@ -843,13 +869,16 @@ async def test_request_mask():
|
|
|
843
869
|
@pytest.mark.asyncio
|
|
844
870
|
async def test_log_content():
|
|
845
871
|
client = Client()
|
|
846
|
-
client.client = mock.AsyncMock()
|
|
872
|
+
client.client.send = mock.AsyncMock()
|
|
847
873
|
client.logger = mock.Mock()
|
|
848
874
|
response = httpx.Response(status_code=200, content='lol:]bar')
|
|
849
875
|
response.request = httpx.Request('POST', '/')
|
|
850
|
-
client.client.
|
|
876
|
+
client.client.send.return_value = response
|
|
851
877
|
await client.post('/', content='lol:]foo')
|
|
852
|
-
client.logger.bind.assert_called_once_with(
|
|
878
|
+
client.logger.bind.assert_called_once_with(
|
|
879
|
+
method='POST',
|
|
880
|
+
url='http://lol/'
|
|
881
|
+
)
|
|
853
882
|
log = client.logger.bind.return_value
|
|
854
883
|
log.debug.assert_called_once_with('request', content='lol:]foo')
|
|
855
884
|
log.info.assert_called_once_with(
|
|
@@ -860,14 +889,17 @@ async def test_log_content():
|
|
|
860
889
|
@pytest.mark.asyncio
|
|
861
890
|
async def test_log_quiet():
|
|
862
891
|
client = Client()
|
|
863
|
-
client.client = mock.AsyncMock()
|
|
892
|
+
client.client.send = mock.AsyncMock()
|
|
864
893
|
client.logger = mock.Mock()
|
|
865
894
|
response = httpx.Response(status_code=200, content='[1]')
|
|
866
895
|
response.request = httpx.Request('GET', '/')
|
|
867
|
-
client.client.
|
|
896
|
+
client.client.send.return_value = response
|
|
868
897
|
await client.get('/', json=[1], quiet=True)
|
|
869
898
|
log = client.logger.bind.return_value
|
|
870
|
-
client.logger.bind.assert_called_once_with(
|
|
899
|
+
client.logger.bind.assert_called_once_with(
|
|
900
|
+
method='GET',
|
|
901
|
+
url='http://lol/',
|
|
902
|
+
)
|
|
871
903
|
log = client.logger.bind.return_value
|
|
872
904
|
assert not log.debug.call_args_list
|
|
873
905
|
log.info.assert_called_once_with('response', status_code=200)
|
|
@@ -933,7 +965,7 @@ def test_id_value():
|
|
|
933
965
|
@pytest.mark.asyncio
|
|
934
966
|
async def test_debug():
|
|
935
967
|
client = Client(mask=['scrt', 'password'], debug=True)
|
|
936
|
-
client.client = mock.AsyncMock()
|
|
968
|
+
client.client.send = mock.AsyncMock()
|
|
937
969
|
|
|
938
970
|
client.logger = mock.Mock()
|
|
939
971
|
response = httpx.Response(
|
|
@@ -942,9 +974,12 @@ async def test_debug():
|
|
|
942
974
|
)
|
|
943
975
|
data = dict(foo='bar', password='secret')
|
|
944
976
|
response.request = httpx.Request('POST', '/', json=data)
|
|
945
|
-
client.client.
|
|
977
|
+
client.client.send.return_value = response
|
|
946
978
|
await client.post('/', json=data, quiet=True)
|
|
947
|
-
client.logger.bind.assert_called_once_with(
|
|
979
|
+
client.logger.bind.assert_called_once_with(
|
|
980
|
+
method='POST',
|
|
981
|
+
url='http://lol/',
|
|
982
|
+
)
|
|
948
983
|
log = client.logger.bind.return_value
|
|
949
984
|
log.debug.assert_called_once_with(
|
|
950
985
|
'request',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|