httpware 0.11.0__tar.gz → 0.12.0__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.
Files changed (25) hide show
  1. {httpware-0.11.0 → httpware-0.12.0}/PKG-INFO +1 -1
  2. {httpware-0.11.0 → httpware-0.12.0}/pyproject.toml +1 -1
  3. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/client.py +486 -6
  4. {httpware-0.11.0 → httpware-0.12.0}/README.md +0 -0
  5. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/__init__.py +0 -0
  6. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/_internal/__init__.py +0 -0
  7. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/_internal/exception_mapping.py +0 -0
  8. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/_internal/import_checker.py +0 -0
  9. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/_internal/observability.py +0 -0
  10. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/_internal/redaction.py +0 -0
  11. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/_internal/status.py +0 -0
  12. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/decoders/__init__.py +0 -0
  13. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/decoders/msgspec.py +0 -0
  14. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/decoders/pydantic.py +0 -0
  15. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/errors.py +0 -0
  16. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/__init__.py +0 -0
  17. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/chain.py +0 -0
  18. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/__init__.py +0 -0
  19. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/_backoff.py +0 -0
  20. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/budget.py +0 -0
  21. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/bulkhead.py +0 -0
  22. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/circuit_breaker.py +0 -0
  23. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/retry.py +0 -0
  24. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/middleware/resilience/timeout.py +0 -0
  25. {httpware-0.11.0 → httpware-0.12.0}/src/httpware/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpware
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: Resilience-first async HTTP client framework for Python
5
5
  Keywords: http,async,client,resilience,retry,circuit-breaker,middleware,httpx,pydantic
6
6
  Author: Artur Shiriev
@@ -26,7 +26,7 @@ classifiers = [
26
26
  "Topic :: Internet :: WWW/HTTP",
27
27
  "Framework :: AsyncIO",
28
28
  ]
29
- version = "0.11.0"
29
+ version = "0.12.0"
30
30
  dependencies = [
31
31
  "httpx2>=2.0.0,<3.0",
32
32
  ]
@@ -228,7 +228,7 @@ class AsyncClient:
228
228
  """Delegate request construction to the wrapped httpx2.AsyncClient."""
229
229
  return self._httpx2_client.build_request(method, url, **kwargs)
230
230
 
231
- async def _request_with_body( # noqa: PLR0913, C901 — mirrors httpx2 per-method signatures; kwargs-forwarding complexity is structural
231
+ def _prepare_request( # noqa: PLR0913, C901 — mirrors httpx2 per-method signatures; kwargs-forwarding complexity is structural
232
232
  self,
233
233
  method: str,
234
234
  url: str,
@@ -242,8 +242,7 @@ class AsyncClient:
242
242
  content: typing.Any | None = None,
243
243
  data: typing.Any | None = None,
244
244
  files: typing.Any | None = None,
245
- response_model: type[T] | None = None,
246
- ) -> httpx2.Response | T:
245
+ ) -> httpx2.Request:
247
246
  kwargs: dict[str, typing.Any] = {}
248
247
  if params is not None:
249
248
  kwargs["params"] = params
@@ -266,8 +265,70 @@ class AsyncClient:
266
265
  request = self._httpx2_client.build_request(method, url, **kwargs)
267
266
  if _is_streaming_body_async(content) or _is_streaming_body_async(data) or _is_streaming_body_async(files):
268
267
  request.extensions[STREAMING_BODY_MARKER] = True
268
+ return request
269
+
270
+ async def _request_with_body( # noqa: PLR0913 — mirrors httpx2 per-method signatures
271
+ self,
272
+ method: str,
273
+ url: str,
274
+ *,
275
+ params: typing.Any | None = None,
276
+ headers: typing.Any | None = None,
277
+ cookies: typing.Any | None = None,
278
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
279
+ extensions: typing.Any | None = None,
280
+ json: typing.Any | None = None,
281
+ content: typing.Any | None = None,
282
+ data: typing.Any | None = None,
283
+ files: typing.Any | None = None,
284
+ response_model: type[T] | None = None,
285
+ ) -> httpx2.Response | T:
286
+ request = self._prepare_request(
287
+ method,
288
+ url,
289
+ params=params,
290
+ headers=headers,
291
+ cookies=cookies,
292
+ timeout=timeout,
293
+ extensions=extensions,
294
+ json=json,
295
+ content=content,
296
+ data=data,
297
+ files=files,
298
+ )
269
299
  return await self.send(request, response_model=response_model)
