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.31.2 #595

Merged
merged 6 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.31.1"
".": "0.31.2"
}
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 0.31.2 (2024-07-17)

Full Changelog: [v0.31.1...v0.31.2](https://github.com/anthropics/anthropic-sdk-python/compare/v0.31.1...v0.31.2)

### Bug Fixes

* **vertex:** also refresh auth if there is no token ([4a8d02d](https://github.com/anthropics/anthropic-sdk-python/commit/4a8d02d0616c04a2acc31a3179b7d50093d6371e))
* **vertex:** correct request options in retries ([460547b](https://github.com/anthropics/anthropic-sdk-python/commit/460547b7e6bafa4044127760946d141d1e49131b))


### Chores

* **docs:** minor update to formatting of API link in README ([#594](https://github.com/anthropics/anthropic-sdk-python/issues/594)) ([113b6ac](https://github.com/anthropics/anthropic-sdk-python/commit/113b6ac65de2a670b0d957d11d48b060106150d3))
* **internal:** update formatting ([#597](https://github.com/anthropics/anthropic-sdk-python/issues/597)) ([565dfcd](https://github.com/anthropics/anthropic-sdk-python/commit/565dfcd4610c26b598f6c72e9182e8c60bffc2a0))
* **tests:** faster bedrock retry tests ([4ff067f](https://github.com/anthropics/anthropic-sdk-python/commit/4ff067f48e8e177ebdb8f06d6a4a0ffe9a096a8b))

## 0.31.1 (2024-07-15)

Full Changelog: [v0.31.0...v0.31.1](https://github.com/anthropics/anthropic-sdk-python/compare/v0.31.0...v0.31.1)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and offers both synchronous and asynchronous clients powered by [httpx](https://

## Documentation

The REST API documentation can be found [on docs.anthropic.com](https://docs.anthropic.com/claude/reference/). The full API of this library can be found in [api.md](api.md).
The REST API documentation can be found on [docs.anthropic.com](https://docs.anthropic.com/claude/reference/). The full API of this library can be found in [api.md](api.md).

## Installation

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "anthropic"
version = "0.31.1"
version = "0.31.2"
description = "The official Python library for the anthropic API"
dynamic = ["readme"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion src/anthropic/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "anthropic"
__version__ = "0.31.1" # x-release-please-version
__version__ = "0.31.2" # x-release-please-version
72 changes: 38 additions & 34 deletions src/anthropic/lib/vertex/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ._auth import load_auth, refresh_auth
from ..._types import NOT_GIVEN, NotGiven, Transport, ProxiesTypes, AsyncTransport
from ..._utils import is_dict, asyncify, is_given
from ..._compat import typed_cached_property
from ..._compat import model_copy, typed_cached_property
from ..._models import FinalRequestOptions
from ..._version import __version__
from ..._streaming import Stream, AsyncStream
Expand All @@ -37,37 +37,6 @@


class BaseVertexClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
@override
def _build_request(
self,
options: FinalRequestOptions,
) -> httpx.Request:
if is_dict(options.json_data):
options.json_data.setdefault("anthropic_version", DEFAULT_VERSION)

if options.url == "/v1/messages" and options.method == "post":
project_id = self.project_id
if project_id is None:
raise RuntimeError(
"No project_id was given and it could not be resolved from credentials. The client should be instantiated with the `project_id` argument or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set."
)

if not is_dict(options.json_data):
raise RuntimeError("Expected json data to be a dictionary for post /v1/messages")

model = options.json_data.pop("model")
stream = options.json_data.get("stream", False)
specifier = "streamRawPredict" if stream else "rawPredict"

options.url = (
f"/projects/{self.project_id}/locations/{self.region}/publishers/anthropic/models/{model}:{specifier}"
)

if is_dict(options.json_data):
options.json_data.pop("model", None)

return super()._build_request(options)

@typed_cached_property
def region(self) -> str:
raise RuntimeError("region not set")
Expand Down Expand Up @@ -174,6 +143,10 @@ def __init__(

self.messages = Messages(self)

@override
def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
return _prepare_options(options, project_id=self.project_id, region=self.region)

@override
def _prepare_request(self, request: httpx.Request) -> None:
if request.headers.get("Authorization"):
Expand All @@ -191,7 +164,7 @@ def _ensure_access_token(self) -> str:
if not self.project_id:
self.project_id = project_id

if self.credentials.expired:
if self.credentials.expired or not self.credentials.token:
refresh_auth(self.credentials)

if not self.credentials.token:
Expand Down Expand Up @@ -336,6 +309,10 @@ def __init__(

self.messages = AsyncMessages(self)

@override
async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
return _prepare_options(options, project_id=self.project_id, region=self.region)

@override
async def _prepare_request(self, request: httpx.Request) -> None:
if request.headers.get("Authorization"):
Expand All @@ -353,7 +330,7 @@ async def _ensure_access_token(self) -> str:
if not self.project_id:
self.project_id = project_id

if self.credentials.expired:
if self.credentials.expired or not self.credentials.token:
await asyncify(refresh_auth)(self.credentials)

if not self.credentials.token:
Expand Down Expand Up @@ -436,3 +413,30 @@ def copy(
# Alias for `copy` for nicer inline usage, e.g.
# client.with_options(timeout=10).foo.create(...)
with_options = copy


def _prepare_options(input_options: FinalRequestOptions, *, project_id: str | None, region: str) -> FinalRequestOptions:
options = model_copy(input_options, deep=True)

if is_dict(options.json_data):
options.json_data.setdefault("anthropic_version", DEFAULT_VERSION)

if options.url == "/v1/messages" and options.method == "post":
if project_id is None:
raise RuntimeError(
"No project_id was given and it could not be resolved from credentials. The client should be instantiated with the `project_id` argument or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set."
)

if not is_dict(options.json_data):
raise RuntimeError("Expected json data to be a dictionary for post /v1/messages")

model = options.json_data.pop("model")
stream = options.json_data.get("stream", False)
specifier = "streamRawPredict" if stream else "rawPredict"

options.url = f"/projects/{project_id}/locations/{region}/publishers/anthropic/models/{model}:{specifier}"

if is_dict(options.json_data):
options.json_data.pop("model", None)

return options
1 change: 1 addition & 0 deletions src/anthropic/types/content_block_delta_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_content_block_delta_event import RawContentBlockDeltaEvent

__all__ = ["ContentBlockDeltaEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/content_block_start_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_content_block_start_event import RawContentBlockStartEvent

__all__ = ["ContentBlockStartEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/content_block_stop_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_content_block_stop_event import RawContentBlockStopEvent

__all__ = ["ContentBlockStopEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/message_delta_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_message_delta_event import RawMessageDeltaEvent

__all__ = ["MessageDeltaEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/message_delta_usage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .._models import BaseModel

__all__ = ["MessageDeltaUsage"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/message_start_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_message_start_event import RawMessageStartEvent

__all__ = ["MessageStartEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/message_stop_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_message_stop_event import RawMessageStopEvent

__all__ = ["MessageStopEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/message_stream_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .raw_message_stream_event import RawMessageStreamEvent

__all__ = ["MessageStreamEvent"]
Expand Down
1 change: 1 addition & 0 deletions src/anthropic/types/usage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.



from .._models import BaseModel

__all__ = ["Usage"]
Expand Down
4 changes: 2 additions & 2 deletions tests/lib/test_bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MockRequestCall(Protocol):
def test_messages_retries(respx_mock: MockRouter) -> None:
respx_mock.post(re.compile(r"https://bedrock-runtime\.us-east-1\.amazonaws\.com/model/.*/invoke")).mock(
side_effect=[
httpx.Response(500, json={"error": "server error"}),
httpx.Response(500, json={"error": "server error"}, headers={"retry-after-ms": "10"}),
httpx.Response(200, json={"foo": "bar"}),
]
)
Expand Down Expand Up @@ -63,7 +63,7 @@ def test_messages_retries(respx_mock: MockRouter) -> None:
async def test_messages_retries_async(respx_mock: MockRouter) -> None:
respx_mock.post(re.compile(r"https://bedrock-runtime\.us-east-1\.amazonaws\.com/model/.*/invoke")).mock(
side_effect=[
httpx.Response(500, json={"error": "server error"}),
httpx.Response(500, json={"error": "server error"}, headers={"retry-after-ms": "10"}),
httpx.Response(200, json={"foo": "bar"}),
]
)
Expand Down
68 changes: 66 additions & 2 deletions tests/lib/test_vertex.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,52 @@
from __future__ import annotations

import os
from typing import cast
from typing_extensions import Protocol

import httpx
import pytest
from respx import MockRouter

from anthropic import AnthropicVertex, AsyncAnthropicVertex

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")


class MockRequestCall(Protocol):
request: httpx.Request


class TestAnthropicVertex:
client = AnthropicVertex(region="region", project_id="project")
client = AnthropicVertex(region="region", project_id="project", access_token="my-access-token")

@pytest.mark.respx()
def test_messages_retries(self, respx_mock: MockRouter) -> None:
request_url = "https://region-aiplatform.googleapis.com/v1/projects/project/locations/region/publishers/anthropic/models/claude-3-sonnet@20240229:rawPredict"
respx_mock.post(request_url).mock(
side_effect=[
httpx.Response(500, json={"error": "server error"}, headers={"retry-after-ms": "10"}),
httpx.Response(200, json={"foo": "bar"}),
]
)

self.client.messages.create(
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Say hello there!",
}
],
model="claude-3-sonnet@20240229",
)

calls = cast("list[MockRequestCall]", respx_mock.calls)

assert len(calls) == 2

assert calls[0].request.url == request_url
assert calls[1].request.url == request_url

def test_copy(self) -> None:
copied = self.client.copy()
Expand Down Expand Up @@ -86,7 +121,36 @@ def test_copy_default_headers(self) -> None:


class TestAsyncAnthropicVertex:
client = AsyncAnthropicVertex(region="region", project_id="project")
client = AsyncAnthropicVertex(region="region", project_id="project", access_token="my-access-token")

@pytest.mark.respx()
@pytest.mark.asyncio()
async def test_messages_retries(self, respx_mock: MockRouter) -> None:
request_url = "https://region-aiplatform.googleapis.com/v1/projects/project/locations/region/publishers/anthropic/models/claude-3-sonnet@20240229:rawPredict"
respx_mock.post(request_url).mock(
side_effect=[
httpx.Response(500, json={"error": "server error"}, headers={"retry-after-ms": "10"}),
httpx.Response(200, json={"foo": "bar"}),
]
)

await self.client.with_options(timeout=0.2).messages.create(
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Say hello there!",
}
],
model="claude-3-sonnet@20240229",
)

calls = cast("list[MockRequestCall]", respx_mock.calls)

assert len(calls) == 2

assert calls[0].request.url == request_url
assert calls[1].request.url == request_url

def test_copy(self) -> None:
copied = self.client.copy()
Expand Down
Loading