Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 0.5.7 #81

Merged
merged 9 commits into from
Dec 6, 2023
Merged
21 changes: 21 additions & 0 deletions .github/workflows/lint-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: "Lint PR"

on:
pull_request_target:
types:
- opened
- edited
- synchronize

permissions:
pull-requests: read

jobs:
pr_title:
name: Validate PR title
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.repository == 'anthropics/anthropic-bedrock-python'
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.5.6"
".": "0.5.7"
}
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## 0.5.7 (2023-12-06)

Full Changelog: [v0.5.6...v0.5.7](https://github.com/anthropics/anthropic-bedrock-python/compare/v0.5.6...v0.5.7)

### Bug Fixes

* bump default request timeout to 10min to match documentation ([#88](https://github.com/anthropics/anthropic-bedrock-python/issues/88)) ([fc85322](https://github.com/anthropics/anthropic-bedrock-python/commit/fc8532218cf327b689718e96e30dc6de201c3eb2))
* **client:** correct base_url setter implementation ([#84](https://github.com/anthropics/anthropic-bedrock-python/issues/84)) ([6983847](https://github.com/anthropics/anthropic-bedrock-python/commit/698384793dad3d202a04498c67df5672037a5ada))
* **client:** ensure retried requests are closed ([#80](https://github.com/anthropics/anthropic-bedrock-python/issues/80)) ([3f28ec8](https://github.com/anthropics/anthropic-bedrock-python/commit/3f28ec864b48f694976f4cc69ee693c7d7082abf))


### Chores

* **internal:** remove unused file ([#83](https://github.com/anthropics/anthropic-bedrock-python/issues/83)) ([aa0ed40](https://github.com/anthropics/anthropic-bedrock-python/commit/aa0ed406abfad95df6d51e050402f8448dbdbdaa))
* **internal:** replace string concatenation with f-strings ([#82](https://github.com/anthropics/anthropic-bedrock-python/issues/82)) ([e3545db](https://github.com/anthropics/anthropic-bedrock-python/commit/e3545db53fa8dbeb8b302c3072e13c6a1f020533))
* **package:** lift anyio v4 restriction ([#85](https://github.com/anthropics/anthropic-bedrock-python/issues/85)) ([8879c77](https://github.com/anthropics/anthropic-bedrock-python/commit/8879c777da8d4d1cc5de7e5d7ffcabd442e61591))

## 0.5.6 (2023-11-29)

Full Changelog: [v0.5.5...v0.5.6](https://github.com/anthropics/anthropic-bedrock-python/compare/v0.5.5...v0.5.6)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ client.with_options(max_retries=5).completions.create(

### Timeouts

By default requests time out after 1 minute. You can configure this with a `timeout` option,
By default requests time out after 10 minutes. You can configure this with a `timeout` option,
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:

```python
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "anthropic-bedrock"
version = "0.5.6"
version = "0.5.7"
description = "The official Python library for the anthropic-bedrock API"
readme = "README.md"
license = "MIT"
Expand All @@ -11,7 +11,7 @@ dependencies = [
"httpx>=0.23.0, <1",
"pydantic>=1.9.0, <3",
"typing-extensions>=4.5, <5",
"anyio>=3.5.0, <4",
"anyio>=3.5.0, <5",
"distro>=1.7.0, <2",
"sniffio",
"tokenizers >= 0.13.0",
Expand Down Expand Up @@ -51,7 +51,7 @@ dev-dependencies = [
"pyright==1.1.332",
"mypy==1.7.1",
"black==23.3.0",
"respx==0.19.2",
"respx==0.20.2",
"pytest==7.1.1",
"pytest-asyncio==0.21.1",
"ruff==0.0.282",
Expand Down
19 changes: 9 additions & 10 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

-e file:.
annotated-types==0.6.0
anyio==3.7.1
anyio==4.1.0
argcomplete==3.1.2
attrs==23.1.0
black==23.3.0
boto3==1.28.58
boto3-stubs==1.28.41
botocore==1.31.58
botocore-stubs==1.33.1
botocore-stubs==1.33.6.post1
certifi==2023.7.22
charset-normalizer==3.3.2
click==8.1.7
Expand All @@ -25,10 +25,10 @@ distlib==0.3.7
distro==1.8.0
exceptiongroup==1.1.3
filelock==3.12.4
fsspec==2023.10.0
h11==0.12.0
httpcore==0.15.0
httpx==0.23.0
fsspec==2023.12.0
h11==0.14.0
httpcore==1.0.2
httpx==0.25.2
huggingface-hub==0.16.4
idna==3.4
iniconfig==2.0.0
Expand All @@ -52,8 +52,7 @@ python-dateutil==2.8.2
pytz==2023.3.post1
pyyaml==6.0.1
requests==2.31.0
respx==0.19.2
rfc3986==1.5.0
respx==0.20.2
ruff==0.0.282
s3transfer==0.7.0
six==1.16.0
Expand All @@ -62,8 +61,8 @@ time-machine==2.9.0
tokenizers==0.14.0
tomli==2.0.1
tqdm==4.66.1
types-awscrt==0.19.17
types-s3transfer==0.7.0
types-awscrt==0.19.19
types-s3transfer==0.8.2
typing-extensions==4.8.0
urllib3==1.26.18
virtualenv==20.24.5
Expand Down
15 changes: 7 additions & 8 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@

-e file:.
annotated-types==0.6.0
anyio==3.7.1
anyio==4.1.0
boto3==1.28.58
botocore==1.31.58
certifi==2023.7.22
charset-normalizer==3.3.0
charset-normalizer==3.3.2
distro==1.8.0
exceptiongroup==1.1.3
filelock==3.12.4
fsspec==2023.9.2
h11==0.12.0
httpcore==0.15.0
httpx==0.23.0
filelock==3.13.1
fsspec==2023.12.0
h11==0.14.0
httpcore==1.0.2
httpx==0.25.2
huggingface-hub==0.16.4
idna==3.4
jmespath==1.0.1
Expand All @@ -29,7 +29,6 @@ pydantic-core==2.10.1
python-dateutil==2.8.2
pyyaml==6.0.1
requests==2.31.0
rfc3986==1.5.0
s3transfer==0.7.0
six==1.16.0
sniffio==1.3.0
Expand Down
102 changes: 81 additions & 21 deletions src/anthropic_bedrock/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
DEFAULT_TIMEOUT,
DEFAULT_MAX_RETRIES,
RAW_RESPONSE_HEADER,
STREAMED_RAW_RESPONSE_HEADER,
)
from ._streaming import Stream, AsyncStream
from ._exceptions import (
Expand Down Expand Up @@ -363,14 +364,21 @@ def _make_status_error_from_response(
self,
response: httpx.Response,
) -> APIStatusError:
err_text = response.text.strip()
body = err_text
if response.is_closed and not response.is_stream_consumed:
# We can't read the response body as it has been closed
# before it was read. This can happen if an event hook
# raises a status error.
body = None
err_msg = f"Error code: {response.status_code}"
else:
err_text = response.text.strip()
body = err_text

try:
body = json.loads(err_text)
err_msg = f"Error code: {response.status_code} - {body}"
except Exception:
err_msg = err_text or f"Error code: {response.status_code}"
try:
body = json.loads(err_text)
err_msg = f"Error code: {response.status_code} - {body}"
except Exception:
err_msg = err_text or f"Error code: {response.status_code}"

return self._make_status_error(err_msg, body=body, response=response)

Expand Down Expand Up @@ -534,6 +542,12 @@ def _process_response_data(
except pydantic.ValidationError as err:
raise APIResponseValidationError(response=response, body=data) from err

def _should_stream_response_body(self, *, request: httpx.Request) -> bool:
if request.headers.get(STREAMED_RAW_RESPONSE_HEADER) == "true":
return True

return False

@property
def qs(self) -> Querystring:
return Querystring()
Expand Down Expand Up @@ -578,7 +592,7 @@ def base_url(self) -> URL:

@base_url.setter
def base_url(self, url: URL | str) -> None:
self._client.base_url = url if isinstance(url, URL) else URL(url)
self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))

@lru_cache(maxsize=None)
def platform_headers(self) -> Dict[str, str]:
Expand Down Expand Up @@ -606,7 +620,7 @@ def _calculate_retry_timeout(
if response_headers is not None:
retry_header = response_headers.get("retry-after")
try:
retry_after = int(retry_header)
retry_after = float(retry_header)
except Exception:
retry_date_tuple = email.utils.parsedate_tz(retry_header)
if retry_date_tuple is None:
Expand Down Expand Up @@ -862,14 +876,21 @@ def _request(
request = self._build_request(options)
self._prepare_request(request)

response = None

try:
response = self._client.send(request, auth=self.custom_auth, stream=stream)
response = self._client.send(
request,
auth=self.custom_auth,
stream=stream or self._should_stream_response_body(request=request),
)
log.debug(
'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
)
response.raise_for_status()
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
if retries > 0 and self._should_retry(err.response):
err.response.close()
return self._retry_request(
options,
cast_to,
Expand All @@ -881,27 +902,39 @@ def _request(

# If the response is streamed then we need to explicitly read the response
# to completion before attempting to access the response text.
err.response.read()
if not err.response.is_closed:
err.response.read()

raise self._make_status_error_from_response(err.response) from None
except httpx.TimeoutException as err:
if response is not None:
response.close()

if retries > 0:
return self._retry_request(
options,
cast_to,
retries,
stream=stream,
stream_cls=stream_cls,
response_headers=response.headers if response is not None else None,
)

raise APITimeoutError(request=request) from err
except Exception as err:
if response is not None:
response.close()

if retries > 0:
return self._retry_request(
options,
cast_to,
retries,
stream=stream,
stream_cls=stream_cls,
response_headers=response.headers if response is not None else None,
)

raise APIConnectionError(request=request) from err

return self._process_response(
Expand All @@ -917,7 +950,7 @@ def _retry_request(
options: FinalRequestOptions,
cast_to: Type[ResponseT],
remaining_retries: int,
response_headers: Optional[httpx.Headers] = None,
response_headers: httpx.Headers | None,
*,
stream: bool,
stream_cls: type[_StreamT] | None,
Expand Down Expand Up @@ -1303,14 +1336,21 @@ async def _request(
request = self._build_request(options)
await self._prepare_request(request)

response = None

try:
response = await self._client.send(request, auth=self.custom_auth, stream=stream)
response = await self._client.send(
request,
auth=self.custom_auth,
stream=stream or self._should_stream_response_body(request=request),
)
log.debug(
'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
)
response.raise_for_status()
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
if retries > 0 and self._should_retry(err.response):
await err.response.aclose()
return await self._retry_request(
options,
cast_to,
Expand All @@ -1322,19 +1362,39 @@ async def _request(

# If the response is streamed then we need to explicitly read the response
# to completion before attempting to access the response text.
await err.response.aread()
if not err.response.is_closed:
await err.response.aread()

raise self._make_status_error_from_response(err.response) from None
except httpx.ConnectTimeout as err:
if retries > 0:
return await self._retry_request(options, cast_to, retries, stream=stream, stream_cls=stream_cls)
raise APITimeoutError(request=request) from err
except httpx.TimeoutException as err:
if response is not None:
await response.aclose()

if retries > 0:
return await self._retry_request(options, cast_to, retries, stream=stream, stream_cls=stream_cls)
return await self._retry_request(
options,
cast_to,
retries,
stream=stream,
stream_cls=stream_cls,
response_headers=response.headers if response is not None else None,
)

raise APITimeoutError(request=request) from err
except Exception as err:
if response is not None:
await response.aclose()

if retries > 0:
return await self._retry_request(options, cast_to, retries, stream=stream, stream_cls=stream_cls)
return await self._retry_request(
options,
cast_to,
retries,
stream=stream,
stream_cls=stream_cls,
response_headers=response.headers if response is not None else None,
)

raise APIConnectionError(request=request) from err

return self._process_response(
Expand All @@ -1350,7 +1410,7 @@ async def _retry_request(
options: FinalRequestOptions,
cast_to: Type[ResponseT],
remaining_retries: int,
response_headers: Optional[httpx.Headers] = None,
response_headers: httpx.Headers | None,
*,
stream: bool,
stream_cls: type[_AsyncStreamT] | None,
Expand Down
Loading