270
300
 
301
+ async def _request_with_body_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
302
+ self,
303
+ method: str,
304
+ url: str,
305
+ *,
306
+ params: typing.Any | None = None,
307
+ headers: typing.Any | None = None,
308
+ cookies: typing.Any | None = None,
309
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
310
+ extensions: typing.Any | None = None,
311
+ json: typing.Any | None = None,
312
+ content: typing.Any | None = None,
313
+ data: typing.Any | None = None,
314
+ files: typing.Any | None = None,
315
+ response_model: type[T],
316
+ ) -> tuple[httpx2.Response, T]:
317
+ request = self._prepare_request(
318
+ method,
319
+ url,
320
+ params=params,
321
+ headers=headers,
322
+ cookies=cookies,
323
+ timeout=timeout,
324
+ extensions=extensions,
325
+ json=json,
326
+ content=content,
327
+ data=data,
328
+ files=files,
329
+ )
330
+ return await self.send_with_response(request, response_model=response_model)
331
+
271
332
  @typing.overload
272
333
  async def get(
273
334
  self,
@@ -317,6 +378,29 @@ class AsyncClient:
317
378
  response_model=response_model,
318
379
  )
319
380
 
381
+ async def get_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
382
+ self,
383
+ url: str,
384
+ *,
385
+ params: typing.Any | None = None,
386
+ headers: typing.Any | None = None,
387
+ cookies: typing.Any | None = None,
388
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
389
+ extensions: typing.Any | None = None,
390
+ response_model: type[T],
391
+ ) -> tuple[httpx2.Response, T]:
392
+ """Send a GET request; return (response, decoded body)."""
393
+ return await self._request_with_body_with_response(
394
+ "GET",
395
+ url,
396
+ params=params,
397
+ headers=headers,
398
+ cookies=cookies,
399
+ timeout=timeout,
400
+ extensions=extensions,
401
+ response_model=response_model,
402
+ )
403
+
320
404
  @typing.overload
321
405
  async def post(
322
406
  self,
@@ -382,6 +466,37 @@ class AsyncClient:
382
466
  response_model=response_model,
383
467
  )
384
468
 
469
+ async def post_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
470
+ self,
471
+ url: str,
472
+ *,
473
+ params: typing.Any | None = None,
474
+ headers: typing.Any | None = None,
475
+ cookies: typing.Any | None = None,
476
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
477
+ extensions: typing.Any | None = None,
478
+ json: typing.Any | None = None,
479
+ content: typing.Any | None = None,
480
+ data: typing.Any | None = None,
481
+ files: typing.Any | None = None,
482
+ response_model: type[T],
483
+ ) -> tuple[httpx2.Response, T]:
484
+ """Send a POST request; return (response, decoded body)."""
485
+ return await self._request_with_body_with_response(
486
+ "POST",
487
+ url,
488
+ params=params,
489
+ headers=headers,
490
+ cookies=cookies,
491
+ timeout=timeout,
492
+ extensions=extensions,
493
+ json=json,
494
+ content=content,
495
+ data=data,
496
+ files=files,
497
+ response_model=response_model,
498
+ )
499
+
385
500
  @typing.overload
386
501
  async def put(
387
502
  self,
@@ -447,6 +562,37 @@ class AsyncClient:
447
562
  response_model=response_model,
448
563
  )
449
564
 
