Skip to content

Commit

Permalink
refactor(test): refactor authentication tests (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot committed Oct 12, 2023
1 parent bb581b5 commit c82da53
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 44 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ completion = anthropic.completions.create(
print(completion.completion)
```

While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
and adding `ANTHROPIC_API_KEY="my api key"` to your `.env` file so that your API Key is not stored in source control.
While you can provide an `api_key` keyword argument,
we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
to add `ANTHROPIC_API_KEY="my-anthropic-api-key"` to your `.env` file
so that your API Key is not stored in source control.

## Async usage

Expand Down
5 changes: 4 additions & 1 deletion src/anthropic/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,10 @@ def close(self) -> None:
The client will *not* be usable after this.
"""
self._client.close()
# If an error is thrown while constructing a client, self._client
# may not be present
if hasattr(self, "_client"):
self._client.close()

def __enter__(self: _T) -> _T:
return self
Expand Down
48 changes: 22 additions & 26 deletions src/anthropic/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ class Anthropic(SyncAPIClient):
def __init__(
self,
*,
auth_token: str | None = None,
api_key: str | None = os.environ.get("ANTHROPIC_API_KEY", None),
auth_token: str | None = os.environ.get("ANTHROPIC_AUTH_TOKEN", None),
base_url: Optional[str] = None,
api_key: Optional[str] = None,
timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
max_retries: int = DEFAULT_MAX_RETRIES,
default_headers: Mapping[str, str] | None = None,
Expand Down Expand Up @@ -93,11 +93,9 @@ def __init__(
- `api_key` from `ANTHROPIC_API_KEY`
- `auth_token` from `ANTHROPIC_AUTH_TOKEN`
"""
api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", None)
self.api_key = api_key

auth_token_envvar = os.environ.get("ANTHROPIC_AUTH_TOKEN", None)
self.auth_token = auth_token or auth_token_envvar or None
self.auth_token = auth_token

if base_url is None:
base_url = f"https://api.anthropic.com"
Expand Down Expand Up @@ -126,21 +124,21 @@ def qs(self) -> Querystring:

@property
def auth_headers(self) -> dict[str, str]:
if self._api_key_header:
return self._api_key_header
if self._auth_token_bearer:
return self._auth_token_bearer
if self._api_key_auth:
return self._api_key_auth
if self._bearer_auth:
return self._bearer_auth
return {}

@property
def _api_key_header(self) -> dict[str, str]:
def _api_key_auth(self) -> dict[str, str]:
api_key = self.api_key
if api_key is None:
return {}
return {"X-Api-Key": api_key}

@property
def _auth_token_bearer(self) -> dict[str, str]:
def _bearer_auth(self) -> dict[str, str]:
auth_token = self.auth_token
if auth_token is None:
return {}
Expand Down Expand Up @@ -172,8 +170,8 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
def copy(
self,
*,
auth_token: str | None = None,
api_key: str | None = None,
auth_token: str | None = None,
base_url: str | None = None,
timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
http_client: httpx.Client | None = None,
Expand Down Expand Up @@ -227,9 +225,9 @@ def copy(
http_client = http_client or self._client

return self.__class__(
api_key=api_key or self.api_key,
auth_token=auth_token or self.auth_token,
base_url=base_url or str(self.base_url),
api_key=api_key or self.api_key,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
http_client=http_client,
connection_pool_limits=connection_pool_limits,
Expand Down Expand Up @@ -312,9 +310,9 @@ class AsyncAnthropic(AsyncAPIClient):
def __init__(
self,
*,
auth_token: str | None = None,
api_key: str | None = os.environ.get("ANTHROPIC_API_KEY", None),
auth_token: str | None = os.environ.get("ANTHROPIC_AUTH_TOKEN", None),
base_url: Optional[str] = None,
api_key: Optional[str] = None,
timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
max_retries: int = DEFAULT_MAX_RETRIES,
default_headers: Mapping[str, str] | None = None,
Expand Down Expand Up @@ -343,11 +341,9 @@ def __init__(
- `api_key` from `ANTHROPIC_API_KEY`
- `auth_token` from `ANTHROPIC_AUTH_TOKEN`
"""
api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", None)
self.api_key = api_key

auth_token_envvar = os.environ.get("ANTHROPIC_AUTH_TOKEN", None)
self.auth_token = auth_token or auth_token_envvar or None
self.auth_token = auth_token

if base_url is None:
base_url = f"https://api.anthropic.com"
Expand Down Expand Up @@ -376,21 +372,21 @@ def qs(self) -> Querystring:

@property
def auth_headers(self) -> dict[str, str]:
if self._api_key_header:
return self._api_key_header
if self._auth_token_bearer:
return self._auth_token_bearer
if self._api_key_auth:
return self._api_key_auth
if self._bearer_auth:
return self._bearer_auth
return {}

@property
def _api_key_header(self) -> dict[str, str]:
def _api_key_auth(self) -> dict[str, str]:
api_key = self.api_key
if api_key is None:
return {}
return {"X-Api-Key": api_key}

@property
def _auth_token_bearer(self) -> dict[str, str]:
def _bearer_auth(self) -> dict[str, str]:
auth_token = self.auth_token
if auth_token is None:
return {}
Expand Down Expand Up @@ -422,8 +418,8 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
def copy(
self,
*,
auth_token: str | None = None,
api_key: str | None = None,
auth_token: str | None = None,
base_url: str | None = None,
timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
http_client: httpx.AsyncClient | None = None,
Expand Down Expand Up @@ -477,9 +473,9 @@ def copy(
http_client = http_client or self._client

return self.__class__(
api_key=api_key or self.api_key,
auth_token=auth_token or self.auth_token,
base_url=base_url or str(self.base_url),
api_key=api_key or self.api_key,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
http_client=http_client,
connection_pool_limits=connection_pool_limits,
Expand Down
3 changes: 3 additions & 0 deletions src/anthropic/_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
from ._utils import extract_type_arg as extract_type_arg
from ._utils import is_required_type as is_required_type
from ._utils import is_annotated_type as is_annotated_type
from ._utils import maybe_coerce_float as maybe_coerce_float
from ._utils import maybe_coerce_boolean as maybe_coerce_boolean
from ._utils import maybe_coerce_integer as maybe_coerce_integer
from ._utils import strip_annotated_type as strip_annotated_type
from ._transform import PropertyInfo as PropertyInfo
from ._transform import transform as transform
Expand Down
18 changes: 18 additions & 0 deletions src/anthropic/_utils/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,24 @@ def coerce_boolean(val: str) -> bool:
return val == "true" or val == "1" or val == "on"


def maybe_coerce_integer(val: str | None) -> int | None:
if val is None:
return None
return coerce_integer(val)


def maybe_coerce_float(val: str | None) -> float | None:
if val is None:
return None
return coerce_float(val)


def maybe_coerce_boolean(val: str | None) -> bool | None:
if val is None:
return None
return coerce_boolean(val)


def removeprefix(string: str, prefix: str) -> str:
"""Remove a prefix from a string.
Expand Down
2 changes: 1 addition & 1 deletion tests/api_resources/test_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from anthropic.types import Completion

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = os.environ.get("API_KEY", "something1234")
api_key = "my-anthropic-api-key"


class TestCompletions:
Expand Down
2 changes: 1 addition & 1 deletion tests/api_resources/test_top_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from anthropic import Anthropic, AsyncAnthropic

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = os.environ.get("API_KEY", "something1234")
api_key = "my-anthropic-api-key"


class TestTopLevel:
Expand Down
20 changes: 7 additions & 13 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = os.environ.get("API_KEY", "something1234")
api_key = "my-anthropic-api-key"


def _get_params(client: BaseClient) -> dict[str, str]:
Expand All @@ -52,12 +52,9 @@ def test_copy(self) -> None:
copied = self.client.copy()
assert id(copied) != id(self.client)

copied = self.client.copy(api_key="my new api key")
assert copied.api_key == "my new api key"
assert self.client.api_key == api_key

copied = self.client.copy(auth_token="my-auth-token")
assert copied.auth_token == "my-auth-token"
copied = self.client.copy(api_key="another my-anthropic-api-key")
assert copied.api_key == "another my-anthropic-api-key"
assert self.client.api_key == "my-anthropic-api-key"

def test_copy_default_options(self) -> None:
# options that have a default are overridden correctly
Expand Down Expand Up @@ -669,12 +666,9 @@ def test_copy(self) -> None:
copied = self.client.copy()
assert id(copied) != id(self.client)

copied = self.client.copy(api_key="my new api key")
assert copied.api_key == "my new api key"
assert self.client.api_key == api_key

copied = self.client.copy(auth_token="my-auth-token")
assert copied.auth_token == "my-auth-token"
copied = self.client.copy(api_key="another my-anthropic-api-key")
assert copied.api_key == "another my-anthropic-api-key"
assert self.client.api_key == "my-anthropic-api-key"

def test_copy_default_options(self) -> None:
# options that have a default are overridden correctly
Expand Down

0 comments on commit c82da53

Please sign in to comment.