isaacus 0.5.0__tar.gz → 0.6.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.
- isaacus-0.6.0/.release-please-manifest.json +3 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/CHANGELOG.md +24 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/PKG-INFO +1 -1
- {isaacus-0.5.0 → isaacus-0.6.0}/api.md +14 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/pyproject.toml +1 -1
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_base_client.py +175 -239
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_client.py +10 -4
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_models.py +2 -2
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_response.py +1 -1
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_utils.py +9 -1
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_version.py +1 -1
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/resources/__init__.py +14 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/resources/classifications/universal.py +3 -6
- isaacus-0.6.0/src/isaacus/resources/extractions/__init__.py +33 -0
- isaacus-0.6.0/src/isaacus/resources/extractions/extractions.py +102 -0
- isaacus-0.6.0/src/isaacus/resources/extractions/qa.py +258 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/resources/rerankings.py +3 -6
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/types/classifications/universal_create_params.py +1 -1
- isaacus-0.6.0/src/isaacus/types/extractions/__init__.py +6 -0
- isaacus-0.6.0/src/isaacus/types/extractions/answer_extraction.py +71 -0
- isaacus-0.6.0/src/isaacus/types/extractions/qa_create_params.py +64 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/types/reranking_create_params.py +1 -1
- isaacus-0.6.0/tests/api_resources/extractions/__init__.py +1 -0
- isaacus-0.6.0/tests/api_resources/extractions/test_qa.py +152 -0
- isaacus-0.5.0/.release-please-manifest.json +0 -3
- {isaacus-0.5.0 → isaacus-0.6.0}/.gitignore +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/CONTRIBUTING.md +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/LICENSE +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/README.md +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/SECURITY.md +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/bin/check-release-environment +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/bin/publish-pypi +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/examples/.keep +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/mypy.ini +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/noxfile.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/release-please-config.json +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/requirements-dev.lock +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/requirements.lock +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_compat.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_constants.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_exceptions.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_files.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_qs.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_resource.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_streaming.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_types.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_logs.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_proxy.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_reflection.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_streams.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_sync.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_transform.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/_utils/_typing.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/lib/.keep +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/py.typed +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/resources/classifications/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/resources/classifications/classifications.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/types/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/types/classifications/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/types/classifications/universal_classification.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/src/isaacus/types/reranking.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/api_resources/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/api_resources/classifications/__init__.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/api_resources/classifications/test_universal.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/api_resources/test_rerankings.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/conftest.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/sample_file.txt +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_client.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_deepcopy.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_extract_files.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_files.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_models.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_qs.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_required_args.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_response.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_streaming.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_transform.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_utils/test_proxy.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/test_utils/test_typing.py +0 -0
- {isaacus-0.5.0 → isaacus-0.6.0}/tests/utils.py +0 -0
@@ -1,5 +1,29 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.6.0 (2025-04-30)
|
4
|
+
|
5
|
+
Full Changelog: [v0.5.0...v0.6.0](https://github.com/isaacus-dev/isaacus-python/compare/v0.5.0...v0.6.0)
|
6
|
+
|
7
|
+
### Features
|
8
|
+
|
9
|
+
* **api:** introduced extractive QA ([7b9856c](https://github.com/isaacus-dev/isaacus-python/commit/7b9856c7a64fd4694d0fe8436934fa520faa38cc))
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* **pydantic v1:** more robust ModelField.annotation check ([40be0d5](https://github.com/isaacus-dev/isaacus-python/commit/40be0d5d7bb0c4d5187c0207e6470800e9827216))
|
15
|
+
|
16
|
+
|
17
|
+
### Chores
|
18
|
+
|
19
|
+
* broadly detect json family of content-type headers ([ef18419](https://github.com/isaacus-dev/isaacus-python/commit/ef18419dc26bba05aec8f5e29711bcc6fe329e9e))
|
20
|
+
* **ci:** add timeout thresholds for CI jobs ([f0438ce](https://github.com/isaacus-dev/isaacus-python/commit/f0438cebcfc587af81d967e610dc33ea5a53bb32))
|
21
|
+
* **ci:** only use depot for staging repos ([869c0ff](https://github.com/isaacus-dev/isaacus-python/commit/869c0ff5824ccfd63a4123a026530df11352db44))
|
22
|
+
* **internal:** codegen related update ([8860ae0](https://github.com/isaacus-dev/isaacus-python/commit/8860ae0393429d660038ce1c8d15020a42141979))
|
23
|
+
* **internal:** fix list file params ([6dc4e32](https://github.com/isaacus-dev/isaacus-python/commit/6dc4e32ab00e83d2307bfb729222f66f24a1f45f))
|
24
|
+
* **internal:** import reformatting ([57473e2](https://github.com/isaacus-dev/isaacus-python/commit/57473e25e03b551ab85b4d2ec484defdcc2de09d))
|
25
|
+
* **internal:** refactor retries to not use recursion ([513599c](https://github.com/isaacus-dev/isaacus-python/commit/513599ce261e2ec9a034715e20ec150025186255))
|
26
|
+
|
3
27
|
## 0.5.0 (2025-04-19)
|
4
28
|
|
5
29
|
Full Changelog: [v0.4.0...v0.5.0](https://github.com/isaacus-dev/isaacus-python/compare/v0.4.0...v0.5.0)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: isaacus
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: The official Python library for the isaacus API
|
5
5
|
Project-URL: Homepage, https://github.com/isaacus-dev/isaacus-python
|
6
6
|
Project-URL: Repository, https://github.com/isaacus-dev/isaacus-python
|
@@ -23,3 +23,17 @@ from isaacus.types import Reranking
|
|
23
23
|
Methods:
|
24
24
|
|
25
25
|
- <code title="post /rerankings">client.rerankings.<a href="./src/isaacus/resources/rerankings.py">create</a>(\*\*<a href="src/isaacus/types/reranking_create_params.py">params</a>) -> <a href="./src/isaacus/types/reranking.py">Reranking</a></code>
|
26
|
+
|
27
|
+
# Extractions
|
28
|
+
|
29
|
+
## Qa
|
30
|
+
|
31
|
+
Types:
|
32
|
+
|
33
|
+
```python
|
34
|
+
from isaacus.types.extractions import AnswerExtraction
|
35
|
+
```
|
36
|
+
|
37
|
+
Methods:
|
38
|
+
|
39
|
+
- <code title="post /extractions/qa">client.extractions.qa.<a href="./src/isaacus/resources/extractions/qa.py">create</a>(\*\*<a href="src/isaacus/types/extractions/qa_create_params.py">params</a>) -> <a href="./src/isaacus/types/extractions/answer_extraction.py">AnswerExtraction</a></code>
|
@@ -412,8 +412,7 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
412
412
|
headers = httpx.Headers(headers_dict)
|
413
413
|
|
414
414
|
idempotency_header = self._idempotency_header
|
415
|
-
if idempotency_header and options.
|
416
|
-
options.idempotency_key = options.idempotency_key or self._idempotency_key()
|
415
|
+
if idempotency_header and options.idempotency_key and idempotency_header not in headers:
|
417
416
|
headers[idempotency_header] = options.idempotency_key
|
418
417
|
|
419
418
|
# Don't set these headers if they were already set or removed by the caller. We check
|
@@ -878,7 +877,6 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
878
877
|
self,
|
879
878
|
cast_to: Type[ResponseT],
|
880
879
|
options: FinalRequestOptions,
|
881
|
-
remaining_retries: Optional[int] = None,
|
882
880
|
*,
|
883
881
|
stream: Literal[True],
|
884
882
|
stream_cls: Type[_StreamT],
|
@@ -889,7 +887,6 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
889
887
|
self,
|
890
888
|
cast_to: Type[ResponseT],
|
891
889
|
options: FinalRequestOptions,
|
892
|
-
remaining_retries: Optional[int] = None,
|
893
890
|
*,
|
894
891
|
stream: Literal[False] = False,
|
895
892
|
) -> ResponseT: ...
|
@@ -899,7 +896,6 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
899
896
|
self,
|
900
897
|
cast_to: Type[ResponseT],
|
901
898
|
options: FinalRequestOptions,
|
902
|
-
remaining_retries: Optional[int] = None,
|
903
899
|
*,
|
904
900
|
stream: bool = False,
|
905
901
|
stream_cls: Type[_StreamT] | None = None,
|
@@ -909,125 +905,109 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
909
905
|
self,
|
910
906
|
cast_to: Type[ResponseT],
|
911
907
|
options: FinalRequestOptions,
|
912
|
-
remaining_retries: Optional[int] = None,
|
913
908
|
*,
|
914
909
|
stream: bool = False,
|
915
910
|
stream_cls: type[_StreamT] | None = None,
|
916
911
|
) -> ResponseT | _StreamT:
|
917
|
-
|
918
|
-
retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
|
919
|
-
else:
|
920
|
-
retries_taken = 0
|
921
|
-
|
922
|
-
return self._request(
|
923
|
-
cast_to=cast_to,
|
924
|
-
options=options,
|
925
|
-
stream=stream,
|
926
|
-
stream_cls=stream_cls,
|
927
|
-
retries_taken=retries_taken,
|
928
|
-
)
|
912
|
+
cast_to = self._maybe_override_cast_to(cast_to, options)
|
929
913
|
|
930
|
-
def _request(
|
931
|
-
self,
|
932
|
-
*,
|
933
|
-
cast_to: Type[ResponseT],
|
934
|
-
options: FinalRequestOptions,
|
935
|
-
retries_taken: int,
|
936
|
-
stream: bool,
|
937
|
-
stream_cls: type[_StreamT] | None,
|
938
|
-
) -> ResponseT | _StreamT:
|
939
914
|
# create a copy of the options we were given so that if the
|
940
915
|
# options are mutated later & we then retry, the retries are
|
941
916
|
# given the original options
|
942
917
|
input_options = model_copy(options)
|
943
|
-
|
944
|
-
cast_to = self._maybe_override_cast_to(cast_to, options)
|
945
|
-
options = self._prepare_options(options)
|
946
|
-
|
947
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
948
|
-
request = self._build_request(options, retries_taken=retries_taken)
|
949
|
-
self._prepare_request(request)
|
950
|
-
|
951
|
-
if options.idempotency_key:
|
918
|
+
if input_options.idempotency_key is None and input_options.method.lower() != "get":
|
952
919
|
# ensure the idempotency key is reused between requests
|
953
|
-
input_options.idempotency_key =
|
920
|
+
input_options.idempotency_key = self._idempotency_key()
|
954
921
|
|
955
|
-
|
956
|
-
|
957
|
-
kwargs["auth"] = self.custom_auth
|
922
|
+
response: httpx.Response | None = None
|
923
|
+
max_retries = input_options.get_max_retries(self.max_retries)
|
958
924
|
|
959
|
-
|
925
|
+
retries_taken = 0
|
926
|
+
for retries_taken in range(max_retries + 1):
|
927
|
+
options = model_copy(input_options)
|
928
|
+
options = self._prepare_options(options)
|
960
929
|
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
stream=stream or self._should_stream_response_body(request=request),
|
965
|
-
**kwargs,
|
966
|
-
)
|
967
|
-
except httpx.TimeoutException as err:
|
968
|
-
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
930
|
+
remaining_retries = max_retries - retries_taken
|
931
|
+
request = self._build_request(options, retries_taken=retries_taken)
|
932
|
+
self._prepare_request(request)
|
969
933
|
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
cast_to,
|
974
|
-
retries_taken=retries_taken,
|
975
|
-
stream=stream,
|
976
|
-
stream_cls=stream_cls,
|
977
|
-
response_headers=None,
|
978
|
-
)
|
934
|
+
kwargs: HttpxSendArgs = {}
|
935
|
+
if self.custom_auth is not None:
|
936
|
+
kwargs["auth"] = self.custom_auth
|
979
937
|
|
980
|
-
log.debug("
|
981
|
-
raise APITimeoutError(request=request) from err
|
982
|
-
except Exception as err:
|
983
|
-
log.debug("Encountered Exception", exc_info=True)
|
938
|
+
log.debug("Sending HTTP Request: %s %s", request.method, request.url)
|
984
939
|
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
stream_cls=stream_cls,
|
992
|
-
response_headers=None,
|
940
|
+
response = None
|
941
|
+
try:
|
942
|
+
response = self._client.send(
|
943
|
+
request,
|
944
|
+
stream=stream or self._should_stream_response_body(request=request),
|
945
|
+
**kwargs,
|
993
946
|
)
|
947
|
+
except httpx.TimeoutException as err:
|
948
|
+
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
949
|
+
|
950
|
+
if remaining_retries > 0:
|
951
|
+
self._sleep_for_retry(
|
952
|
+
retries_taken=retries_taken,
|
953
|
+
max_retries=max_retries,
|
954
|
+
options=input_options,
|
955
|
+
response=None,
|
956
|
+
)
|
957
|
+
continue
|
958
|
+
|
959
|
+
log.debug("Raising timeout error")
|
960
|
+
raise APITimeoutError(request=request) from err
|
961
|
+
except Exception as err:
|
962
|
+
log.debug("Encountered Exception", exc_info=True)
|
963
|
+
|
964
|
+
if remaining_retries > 0:
|
965
|
+
self._sleep_for_retry(
|
966
|
+
retries_taken=retries_taken,
|
967
|
+
max_retries=max_retries,
|
968
|
+
options=input_options,
|
969
|
+
response=None,
|
970
|
+
)
|
971
|
+
continue
|
972
|
+
|
973
|
+
log.debug("Raising connection error")
|
974
|
+
raise APIConnectionError(request=request) from err
|
975
|
+
|
976
|
+
log.debug(
|
977
|
+
'HTTP Response: %s %s "%i %s" %s',
|
978
|
+
request.method,
|
979
|
+
request.url,
|
980
|
+
response.status_code,
|
981
|
+
response.reason_phrase,
|
982
|
+
response.headers,
|
983
|
+
)
|
994
984
|
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
985
|
+
try:
|
986
|
+
response.raise_for_status()
|
987
|
+
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
|
988
|
+
log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
|
989
|
+
|
990
|
+
if remaining_retries > 0 and self._should_retry(err.response):
|
991
|
+
err.response.close()
|
992
|
+
self._sleep_for_retry(
|
993
|
+
retries_taken=retries_taken,
|
994
|
+
max_retries=max_retries,
|
995
|
+
options=input_options,
|
996
|
+
response=response,
|
997
|
+
)
|
998
|
+
continue
|
1006
999
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
if remaining_retries > 0 and self._should_retry(err.response):
|
1013
|
-
err.response.close()
|
1014
|
-
return self._retry_request(
|
1015
|
-
input_options,
|
1016
|
-
cast_to,
|
1017
|
-
retries_taken=retries_taken,
|
1018
|
-
response_headers=err.response.headers,
|
1019
|
-
stream=stream,
|
1020
|
-
stream_cls=stream_cls,
|
1021
|
-
)
|
1000
|
+
# If the response is streamed then we need to explicitly read the response
|
1001
|
+
# to completion before attempting to access the response text.
|
1002
|
+
if not err.response.is_closed:
|
1003
|
+
err.response.read()
|
1022
1004
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
if not err.response.is_closed:
|
1026
|
-
err.response.read()
|
1005
|
+
log.debug("Re-raising status error")
|
1006
|
+
raise self._make_status_error_from_response(err.response) from None
|
1027
1007
|
|
1028
|
-
|
1029
|
-
raise self._make_status_error_from_response(err.response) from None
|
1008
|
+
break
|
1030
1009
|
|
1010
|
+
assert response is not None, "could not resolve response (should never happen)"
|
1031
1011
|
return self._process_response(
|
1032
1012
|
cast_to=cast_to,
|
1033
1013
|
options=options,
|
@@ -1037,37 +1017,20 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
1037
1017
|
retries_taken=retries_taken,
|
1038
1018
|
)
|
1039
1019
|
|
1040
|
-
def
|
1041
|
-
self,
|
1042
|
-
|
1043
|
-
|
1044
|
-
*,
|
1045
|
-
retries_taken: int,
|
1046
|
-
response_headers: httpx.Headers | None,
|
1047
|
-
stream: bool,
|
1048
|
-
stream_cls: type[_StreamT] | None,
|
1049
|
-
) -> ResponseT | _StreamT:
|
1050
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
1020
|
+
def _sleep_for_retry(
|
1021
|
+
self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
|
1022
|
+
) -> None:
|
1023
|
+
remaining_retries = max_retries - retries_taken
|
1051
1024
|
if remaining_retries == 1:
|
1052
1025
|
log.debug("1 retry left")
|
1053
1026
|
else:
|
1054
1027
|
log.debug("%i retries left", remaining_retries)
|
1055
1028
|
|
1056
|
-
timeout = self._calculate_retry_timeout(remaining_retries, options,
|
1029
|
+
timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
|
1057
1030
|
log.info("Retrying request to %s in %f seconds", options.url, timeout)
|
1058
1031
|
|
1059
|
-
# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
|
1060
|
-
# different thread if necessary.
|
1061
1032
|
time.sleep(timeout)
|
1062
1033
|
|
1063
|
-
return self._request(
|
1064
|
-
options=options,
|
1065
|
-
cast_to=cast_to,
|
1066
|
-
retries_taken=retries_taken + 1,
|
1067
|
-
stream=stream,
|
1068
|
-
stream_cls=stream_cls,
|
1069
|
-
)
|
1070
|
-
|
1071
1034
|
def _process_response(
|
1072
1035
|
self,
|
1073
1036
|
*,
|
@@ -1411,7 +1374,6 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1411
1374
|
options: FinalRequestOptions,
|
1412
1375
|
*,
|
1413
1376
|
stream: Literal[False] = False,
|
1414
|
-
remaining_retries: Optional[int] = None,
|
1415
1377
|
) -> ResponseT: ...
|
1416
1378
|
|
1417
1379
|
@overload
|
@@ -1422,7 +1384,6 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1422
1384
|
*,
|
1423
1385
|
stream: Literal[True],
|
1424
1386
|
stream_cls: type[_AsyncStreamT],
|
1425
|
-
remaining_retries: Optional[int] = None,
|
1426
1387
|
) -> _AsyncStreamT: ...
|
1427
1388
|
|
1428
1389
|
@overload
|
@@ -1433,7 +1394,6 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1433
1394
|
*,
|
1434
1395
|
stream: bool,
|
1435
1396
|
stream_cls: type[_AsyncStreamT] | None = None,
|
1436
|
-
remaining_retries: Optional[int] = None,
|
1437
1397
|
) -> ResponseT | _AsyncStreamT: ...
|
1438
1398
|
|
1439
1399
|
async def request(
|
@@ -1443,120 +1403,111 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1443
1403
|
*,
|
1444
1404
|
stream: bool = False,
|
1445
1405
|
stream_cls: type[_AsyncStreamT] | None = None,
|
1446
|
-
remaining_retries: Optional[int] = None,
|
1447
|
-
) -> ResponseT | _AsyncStreamT:
|
1448
|
-
if remaining_retries is not None:
|
1449
|
-
retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
|
1450
|
-
else:
|
1451
|
-
retries_taken = 0
|
1452
|
-
|
1453
|
-
return await self._request(
|
1454
|
-
cast_to=cast_to,
|
1455
|
-
options=options,
|
1456
|
-
stream=stream,
|
1457
|
-
stream_cls=stream_cls,
|
1458
|
-
retries_taken=retries_taken,
|
1459
|
-
)
|
1460
|
-
|
1461
|
-
async def _request(
|
1462
|
-
self,
|
1463
|
-
cast_to: Type[ResponseT],
|
1464
|
-
options: FinalRequestOptions,
|
1465
|
-
*,
|
1466
|
-
stream: bool,
|
1467
|
-
stream_cls: type[_AsyncStreamT] | None,
|
1468
|
-
retries_taken: int,
|
1469
1406
|
) -> ResponseT | _AsyncStreamT:
|
1470
1407
|
if self._platform is None:
|
1471
1408
|
# `get_platform` can make blocking IO calls so we
|
1472
1409
|
# execute it earlier while we are in an async context
|
1473
1410
|
self._platform = await asyncify(get_platform)()
|
1474
1411
|
|
1412
|
+
cast_to = self._maybe_override_cast_to(cast_to, options)
|
1413
|
+
|
1475
1414
|
# create a copy of the options we were given so that if the
|
1476
1415
|
# options are mutated later & we then retry, the retries are
|
1477
1416
|
# given the original options
|
1478
1417
|
input_options = model_copy(options)
|
1479
|
-
|
1480
|
-
cast_to = self._maybe_override_cast_to(cast_to, options)
|
1481
|
-
options = await self._prepare_options(options)
|
1482
|
-
|
1483
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
1484
|
-
request = self._build_request(options, retries_taken=retries_taken)
|
1485
|
-
await self._prepare_request(request)
|
1486
|
-
|
1487
|
-
if options.idempotency_key:
|
1418
|
+
if input_options.idempotency_key is None and input_options.method.lower() != "get":
|
1488
1419
|
# ensure the idempotency key is reused between requests
|
1489
|
-
input_options.idempotency_key =
|
1420
|
+
input_options.idempotency_key = self._idempotency_key()
|
1490
1421
|
|
1491
|
-
|
1492
|
-
|
1493
|
-
kwargs["auth"] = self.custom_auth
|
1422
|
+
response: httpx.Response | None = None
|
1423
|
+
max_retries = input_options.get_max_retries(self.max_retries)
|
1494
1424
|
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
**kwargs,
|
1500
|
-
)
|
1501
|
-
except httpx.TimeoutException as err:
|
1502
|
-
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
1425
|
+
retries_taken = 0
|
1426
|
+
for retries_taken in range(max_retries + 1):
|
1427
|
+
options = model_copy(input_options)
|
1428
|
+
options = await self._prepare_options(options)
|
1503
1429
|
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
cast_to,
|
1508
|
-
retries_taken=retries_taken,
|
1509
|
-
stream=stream,
|
1510
|
-
stream_cls=stream_cls,
|
1511
|
-
response_headers=None,
|
1512
|
-
)
|
1430
|
+
remaining_retries = max_retries - retries_taken
|
1431
|
+
request = self._build_request(options, retries_taken=retries_taken)
|
1432
|
+
await self._prepare_request(request)
|
1513
1433
|
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
log.debug("Encountered Exception", exc_info=True)
|
1434
|
+
kwargs: HttpxSendArgs = {}
|
1435
|
+
if self.custom_auth is not None:
|
1436
|
+
kwargs["auth"] = self.custom_auth
|
1518
1437
|
|
1519
|
-
|
1520
|
-
return await self._retry_request(
|
1521
|
-
input_options,
|
1522
|
-
cast_to,
|
1523
|
-
retries_taken=retries_taken,
|
1524
|
-
stream=stream,
|
1525
|
-
stream_cls=stream_cls,
|
1526
|
-
response_headers=None,
|
1527
|
-
)
|
1438
|
+
log.debug("Sending HTTP Request: %s %s", request.method, request.url)
|
1528
1439
|
|
1529
|
-
|
1530
|
-
|
1440
|
+
response = None
|
1441
|
+
try:
|
1442
|
+
response = await self._client.send(
|
1443
|
+
request,
|
1444
|
+
stream=stream or self._should_stream_response_body(request=request),
|
1445
|
+
**kwargs,
|
1446
|
+
)
|
1447
|
+
except httpx.TimeoutException as err:
|
1448
|
+
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
1449
|
+
|
1450
|
+
if remaining_retries > 0:
|
1451
|
+
await self._sleep_for_retry(
|
1452
|
+
retries_taken=retries_taken,
|
1453
|
+
max_retries=max_retries,
|
1454
|
+
options=input_options,
|
1455
|
+
response=None,
|
1456
|
+
)
|
1457
|
+
continue
|
1458
|
+
|
1459
|
+
log.debug("Raising timeout error")
|
1460
|
+
raise APITimeoutError(request=request) from err
|
1461
|
+
except Exception as err:
|
1462
|
+
log.debug("Encountered Exception", exc_info=True)
|
1463
|
+
|
1464
|
+
if remaining_retries > 0:
|
1465
|
+
await self._sleep_for_retry(
|
1466
|
+
retries_taken=retries_taken,
|
1467
|
+
max_retries=max_retries,
|
1468
|
+
options=input_options,
|
1469
|
+
response=None,
|
1470
|
+
)
|
1471
|
+
continue
|
1472
|
+
|
1473
|
+
log.debug("Raising connection error")
|
1474
|
+
raise APIConnectionError(request=request) from err
|
1475
|
+
|
1476
|
+
log.debug(
|
1477
|
+
'HTTP Response: %s %s "%i %s" %s',
|
1478
|
+
request.method,
|
1479
|
+
request.url,
|
1480
|
+
response.status_code,
|
1481
|
+
response.reason_phrase,
|
1482
|
+
response.headers,
|
1483
|
+
)
|
1531
1484
|
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1485
|
+
try:
|
1486
|
+
response.raise_for_status()
|
1487
|
+
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
|
1488
|
+
log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
|
1489
|
+
|
1490
|
+
if remaining_retries > 0 and self._should_retry(err.response):
|
1491
|
+
await err.response.aclose()
|
1492
|
+
await self._sleep_for_retry(
|
1493
|
+
retries_taken=retries_taken,
|
1494
|
+
max_retries=max_retries,
|
1495
|
+
options=input_options,
|
1496
|
+
response=response,
|
1497
|
+
)
|
1498
|
+
continue
|
1535
1499
|
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
if remaining_retries > 0 and self._should_retry(err.response):
|
1542
|
-
await err.response.aclose()
|
1543
|
-
return await self._retry_request(
|
1544
|
-
input_options,
|
1545
|
-
cast_to,
|
1546
|
-
retries_taken=retries_taken,
|
1547
|
-
response_headers=err.response.headers,
|
1548
|
-
stream=stream,
|
1549
|
-
stream_cls=stream_cls,
|
1550
|
-
)
|
1500
|
+
# If the response is streamed then we need to explicitly read the response
|
1501
|
+
# to completion before attempting to access the response text.
|
1502
|
+
if not err.response.is_closed:
|
1503
|
+
await err.response.aread()
|
1551
1504
|
|
1552
|
-
|
1553
|
-
|
1554
|
-
if not err.response.is_closed:
|
1555
|
-
await err.response.aread()
|
1505
|
+
log.debug("Re-raising status error")
|
1506
|
+
raise self._make_status_error_from_response(err.response) from None
|
1556
1507
|
|
1557
|
-
|
1558
|
-
raise self._make_status_error_from_response(err.response) from None
|
1508
|
+
break
|
1559
1509
|
|
1510
|
+
assert response is not None, "could not resolve response (should never happen)"
|
1560
1511
|
return await self._process_response(
|
1561
1512
|
cast_to=cast_to,
|
1562
1513
|
options=options,
|
@@ -1566,35 +1517,20 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1566
1517
|
retries_taken=retries_taken,
|
1567
1518
|
)
|
1568
1519
|
|
1569
|
-
async def
|
1570
|
-
self,
|
1571
|
-
|
1572
|
-
|
1573
|
-
*,
|
1574
|
-
retries_taken: int,
|
1575
|
-
response_headers: httpx.Headers | None,
|
1576
|
-
stream: bool,
|
1577
|
-
stream_cls: type[_AsyncStreamT] | None,
|
1578
|
-
) -> ResponseT | _AsyncStreamT:
|
1579
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
1520
|
+
async def _sleep_for_retry(
|
1521
|
+
self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
|
1522
|
+
) -> None:
|
1523
|
+
remaining_retries = max_retries - retries_taken
|
1580
1524
|
if remaining_retries == 1:
|
1581
1525
|
log.debug("1 retry left")
|
1582
1526
|
else:
|
1583
1527
|
log.debug("%i retries left", remaining_retries)
|
1584
1528
|
|
1585
|
-
timeout = self._calculate_retry_timeout(remaining_retries, options,
|
1529
|
+
timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
|
1586
1530
|
log.info("Retrying request to %s in %f seconds", options.url, timeout)
|
1587
1531
|
|
1588
1532
|
await anyio.sleep(timeout)
|
1589
1533
|
|
1590
|
-
return await self._request(
|
1591
|
-
options=options,
|
1592
|
-
cast_to=cast_to,
|
1593
|
-
retries_taken=retries_taken + 1,
|
1594
|
-
stream=stream,
|
1595
|
-
stream_cls=stream_cls,
|
1596
|
-
)
|
1597
|
-
|
1598
1534
|
async def _process_response(
|
1599
1535
|
self,
|
1600
1536
|
*,
|