565
+ async def put_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
566
+ self,
567
+ url: str,
568
+ *,
569
+ params: typing.Any | None = None,
570
+ headers: typing.Any | None = None,
571
+ cookies: typing.Any | None = None,
572
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
573
+ extensions: typing.Any | None = None,
574
+ json: typing.Any | None = None,
575
+ content: typing.Any | None = None,
576
+ data: typing.Any | None = None,
577
+ files: typing.Any | None = None,
578
+ response_model: type[T],
579
+ ) -> tuple[httpx2.Response, T]:
580
+ """Send a PUT request; return (response, decoded body)."""
581
+ return await self._request_with_body_with_response(
582
+ "PUT",
583
+ url,
584
+ params=params,
585
+ headers=headers,
586
+ cookies=cookies,
587
+ timeout=timeout,
588
+ extensions=extensions,
589
+ json=json,
590
+ content=content,
591
+ data=data,
592
+ files=files,
593
+ response_model=response_model,
594
+ )
595
+
450
596
  @typing.overload
451
597
  async def patch(
452
598
  self,
@@ -512,6 +658,37 @@ class AsyncClient:
512
658
  response_model=response_model,
513
659
  )
514
660
 
661
+ async def patch_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
662
+ self,
663
+ url: str,
664
+ *,
665
+ params: typing.Any | None = None,
666
+ headers: typing.Any | None = None,
667
+ cookies: typing.Any | None = None,
668
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
669
+ extensions: typing.Any | None = None,
670
+ json: typing.Any | None = None,
671
+ content: typing.Any | None = None,
672
+ data: typing.Any | None = None,
673
+ files: typing.Any | None = None,
674
+ response_model: type[T],
675
+ ) -> tuple[httpx2.Response, T]:
676
+ """Send a PATCH request; return (response, decoded body)."""
677
+ return await self._request_with_body_with_response(
678
+ "PATCH",
679
+ url,
680
+ params=params,
681
+ headers=headers,
682
+ cookies=cookies,
683
+ timeout=timeout,
684
+ extensions=extensions,
685
+ json=json,
686
+ content=content,
687
+ data=data,
688
+ files=files,
689
+ response_model=response_model,
690
+ )
691
+
515
692
  @typing.overload
516
693
  async def delete(
517
694
  self,
@@ -577,6 +754,37 @@ class AsyncClient:
577
754
  response_model=response_model,
578
755
  )
579
756
 
757
+ async def delete_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
758
+ self,
759
+ url: str,
760
+ *,
761
+ params: typing.Any | None = None,
762
+ headers: typing.Any | None = None,
763
+ cookies: typing.Any | None = None,
764
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
765
+ extensions: typing.Any | None = None,
766
+ json: typing.Any | None = None,
767
+ content: typing.Any | None = None,
768
+ data: typing.Any | None = None,
769
+ files: typing.Any | None = None,
770
+ response_model: type[T],
771
+ ) -> tuple[httpx2.Response, T]:
772
+ """Send a DELETE request; return (response, decoded body)."""
773
+ return await self._request_with_body_with_response(
774
+ "DELETE",
775
+ url,
776
+ params=params,
777
+ headers=headers,
778
+ cookies=cookies,
779
+ timeout=timeout,
780
+ extensions=extensions,
781
+ json=json,
782
+ content=content,
783
+ data=data,
784
+ files=files,
785
+ response_model=response_model,
786
+ )
787
+
580
788
  @typing.overload
581
789
  async def head(
582
790
  self,
@@ -743,6 +951,38 @@ class AsyncClient:
743
951
  response_model=response_model,
744
952
  )
745
953
 
954
+ async def request_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
955
+ self,
956
+ method: str,
957
+ url: str,
958
+ *,
959
+ params: typing.Any | None = None,
960
+ headers: typing.Any | None = None,
961
+ cookies: typing.Any | None = None,
962
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
963
+ extensions: typing.Any | None = None,
964
+ json: typing.Any | None = None,
965
+ content: typing.Any | None = None,
966
+ data: typing.Any | None = None,
967
+ files: typing.Any | None = None,
968
+ response_model: type[T],
969
+ ) -> tuple[httpx2.Response, T]:
970
+ """Send a request with an explicit method; return (response, decoded body)."""
971
+ return await self._request_with_body_with_response(
972
+ method,
973
+ url,
974
+ params=params,
975
+ headers=headers,
976
+ cookies=cookies,
977
+ timeout=timeout,
978
+ extensions=extensions,
979
+ json=json,
980
+ content=content,
981
+ data=data,
982
+ files=files,
983
+ response_model=response_model,
984
+ )
985
+
746
986
  @contextlib.asynccontextmanager
