cli2 3.3.26__tar.gz → 3.3.30__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.26/cli2.egg-info → cli2-3.3.30}/PKG-INFO +1 -1
- {cli2-3.3.26 → cli2-3.3.30}/cli2/client.py +116 -59
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_client.py +70 -38
- {cli2-3.3.26 → cli2-3.3.30/cli2.egg-info}/PKG-INFO +1 -1
- {cli2-3.3.26 → cli2-3.3.30}/setup.py +1 -1
- {cli2-3.3.26 → cli2-3.3.30}/MANIFEST.in +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/README.rst +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/classifiers.txt +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/__init__.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/argument.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/asyncio.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/cli.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/colors.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/command.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/configuration.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/decorators.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/display.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/entry_point.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/example_client.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/example_client_complex.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/example_nesting.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/example_obj.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/group.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/logging.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/node.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/overrides.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/sphinx.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/table.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_cli.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_command.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_configuration.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_decorators.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_display.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_entry_point.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_group.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_inject.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_node.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2/test_table.py +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2.egg-info/SOURCES.txt +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2.egg-info/dependency_links.txt +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2.egg-info/entry_points.txt +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2.egg-info/requires.txt +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/cli2.egg-info/top_level.txt +0 -0
- {cli2-3.3.26 → cli2-3.3.30}/setup.cfg +0 -0
|
@@ -1032,12 +1032,13 @@ class Handler:
|
|
|
1032
1032
|
self.tries = self.tries_default if tries is None else tries
|
|
1033
1033
|
self.backoff = self.backoff_default if backoff is None else backoff
|
|
1034
1034
|
|
|
1035
|
-
async def __call__(self, client, response, tries, mask):
|
|
1035
|
+
async def __call__(self, client, response, tries, mask, log):
|
|
1036
1036
|
if isinstance(response, Exception):
|
|
1037
1037
|
if tries >= self.tries:
|
|
1038
1038
|
raise response
|
|
1039
1039
|
# httpx session is rendered unusable after a TransportError
|
|
1040
1040
|
if isinstance(response, httpx.TransportError):
|
|
1041
|
+
log.warn('reconnect', error=repr(response))
|
|
1041
1042
|
await client.client_reset()
|
|
1042
1043
|
return
|
|
1043
1044
|
|
|
@@ -1053,13 +1054,25 @@ class Handler:
|
|
|
1053
1054
|
if tries >= self.tries:
|
|
1054
1055
|
raise RetriesExceededError(client, response, tries, mask)
|
|
1055
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
|
+
|
|
1056
1067
|
if response.status_code in self.retokens:
|
|
1057
1068
|
if tries:
|
|
1058
1069
|
# our authentication is just not working, no need to retry
|
|
1059
1070
|
raise TokenGetError(client, response, tries, mask)
|
|
1071
|
+
log.warn('retoken', **kwargs)
|
|
1060
1072
|
await client.token_reset()
|
|
1061
1073
|
|
|
1062
|
-
|
|
1074
|
+
log.info('retry', **kwargs)
|
|
1075
|
+
await asyncio.sleep(seconds)
|
|
1063
1076
|
|
|
1064
1077
|
|
|
1065
1078
|
class ClientError(Exception):
|
|
@@ -1084,27 +1097,31 @@ class ResponseError(ClientError):
|
|
|
1084
1097
|
:param exc: httpx.HTTPStatusError
|
|
1085
1098
|
"""
|
|
1086
1099
|
output = [msg]
|
|
1100
|
+
key, value = self.client.request_log_data(
|
|
1101
|
+
self.response.request,
|
|
1102
|
+
self.mask,
|
|
1103
|
+
)
|
|
1087
1104
|
request_msg = ' '.join([
|
|
1088
|
-
self.response.request.method,
|
|
1105
|
+
str(self.response.request.method),
|
|
1089
1106
|
str(self.response.request.url),
|
|
1090
1107
|
])
|
|
1091
1108
|
|
|
1092
1109
|
output.append(
|
|
1093
1110
|
f'{colors.reset}{colors.bold}{request_msg}{colors.reset}',
|
|
1094
1111
|
)
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
self.mask,
|
|
1098
|
-
)
|
|
1099
|
-
if request:
|
|
1100
|
-
output.append(display.render(request))
|
|
1112
|
+
if value:
|
|
1113
|
+
output.append(display.render(value))
|
|
1101
1114
|
|
|
1115
|
+
key, value = self.client.response_log_data(self.response, self.mask)
|
|
1102
1116
|
output.append(
|
|
1103
|
-
|
|
1117
|
+
''.join([
|
|
1118
|
+
colors.bold,
|
|
1119
|
+
f'HTTP {self.response.status_code}',
|
|
1120
|
+
colors.reset,
|
|
1121
|
+
])
|
|
1104
1122
|
)
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
output.append(display.render(response))
|
|
1123
|
+
if value:
|
|
1124
|
+
output.append(display.render(value))
|
|
1108
1125
|
|
|
1109
1126
|
return '\n'.join(output)
|
|
1110
1127
|
|
|
@@ -1222,7 +1239,10 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1222
1239
|
"""
|
|
1223
1240
|
client = httpx.AsyncClient(*self._client_args, **self._client_kwargs)
|
|
1224
1241
|
if self.token and not self.token_getting:
|
|
1225
|
-
|
|
1242
|
+
try:
|
|
1243
|
+
self.client_token_apply(client)
|
|
1244
|
+
except NotImplementedError:
|
|
1245
|
+
pass
|
|
1226
1246
|
return client
|
|
1227
1247
|
|
|
1228
1248
|
@client.setter
|
|
@@ -1233,27 +1253,34 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1233
1253
|
def client(self):
|
|
1234
1254
|
self._client = None
|
|
1235
1255
|
|
|
1236
|
-
async def
|
|
1237
|
-
|
|
1256
|
+
async def send(self, request, handler, mask, retries=True, semaphore=None,
|
|
1257
|
+
log=None, auth=None, follow_redirects=None):
|
|
1238
1258
|
"""
|
|
1239
1259
|
Internal request method
|
|
1240
1260
|
"""
|
|
1241
1261
|
semaphore = semaphore or self.semaphore
|
|
1242
1262
|
tries = 0
|
|
1243
1263
|
|
|
1264
|
+
async def _send():
|
|
1265
|
+
return await self.client.send(
|
|
1266
|
+
request,
|
|
1267
|
+
auth=auth,
|
|
1268
|
+
follow_redirects=follow_redirects,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1244
1271
|
async def _request():
|
|
1245
1272
|
if semaphore:
|
|
1246
1273
|
async with semaphore:
|
|
1247
|
-
return await
|
|
1248
|
-
return await
|
|
1274
|
+
return await _send()
|
|
1275
|
+
return await _send()
|
|
1249
1276
|
|
|
1250
1277
|
while retries or tries > 1:
|
|
1251
1278
|
try:
|
|
1252
1279
|
response = await _request()
|
|
1253
1280
|
except Exception as exc:
|
|
1254
|
-
await handler(self, exc, tries, mask)
|
|
1281
|
+
await handler(self, exc, tries, mask, log)
|
|
1255
1282
|
else:
|
|
1256
|
-
if response := await handler(self, response, tries, mask):
|
|
1283
|
+
if response := await handler(self, response, tries, mask, log):
|
|
1257
1284
|
return response
|
|
1258
1285
|
|
|
1259
1286
|
tries += 1
|
|
@@ -1280,7 +1307,7 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1280
1307
|
pass
|
|
1281
1308
|
self.token_getting = False
|
|
1282
1309
|
|
|
1283
|
-
|
|
1310
|
+
def client_token_apply(self, client):
|
|
1284
1311
|
"""
|
|
1285
1312
|
Actually provision self.client with self.token.
|
|
1286
1313
|
|
|
@@ -1310,21 +1337,36 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1310
1337
|
By default, this method does nothing. Implement it to your likings.
|
|
1311
1338
|
|
|
1312
1339
|
This method is supposed to return the token, but doesn't do anything
|
|
1313
|
-
with it by itself
|
|
1340
|
+
with it by itself.
|
|
1341
|
+
|
|
1342
|
+
You also need to implement the :py:meth:`client_token_apply` which is
|
|
1343
|
+
in charge of updating the actual httpx client object with the said
|
|
1344
|
+
token.
|
|
1314
1345
|
|
|
1315
1346
|
.. code-block::
|
|
1316
1347
|
|
|
1317
1348
|
async def token_get(self):
|
|
1318
1349
|
response = await self.post('/login', dict(...))
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1350
|
+
return response.json()['token']
|
|
1351
|
+
|
|
1352
|
+
def client_token_apply(self, client):
|
|
1353
|
+
client.headers['X-ApiKey'] = self.token
|
|
1322
1354
|
"""
|
|
1323
1355
|
raise NotImplementedError()
|
|
1324
1356
|
|
|
1325
|
-
async def request(
|
|
1326
|
-
|
|
1327
|
-
|
|
1357
|
+
async def request(
|
|
1358
|
+
# base arguments
|
|
1359
|
+
self, method, url,
|
|
1360
|
+
*,
|
|
1361
|
+
# cli2 arguments
|
|
1362
|
+
handler=None, quiet=False, accepts=None, refuses=None, tries=None,
|
|
1363
|
+
backoff=None, retries=True, semaphore=None, mask=None,
|
|
1364
|
+
# httpx arguments
|
|
1365
|
+
content=None, data=None, files=None, json=None, params=None,
|
|
1366
|
+
headers=None, cookies=None, auth=httpx.USE_CLIENT_DEFAULT,
|
|
1367
|
+
follow_redirects=httpx.USE_CLIENT_DEFAULT,
|
|
1368
|
+
timeout=httpx.USE_CLIENT_DEFAULT, extensions=None,
|
|
1369
|
+
):
|
|
1328
1370
|
"""
|
|
1329
1371
|
Request method
|
|
1330
1372
|
|
|
@@ -1369,7 +1411,6 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1369
1411
|
the response, set to False if you want only 1 try.
|
|
1370
1412
|
:param accepts: Override for :py:attr:`Handler.accepts`
|
|
1371
1413
|
:param refuses: Override for :py:attr:`Handler.refuses`
|
|
1372
|
-
:param retries: Override for :py:attr:`Handler.retries`
|
|
1373
1414
|
:param tries: Override for :py:attr:`Handler.tries`
|
|
1374
1415
|
:param backoff: Override for :py:attr:`Handler.backoff`
|
|
1375
1416
|
:param semaphore: Override for :py:attr:`Client.semaphore`
|
|
@@ -1393,46 +1434,71 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1393
1434
|
else:
|
|
1394
1435
|
handler = self.handler
|
|
1395
1436
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1437
|
+
request = self.client.build_request(
|
|
1438
|
+
method=method,
|
|
1439
|
+
url=url,
|
|
1440
|
+
content=content,
|
|
1441
|
+
data=data,
|
|
1442
|
+
files=files,
|
|
1443
|
+
json=json,
|
|
1444
|
+
params=params,
|
|
1445
|
+
headers=headers,
|
|
1446
|
+
cookies=cookies,
|
|
1447
|
+
timeout=timeout,
|
|
1448
|
+
extensions=extensions,
|
|
1449
|
+
)
|
|
1399
1450
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1451
|
+
log = self.logger.bind(method=method, url=str(request.url))
|
|
1452
|
+
if not quiet or self.debug:
|
|
1453
|
+
key, value = self.request_log_data(request, mask, quiet)
|
|
1454
|
+
kwargs = dict()
|
|
1455
|
+
if value:
|
|
1456
|
+
kwargs[key] = value
|
|
1457
|
+
log.debug('request', **kwargs)
|
|
1458
|
+
|
|
1459
|
+
response = await self.send(
|
|
1460
|
+
request,
|
|
1403
1461
|
handler=handler,
|
|
1404
1462
|
retries=retries,
|
|
1405
1463
|
semaphore=semaphore,
|
|
1406
1464
|
mask=mask,
|
|
1407
|
-
|
|
1465
|
+
log=log,
|
|
1466
|
+
auth=auth,
|
|
1467
|
+
follow_redirects=follow_redirects,
|
|
1408
1468
|
)
|
|
1409
1469
|
|
|
1410
1470
|
_log = dict(status_code=response.status_code)
|
|
1411
1471
|
if not quiet or self.debug:
|
|
1412
|
-
self.response_log_data(response, mask
|
|
1472
|
+
key, value = self.response_log_data(response, mask)
|
|
1473
|
+
if value:
|
|
1474
|
+
_log[key] = value
|
|
1413
1475
|
|
|
1414
1476
|
log.info('response', **_log)
|
|
1415
1477
|
|
|
1416
1478
|
return response
|
|
1417
1479
|
|
|
1418
|
-
def response_log_data(self, response, mask
|
|
1480
|
+
def response_log_data(self, response, mask):
|
|
1419
1481
|
try:
|
|
1420
|
-
data =
|
|
1421
|
-
key = 'json'
|
|
1482
|
+
data = response.json()
|
|
1422
1483
|
except json.JSONDecodeError:
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
return
|
|
1484
|
+
if response.content:
|
|
1485
|
+
return 'content', self.mask_content(response.content, mask)
|
|
1486
|
+
else:
|
|
1487
|
+
if data:
|
|
1488
|
+
return 'json', self.mask_data(data, mask)
|
|
1489
|
+
return None, None
|
|
1429
1490
|
|
|
1430
1491
|
def request_log_data(self, request, mask, quiet=False):
|
|
1431
1492
|
content = request.content.decode()
|
|
1493
|
+
if not content:
|
|
1494
|
+
return None, None
|
|
1495
|
+
|
|
1432
1496
|
try:
|
|
1433
|
-
|
|
1497
|
+
data = json.loads(content)
|
|
1434
1498
|
except json.JSONDecodeError:
|
|
1435
1499
|
pass
|
|
1500
|
+
else:
|
|
1501
|
+
return 'json', self.mask_data(data, mask)
|
|
1436
1502
|
|
|
1437
1503
|
parsed = parse_qs(content)
|
|
1438
1504
|
if parsed:
|
|
@@ -1440,18 +1506,9 @@ class Client(metaclass=ClientMetaclass):
|
|
|
1440
1506
|
key: value[0] if len(value) == 1 else value
|
|
1441
1507
|
for key, value in parsed.items()
|
|
1442
1508
|
}
|
|
1443
|
-
return self.mask_data(data, mask)
|
|
1444
|
-
|
|
1445
|
-
return self.mask_content(content, mask)
|
|
1446
|
-
|
|
1447
|
-
def kwargs_log_data(self, kwargs, mask, quiet=False):
|
|
1448
|
-
_log = kwargs.copy()
|
|
1449
|
-
if 'content' in kwargs:
|
|
1450
|
-
_log['content'] = self.mask_content(kwargs['content'], mask)
|
|
1451
|
-
for key in ('json', 'data'):
|
|
1452
|
-
if key in kwargs:
|
|
1453
|
-
_log[key] = self.mask_data(kwargs[key], mask)
|
|
1454
|
-
return _log
|
|
1509
|
+
return 'data', self.mask_data(data, mask)
|
|
1510
|
+
|
|
1511
|
+
return 'content', self.mask_content(content, mask)
|
|
1455
1512
|
|
|
1456
1513
|
def mask_content(self, content, mask=None):
|
|
1457
1514
|
"""
|
|
@@ -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,41 @@ 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(
|
|
270
|
+
'reconnect',
|
|
271
|
+
error="TransportError('foo')",
|
|
272
|
+
)
|
|
260
273
|
assert not result
|
|
261
274
|
assert client.client_reset.await_count == 1
|
|
262
275
|
|
|
263
276
|
with pytest.raises(httpx.TransportError) as exc:
|
|
264
|
-
await handler(
|
|
277
|
+
await handler(
|
|
278
|
+
client, httpx.TransportError('x'), handler.tries + 1, [], log
|
|
279
|
+
)
|
|
265
280
|
|
|
266
281
|
response = httpx.Response(status_code=418)
|
|
267
282
|
assert not client.token_reset.await_count
|
|
268
|
-
|
|
283
|
+
log.warn.reset_mock()
|
|
284
|
+
result = await handler(client, response, 0, [], log)
|
|
285
|
+
log.warn.assert_called_once_with(
|
|
286
|
+
'retoken', status_code=418, tries=0, sleep=.0
|
|
287
|
+
)
|
|
269
288
|
assert not result
|
|
270
289
|
assert client.token_reset.await_count == 1
|
|
271
290
|
|
|
272
291
|
handler = cli2.Handler(accepts=[], refuses=[222])
|
|
273
292
|
|
|
274
293
|
response = httpx.Response(status_code=123)
|
|
275
|
-
result = await handler(client, response, 0, [])
|
|
294
|
+
result = await handler(client, response, 0, [], log)
|
|
276
295
|
assert result == response
|
|
277
296
|
|
|
278
297
|
|
|
@@ -285,16 +304,14 @@ async def test_retry(httpx_mock, client_class):
|
|
|
285
304
|
return self.return_token
|
|
286
305
|
|
|
287
306
|
def client_factory(self):
|
|
288
|
-
|
|
307
|
+
client = super().client_factory()
|
|
308
|
+
client.send = mock.Mock()
|
|
309
|
+
return client
|
|
289
310
|
|
|
290
|
-
client = Client(
|
|
291
|
-
retry_request=[500],
|
|
292
|
-
recreate_client=[400],
|
|
293
|
-
backoff=0.1,
|
|
294
|
-
)
|
|
311
|
+
client = Client()
|
|
295
312
|
|
|
296
313
|
current_client = client.client
|
|
297
|
-
client.client.
|
|
314
|
+
client.client.send.side_effect = [
|
|
298
315
|
_response(status_code=500),
|
|
299
316
|
_response(status_code=500),
|
|
300
317
|
_response(status_code=200),
|
|
@@ -774,7 +791,7 @@ def test_datetime_default_fmt():
|
|
|
774
791
|
)
|
|
775
792
|
async def test_mask_logs(key):
|
|
776
793
|
client = Client(mask=['scrt', 'password'])
|
|
777
|
-
client.client = mock.AsyncMock()
|
|
794
|
+
client.client.send = mock.AsyncMock()
|
|
778
795
|
|
|
779
796
|
client.logger = mock.Mock()
|
|
780
797
|
response = httpx.Response(
|
|
@@ -783,9 +800,12 @@ async def test_mask_logs(key):
|
|
|
783
800
|
)
|
|
784
801
|
data = dict(foo='bar', password='secret')
|
|
785
802
|
response.request = httpx.Request('POST', '/', **{key: data})
|
|
786
|
-
client.client.
|
|
803
|
+
client.client.send.return_value = response
|
|
787
804
|
await client.post('/', **{key: data})
|
|
788
|
-
client.logger.bind.assert_called_once_with(
|
|
805
|
+
client.logger.bind.assert_called_once_with(
|
|
806
|
+
method='POST',
|
|
807
|
+
url='http://lol/',
|
|
808
|
+
)
|
|
789
809
|
log = client.logger.bind.return_value
|
|
790
810
|
log.debug.assert_called_once_with(
|
|
791
811
|
'request',
|
|
@@ -822,7 +842,7 @@ async def test_mask_exceptions(client_class):
|
|
|
822
842
|
@pytest.mark.asyncio
|
|
823
843
|
async def test_request_mask():
|
|
824
844
|
client = Client(mask=['password'])
|
|
825
|
-
client.client = mock.AsyncMock()
|
|
845
|
+
client.client.send = mock.AsyncMock()
|
|
826
846
|
|
|
827
847
|
client.logger = mock.Mock()
|
|
828
848
|
response = httpx.Response(
|
|
@@ -831,9 +851,12 @@ async def test_request_mask():
|
|
|
831
851
|
)
|
|
832
852
|
data = dict(foo='bar', password='secret')
|
|
833
853
|
response.request = httpx.Request('POST', '/', json=data)
|
|
834
|
-
client.client.
|
|
854
|
+
client.client.send.return_value = response
|
|
835
855
|
await client.post('/', json=data, mask=['scrt'])
|
|
836
|
-
client.logger.bind.assert_called_once_with(
|
|
856
|
+
client.logger.bind.assert_called_once_with(
|
|
857
|
+
method='POST',
|
|
858
|
+
url='http://lol/'
|
|
859
|
+
)
|
|
837
860
|
log = client.logger.bind.return_value
|
|
838
861
|
log.debug.assert_called_once_with(
|
|
839
862
|
'request',
|
|
@@ -849,13 +872,16 @@ async def test_request_mask():
|
|
|
849
872
|
@pytest.mark.asyncio
|
|
850
873
|
async def test_log_content():
|
|
851
874
|
client = Client()
|
|
852
|
-
client.client = mock.AsyncMock()
|
|
875
|
+
client.client.send = mock.AsyncMock()
|
|
853
876
|
client.logger = mock.Mock()
|
|
854
877
|
response = httpx.Response(status_code=200, content='lol:]bar')
|
|
855
878
|
response.request = httpx.Request('POST', '/')
|
|
856
|
-
client.client.
|
|
879
|
+
client.client.send.return_value = response
|
|
857
880
|
await client.post('/', content='lol:]foo')
|
|
858
|
-
client.logger.bind.assert_called_once_with(
|
|
881
|
+
client.logger.bind.assert_called_once_with(
|
|
882
|
+
method='POST',
|
|
883
|
+
url='http://lol/'
|
|
884
|
+
)
|
|
859
885
|
log = client.logger.bind.return_value
|
|
860
886
|
log.debug.assert_called_once_with('request', content='lol:]foo')
|
|
861
887
|
log.info.assert_called_once_with(
|
|
@@ -866,14 +892,17 @@ async def test_log_content():
|
|
|
866
892
|
@pytest.mark.asyncio
|
|
867
893
|
async def test_log_quiet():
|
|
868
894
|
client = Client()
|
|
869
|
-
client.client = mock.AsyncMock()
|
|
895
|
+
client.client.send = mock.AsyncMock()
|
|
870
896
|
client.logger = mock.Mock()
|
|
871
897
|
response = httpx.Response(status_code=200, content='[1]')
|
|
872
898
|
response.request = httpx.Request('GET', '/')
|
|
873
|
-
client.client.
|
|
899
|
+
client.client.send.return_value = response
|
|
874
900
|
await client.get('/', json=[1], quiet=True)
|
|
875
901
|
log = client.logger.bind.return_value
|
|
876
|
-
client.logger.bind.assert_called_once_with(
|
|
902
|
+
client.logger.bind.assert_called_once_with(
|
|
903
|
+
method='GET',
|
|
904
|
+
url='http://lol/',
|
|
905
|
+
)
|
|
877
906
|
log = client.logger.bind.return_value
|
|
878
907
|
assert not log.debug.call_args_list
|
|
879
908
|
log.info.assert_called_once_with('response', status_code=200)
|
|
@@ -939,7 +968,7 @@ def test_id_value():
|
|
|
939
968
|
@pytest.mark.asyncio
|
|
940
969
|
async def test_debug():
|
|
941
970
|
client = Client(mask=['scrt', 'password'], debug=True)
|
|
942
|
-
client.client = mock.AsyncMock()
|
|
971
|
+
client.client.send = mock.AsyncMock()
|
|
943
972
|
|
|
944
973
|
client.logger = mock.Mock()
|
|
945
974
|
response = httpx.Response(
|
|
@@ -948,9 +977,12 @@ async def test_debug():
|
|
|
948
977
|
)
|
|
949
978
|
data = dict(foo='bar', password='secret')
|
|
950
979
|
response.request = httpx.Request('POST', '/', json=data)
|
|
951
|
-
client.client.
|
|
980
|
+
client.client.send.return_value = response
|
|
952
981
|
await client.post('/', json=data, quiet=True)
|
|
953
|
-
client.logger.bind.assert_called_once_with(
|
|
982
|
+
client.logger.bind.assert_called_once_with(
|
|
983
|
+
method='POST',
|
|
984
|
+
url='http://lol/',
|
|
985
|
+
)
|
|
954
986
|
log = client.logger.bind.return_value
|
|
955
987
|
log.debug.assert_called_once_with(
|
|
956
988
|
'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
|