747
987
  async def stream( # noqa: PLR0913, C901 — mirrors httpx2 per-method signatures; kwargs-forwarding complexity is structural
748
988
  self,
@@ -1003,7 +1243,7 @@ class Client:
1003
1243
  """Delegate request construction to the wrapped httpx2.Client."""
1004
1244
  return self._httpx2_client.build_request(method, url, **kwargs)
1005
1245
 
1006
- def _request_with_body( # noqa: PLR0913, C901 — mirrors httpx2 per-method signatures; kwargs-forwarding complexity is structural
1246
+ def _prepare_request( # noqa: PLR0913, C901 — mirrors httpx2 per-method signatures; kwargs-forwarding complexity is structural
1007
1247
  self,
1008
1248
  method: str,
1009
1249
  url: str,
@@ -1017,8 +1257,7 @@ class Client:
1017
1257
  content: typing.Any | None = None,
1018
1258
  data: typing.Any | None = None,
1019
1259
  files: typing.Any | None = None,
1020
- response_model: type[T] | None = None,
1021
- ) -> httpx2.Response | T:
1260
+ ) -> httpx2.Request:
1022
1261
  kwargs: dict[str, typing.Any] = {}
1023
1262
  if params is not None:
1024
1263
  kwargs["params"] = params
@@ -1041,8 +1280,70 @@ class Client:
1041
1280
  request = self._httpx2_client.build_request(method, url, **kwargs)
1042
1281
  if _is_streaming_body_sync(content) or _is_streaming_body_sync(data) or _is_streaming_body_sync(files):
1043
1282
  request.extensions[STREAMING_BODY_MARKER] = True
1283
+ return request
1284
+
1285
+ def _request_with_body( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1286
+ self,
1287
+ method: str,
1288
+ url: str,
1289
+ *,
1290
+ params: typing.Any | None = None,
1291
+ headers: typing.Any | None = None,
1292
+ cookies: typing.Any | None = None,
1293
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1294
+ extensions: typing.Any | None = None,
1295
+ json: typing.Any | None = None,
1296
+ content: typing.Any | None = None,
1297
+ data: typing.Any | None = None,
1298
+ files: typing.Any | None = None,
1299
+ response_model: type[T] | None = None,
1300
+ ) -> httpx2.Response | T:
1301
+ request = self._prepare_request(
1302
+ method,
1303
+ url,
1304
+ params=params,
1305
+ headers=headers,
1306
+ cookies=cookies,
1307
+ timeout=timeout,
1308
+ extensions=extensions,
1309
+ json=json,
1310
+ content=content,
1311
+ data=data,
1312
+ files=files,
1313
+ )
1044
1314
  return self.send(request, response_model=response_model)
1045
1315
 
1316
+ def _request_with_body_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1317
+ self,
1318
+ method: str,
1319
+ url: str,
1320
+ *,
1321
+ params: typing.Any | None = None,
1322
+ headers: typing.Any | None = None,
1323
+ cookies: typing.Any | None = None,
1324
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1325
+ extensions: typing.Any | None = None,
1326
+ json: typing.Any | None = None,
1327
+ content: typing.Any | None = None,
1328
+ data: typing.Any | None = None,
1329
+ files: typing.Any | None = None,
1330
+ response_model: type[T],
1331
+ ) -> tuple[httpx2.Response, T]:
1332
+ request = self._prepare_request(
1333
+ method,
1334
+ url,
1335
+ params=params,
1336
+ headers=headers,
1337
+ cookies=cookies,
1338
+ timeout=timeout,
1339
+ extensions=extensions,
1340
+ json=json,
1341
+ content=content,
1342
+ data=data,
1343
+ files=files,
1344
+ )
1345
+ return self.send_with_response(request, response_model=response_model)
1346
+
1046
1347
  @typing.overload
1047
1348
  def get(
1048
1349
  self,
@@ -1092,6 +1393,29 @@ class Client:
1092
1393
  response_model=response_model,
1093
1394
  )
1094
1395
 
1396
+ def get_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1397
+ self,
1398
+ url: str,
1399
+ *,
1400
+ params: typing.Any | None = None,
1401
+ headers: typing.Any | None = None,
1402
+ cookies: typing.Any | None = None,
1403
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1404
+ extensions: typing.Any | None = None,
1405
+ response_model: type[T],
1406
+ ) -> tuple[httpx2.Response, T]:
1407
+ """Send a GET request; return (response, decoded body)."""
1408
+ return self._request_with_body_with_response(
1409
+ "GET",
1410
+ url,
1411
+ params=params,
1412
+ headers=headers,
1413
+ cookies=cookies,
1414
+ timeout=timeout,
1415
+ extensions=extensions,
1416
+ response_model=response_model,
1417
+ )
1418
+
1095
1419
  @typing.overload
1096
1420
  def post(
1097
1421
  self,
@@ -1157,6 +1481,37 @@ class Client:
1157
1481
  response_model=response_model,
1158
1482
  )
1159
1483
 
1484
+ def post_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1485
+ self,
1486
+ url: str,
1487
+ *,
1488
+ params: typing.Any | None = None,
1489
+ headers: typing.Any | None = None,
1490
+ cookies: typing.Any | None = None,
1491
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1492
+ extensions: typing.Any | None = None,
1493
+ json: typing.Any | None = None,
1494
+ content: typing.Any | None = None,
1495
+ data: typing.Any | None = None,
1496
+ files: typing.Any | None = None,
1497
+ response_model: type[T],
1498
+ ) -> tuple[httpx2.Response, T]:
1499
+ """Send a POST request; return (response, decoded body)."""
1500
+ return self._request_with_body_with_response(
1501
+ "POST",
1502
+ url,
1503
+ params=params,
1504
+ headers=headers,
1505
+ cookies=cookies,
1506
+ timeout=timeout,
1507
+ extensions=extensions,
1508
+ json=json,
1509
+ content=content,
1510
+ data=data,
1511
+ files=files,
1512
+ response_model=response_model,
1513
+ )
1514
+
1160
1515
  @typing.overload
1161
1516
  def put(
1162
1517
  self,
@@ -1222,6 +1577,37 @@ class Client:
1222
1577
  response_model=response_model,
1223
1578
  )
1224
1579
 
1580
+ def put_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1581
+ self,
1582
+ url: str,
1583
+ *,
1584
+ params: typing.Any | None = None,
1585
+ headers: typing.Any | None = None,
1586
+ cookies: typing.Any | None = None,
1587
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1588
+ extensions: typing.Any | None = None,
1589
+ json: typing.Any | None = None,
1590
+ content: typing.Any | None = None,
1591
+ data: typing.Any | None = None,
1592
+ files: typing.Any | None = None,
1593
+ response_model: type[T],
1594
+ ) -> tuple[httpx2.Response, T]:
1595
+ """Send a PUT request; return (response, decoded body)."""
1596
+ return self._request_with_body_with_response(
1597
+ "PUT",
1598
+ url,
1599
+ params=params,
1600
+ headers=headers,
1601
+ cookies=cookies,
1602
+ timeout=timeout,
1603
+ extensions=extensions,
1604
+ json=json,
1605
+ content=content,
1606
+ data=data,
1607
+ files=files,
1608
+ response_model=response_model,
1609
+ )
1610
+
1225
1611
  @typing.overload
1226
1612
  def patch(
1227
1613
  self,
@@ -1287,6 +1673,37 @@ class Client:
1287
1673
  response_model=response_model,
1288
1674
  )
1289
1675
 
1676
+ def patch_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1677
+ self,
1678
+ url: str,
1679
+ *,
1680
+ params: typing.Any | None = None,
1681
+ headers: typing.Any | None = None,
1682
+ cookies: typing.Any | None = None,
1683
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1684
+ extensions: typing.Any | None = None,
1685
+ json: typing.Any | None = None,
1686
+ content: typing.Any | None = None,
1687
+ data: typing.Any | None = None,
1688
+ files: typing.Any | None = None,
1689
+ response_model: type[T],
1690
+ ) -> tuple[httpx2.Response, T]:
1691
+ """Send a PATCH request; return (response, decoded body)."""
1692
+ return self._request_with_body_with_response(
1693
+ "PATCH",
1694
+ url,
1695
+ params=params,
1696
+ headers=headers,
1697
+ cookies=cookies,
1698
+ timeout=timeout,
1699
+ extensions=extensions,
1700
+ json=json,
1701
+ content=content,
1702
+ data=data,
1703
+ files=files,
1704
+ response_model=response_model,
1705
+ )
1706
+
1290
1707
  @typing.overload
1291
1708
  def delete(
1292
1709
  self,
@@ -1352,6 +1769,37 @@ class Client:
1352
1769
  response_model=response_model,
1353
1770
  )
1354
1771
 
1772
+ def delete_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1773
+ self,
1774
+ url: str,
1775
+ *,
1776
+ params: typing.Any | None = None,
1777
+ headers: typing.Any | None = None,
1778
+ cookies: typing.Any | None = None,
1779
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1780
+ extensions: typing.Any | None = None,
1781
+ json: typing.Any | None = None,
1782
+ content: typing.Any | None = None,
1783
+ data: typing.Any | None = None,
1784
+ files: typing.Any | None = None,
1785
+ response_model: type[T],
1786
+ ) -> tuple[httpx2.Response, T]:
1787
+ """Send a DELETE request; return (response, decoded body)."""
1788
+ return self._request_with_body_with_response(
1789
+ "DELETE",
1790
+ url,
1791
+ params=params,
1792
+ headers=headers,
1793
+ cookies=cookies,
1794
+ timeout=timeout,
1795
+ extensions=extensions,
1796
+ json=json,
1797
+ content=content,
1798
+ data=data,
1799
+ files=files,
1800
+ response_model=response_model,
1801
+ )
1802
+
1355
1803
  @typing.overload
1356
1804
  def head(
1357
1805
  self,
@@ -1518,6 +1966,38 @@ class Client:
1518
1966
  response_model=response_model,
1519
1967
  )
1520
1968
 
1969
+ def request_with_response( # noqa: PLR0913 — mirrors httpx2 per-method signatures
1970
+ self,
1971
+ method: str,
1972
+ url: str,
1973
+ *,
1974
+ params: typing.Any | None = None,
1975
+ headers: typing.Any | None = None,
1976
+ cookies: typing.Any | None = None,
1977
+ timeout: typing.Any = httpx2.USE_CLIENT_DEFAULT,
1978
+ extensions: typing.Any | None = None,
1979
+ json: typing.Any | None = None,
1980
+ content: typing.Any | None = None,
1981
+ data: typing.Any | None = None,
1982
+ files: typing.Any | None = None,
1983
+ response_model: type[T],
1984
+ ) -> tuple[httpx2.Response, T]:
1985
+ """Send a request with an explicit method; return (response, decoded body)."""
1986
+ return self._request_with_body_with_response(
1987
+ method,
1988
+ url,
1989
+ params=params,
1990
+ headers=headers,
1991
+ cookies=cookies,
1992
+ timeout=timeout,
1993
+ extensions=extensions,
1994
+ json=json,
1995
+ content=content,
1996
+ data=data,
1997
+ files=files,
1998
+ response_model=response_model,
1999
+ )
2000
+
1521
2001
  @contextlib.contextmanager
1522
2002
  def stream( # noqa: PLR0913, C901 — mirrors httpx2 per-method signatures; kwargs-forwarding complexity is structural
1523
2003
  self,
File without changes