diff --git a/.github/workflows/handle-release-pr-title-edit.yml b/.github/workflows/handle-release-pr-title-edit.yml deleted file mode 100644 index a2ae73d9..00000000 --- a/.github/workflows/handle-release-pr-title-edit.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Handle release PR title edits -on: - pull_request: - types: - - edited - - unlabeled - -jobs: - update_pr_content: - name: Update pull request content - if: | - ((github.event.action == 'edited' && github.event.changes.title.from != github.event.pull_request.title) || - (github.event.action == 'unlabeled' && github.event.label.name == 'autorelease: custom version')) && - startsWith(github.event.pull_request.head.ref, 'release-please--') && - github.event.pull_request.state == 'open' && - github.event.sender.login != 'stainless-bot' && - github.event.sender.login != 'stainless-app' && - github.repository == 'anthropics/anthropic-sdk-python' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: stainless-api/trigger-release-please@v1 - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1b5dc400..0a40b9d7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.39.0" + ".": "0.40.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index ebe0695a..2d482a8b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 10 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-25f83d91f601c1962b3701fedf829f678f306aca0758af286ee1586cc9931f75.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-cf96271afd65b396dc71c4e897d1d55a9526c122e3890bc87b5ff33316c93853.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8136f860..eced9b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 0.40.0 (2024-11-28) + +Full Changelog: [v0.39.0...v0.40.0](https://github.com/anthropics/anthropic-sdk-python/compare/v0.39.0...v0.40.0) + +### Features + +* **client:** add ._request_id property to object responses ([#743](https://github.com/anthropics/anthropic-sdk-python/issues/743)) ([9fb64a6](https://github.com/anthropics/anthropic-sdk-python/commit/9fb64a627821730fcf48662f6326d4c0f8c623ab)) + + +### Bug Fixes + +* **asyncify:** avoid hanging process under certain conditions ([#756](https://github.com/anthropics/anthropic-sdk-python/issues/756)) ([c71bba2](https://github.com/anthropics/anthropic-sdk-python/commit/c71bba2ad5248400c0142fca5e53c505f9e6d417)) +* **bedrock:** correct URL encoding for model params ([#759](https://github.com/anthropics/anthropic-sdk-python/issues/759)) ([be4e73a](https://github.com/anthropics/anthropic-sdk-python/commit/be4e73a6d6ced33c887cc338c7755f5fe1697e54)) +* **client:** compat with new httpx 0.28.0 release ([#765](https://github.com/anthropics/anthropic-sdk-python/issues/765)) ([de51f60](https://github.com/anthropics/anthropic-sdk-python/commit/de51f6089f7b025db5d150438caf78a615431cde)) +* don't use dicts as iterables in transform ([#750](https://github.com/anthropics/anthropic-sdk-python/issues/750)) ([1f71464](https://github.com/anthropics/anthropic-sdk-python/commit/1f71464a818066687bf6c1bcae0abb991d6ed9cd)) +* **types:** remove anthropic-instant-1.2 model ([#744](https://github.com/anthropics/anthropic-sdk-python/issues/744)) ([23637de](https://github.com/anthropics/anthropic-sdk-python/commit/23637de028c6c870f062fc0fbaefa9ae54a0e053)) + + +### Chores + +* **api:** update spec version ([#751](https://github.com/anthropics/anthropic-sdk-python/issues/751)) ([4ec986c](https://github.com/anthropics/anthropic-sdk-python/commit/4ec986ccfa601a78f9fff721710390c1fb4727cc)) +* **ci:** remove unneeded workflow ([#742](https://github.com/anthropics/anthropic-sdk-python/issues/742)) ([472b7d3](https://github.com/anthropics/anthropic-sdk-python/commit/472b7d362c4bd32e7b32c5a10ed3d40e6821f052)) +* **internal:** exclude mypy from running on tests ([#764](https://github.com/anthropics/anthropic-sdk-python/issues/764)) ([bce763a](https://github.com/anthropics/anthropic-sdk-python/commit/bce763a35f5420d0e811ff3cd4d2bf7494f11081)) +* **internal:** fix compat model_dump method when warnings are passed ([#760](https://github.com/anthropics/anthropic-sdk-python/issues/760)) ([0e09236](https://github.com/anthropics/anthropic-sdk-python/commit/0e0923612bb1ce4eb18f82966055df4fb8cd348d)) +* **internal:** minor formatting changes ([493020e](https://github.com/anthropics/anthropic-sdk-python/commit/493020eed859bd20e49f4ac7ec0b6c3293c7d3fd)) +* remove now unused `cached-property` dep ([#762](https://github.com/anthropics/anthropic-sdk-python/issues/762)) ([b9ffefe](https://github.com/anthropics/anthropic-sdk-python/commit/b9ffefec20279705a86e4ad342597886bd7064ca)) +* **tests:** adjust retry timeout values ([#736](https://github.com/anthropics/anthropic-sdk-python/issues/736)) ([27ed781](https://github.com/anthropics/anthropic-sdk-python/commit/27ed7816aa8ae32f179652ea6171dd621ef8a6b5)) +* **tests:** limit array example length ([#754](https://github.com/anthropics/anthropic-sdk-python/issues/754)) ([6cab2b9](https://github.com/anthropics/anthropic-sdk-python/commit/6cab2b9237c9679ce40e319d035fb4267bca03a7)) + + +### Documentation + +* add info log level to readme ([#761](https://github.com/anthropics/anthropic-sdk-python/issues/761)) ([5966b85](https://github.com/anthropics/anthropic-sdk-python/commit/5966b855f3e58d735d8ccfe3244f549baa3a87ed)) +* move comments in example snippets ([#749](https://github.com/anthropics/anthropic-sdk-python/issues/749)) ([f887930](https://github.com/anthropics/anthropic-sdk-python/commit/f887930773cbb2d5cb0d4d46ec6cd4f320d045ea)) + ## 0.39.0 (2024-11-04) Full Changelog: [v0.38.0...v0.39.0](https://github.com/anthropics/anthropic-sdk-python/compare/v0.38.0...v0.39.0) diff --git a/README.md b/README.md index 0860d40f..f006d1d0 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,7 @@ import os from anthropic import Anthropic client = Anthropic( - # This is the default and can be omitted - api_key=os.environ.get("ANTHROPIC_API_KEY"), + api_key=os.environ.get("ANTHROPIC_API_KEY"), # This is the default and can be omitted ) message = client.messages.create( @@ -58,8 +57,7 @@ import asyncio from anthropic import AsyncAnthropic client = AsyncAnthropic( - # This is the default and can be omitted - api_key=os.environ.get("ANTHROPIC_API_KEY"), + api_key=os.environ.get("ANTHROPIC_API_KEY"), # This is the default and can be omitted ) @@ -427,6 +425,30 @@ Error codes are as followed: | >=500 | `InternalServerError` | | N/A | `APIConnectionError` | +## Request IDs + +> For more information on debugging requests, see [these docs](https://docs.anthropic.com/en/api/errors#request-id) + +All object responses in the SDK provide a `_request_id` property which is added from the `request-id` response header so that you can quickly log failing requests and report them back to Anthropic. + +```python +message = client.messages.create( + max_tokens=1024, + messages=[ + { + "role": "user", + "content": "Hello, Claude", + } + ], + model="claude-3-opus-20240229", +) +print(message._request_id) # req_018EeWyXxfu5pfWkrYcMdjWG +``` + +Note that unlike other properties that use an `_` prefix, the `_request_id` property +*is* public. Unless documented otherwise, *all* other `_` prefix properties, +methods and modules are *private*. + ### Retries Certain errors are automatically retried 2 times by default, with a short exponential backoff. @@ -515,12 +537,14 @@ client = Anthropic( We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. -You can enable logging by setting the environment variable `ANTHROPIC_LOG` to `debug`. +You can enable logging by setting the environment variable `ANTHROPIC_LOG` to `info`. ```shell -$ export ANTHROPIC_LOG=debug +$ export ANTHROPIC_LOG=info ``` +Or to `debug` for more verbose logging. + ### How to tell whether `None` means `null` or missing In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: diff --git a/api.md b/api.md index ef21566a..1ebd122d 100644 --- a/api.md +++ b/api.md @@ -6,6 +6,7 @@ Types: from anthropic.types import ( ContentBlock, ContentBlockDeltaEvent, + ContentBlockParam, ContentBlockStartEvent, ContentBlockStopEvent, ImageBlockParam, diff --git a/mypy.ini b/mypy.ini index b4f97067..a7ba5997 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,7 +5,10 @@ show_error_codes = True # Exclude _files.py because mypy isn't smart enough to apply # the correct type narrowing and as this is an internal module # it's fine to just use Pyright. -exclude = ^(src/anthropic/_files\.py|_dev/.*\.py)$ +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +exclude = ^(src/anthropic/_files\.py|_dev/.*\.py|tests/.*)$ strict_equality = True implicit_reexport = True diff --git a/pyproject.toml b/pyproject.toml index e158942e..e411b281 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "anthropic" -version = "0.39.0" +version = "0.40.0" description = "The official Python library for the anthropic API" dynamic = ["readme"] license = "MIT" @@ -14,7 +14,6 @@ dependencies = [ "anyio>=3.5.0, <5", "distro>=1.7.0, <2", "sniffio", - "cached-property; python_version < '3.8'", "jiter>=0.4.0, <1", ] requires-python = ">= 3.8" @@ -61,6 +60,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "boto3-stubs >= 1", "rich>=13.7.1", + "nest_asyncio==1.6.0" ] [tool.rye.scripts] diff --git a/requirements-dev.lock b/requirements-dev.lock index 854f785c..69698d3f 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,6 +69,7 @@ mdurl==0.1.2 mypy==1.13.0 mypy-extensions==1.0.0 # via mypy +nest-asyncio==1.6.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 diff --git a/src/anthropic/_base_client.py b/src/anthropic/_base_client.py index d7289b5c..9423bcd4 100644 --- a/src/anthropic/_base_client.py +++ b/src/anthropic/_base_client.py @@ -793,6 +793,7 @@ def __init__( custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: + kwargs: dict[str, Any] = {} if limits is not None: warnings.warn( "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", @@ -805,6 +806,7 @@ def __init__( limits = DEFAULT_CONNECTION_LIMITS if transport is not None: + kwargs["transport"] = transport warnings.warn( "The `transport` argument is deprecated. The `http_client` argument should be passed instead", category=DeprecationWarning, @@ -814,6 +816,7 @@ def __init__( raise ValueError("The `http_client` argument is mutually exclusive with `transport`") if proxies is not None: + kwargs["proxies"] = proxies warnings.warn( "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", category=DeprecationWarning, @@ -857,10 +860,9 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, limits=limits, follow_redirects=True, + **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1374,6 +1376,7 @@ def __init__( custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: + kwargs: dict[str, Any] = {} if limits is not None: warnings.warn( "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", @@ -1386,6 +1389,7 @@ def __init__( limits = DEFAULT_CONNECTION_LIMITS if transport is not None: + kwargs["transport"] = transport warnings.warn( "The `transport` argument is deprecated. The `http_client` argument should be passed instead", category=DeprecationWarning, @@ -1395,6 +1399,7 @@ def __init__( raise ValueError("The `http_client` argument is mutually exclusive with `transport`") if proxies is not None: + kwargs["proxies"] = proxies warnings.warn( "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", category=DeprecationWarning, @@ -1438,10 +1443,9 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, limits=limits, follow_redirects=True, + **kwargs, # type: ignore ) def is_closed(self) -> bool: diff --git a/src/anthropic/_compat.py b/src/anthropic/_compat.py index 4794129c..92d9ee61 100644 --- a/src/anthropic/_compat.py +++ b/src/anthropic/_compat.py @@ -145,7 +145,8 @@ def model_dump( exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, - warnings=warnings, + # warnings are not supported in Pydantic v1 + warnings=warnings if PYDANTIC_V2 else True, ) return cast( "dict[str, Any]", @@ -213,9 +214,6 @@ def __set_name__(self, owner: type[Any], name: str) -> None: ... # __set__ is not defined at runtime, but @cached_property is designed to be settable def __set__(self, instance: object, value: _T) -> None: ... else: - try: - from functools import cached_property as cached_property - except ImportError: - from cached_property import cached_property as cached_property + from functools import cached_property as cached_property typed_cached_property = cached_property diff --git a/src/anthropic/_legacy_response.py b/src/anthropic/_legacy_response.py index 50220753..9bd9570a 100644 --- a/src/anthropic/_legacy_response.py +++ b/src/anthropic/_legacy_response.py @@ -25,7 +25,7 @@ from ._types import NoneType from ._utils import is_given, extract_type_arg, is_annotated_type -from ._models import BaseModel, is_basemodel +from ._models import BaseModel, is_basemodel, add_request_id from ._constants import RAW_RESPONSE_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type from ._exceptions import APIResponseValidationError @@ -140,8 +140,11 @@ class MyModel(BaseModel): if is_given(self._options.post_parser): parsed = self._options.post_parser(parsed) + if isinstance(parsed, BaseModel): + add_request_id(parsed, self.request_id) + self._parsed_by_type[cache_key] = parsed - return parsed + return cast(R, parsed) @property def headers(self) -> httpx.Headers: diff --git a/src/anthropic/_models.py b/src/anthropic/_models.py index 6cb469e2..ad846d0c 100644 --- a/src/anthropic/_models.py +++ b/src/anthropic/_models.py @@ -2,7 +2,7 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( Unpack, @@ -95,6 +95,22 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + if TYPE_CHECKING: + _request_id: Optional[str] = None + """The ID of the request, returned via the `request-id` header. Useful for debugging requests and reporting issues to Anthropic. + This will **only** be set for the top-level response object, it will not be defined for nested objects. For example: + + ```py + message = await client.messages.create(...) + message._request_id # req_xxx + message.usage._request_id # raises `AttributeError` + ``` + + Note: unlike other properties that use an `_` prefix, this property + *is* public. Unless documented otherwise, all other `_` prefix properties, + methods and modules are *private*. + """ + def to_dict( self, *, @@ -665,6 +681,21 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: setattr(typ, "__pydantic_config__", config) # noqa: B010 +def add_request_id(obj: BaseModel, request_id: str | None) -> None: + obj._request_id = request_id + + # in Pydantic v1, using setattr like we do above causes the attribute + # to be included when serializing the model which we don't want in this + # case so we need to explicitly exclude it + if not PYDANTIC_V2: + try: + exclude_fields = obj.__exclude_fields__ # type: ignore + except AttributeError: + cast(Any, obj).__exclude_fields__ = {"_request_id", "__exclude_fields__"} + else: + cast(Any, obj).__exclude_fields__ = {*(exclude_fields or {}), "_request_id", "__exclude_fields__"} + + # our use of subclasssing here causes weirdness for type checkers, # so we just pretend that we don't subclass if TYPE_CHECKING: diff --git a/src/anthropic/_response.py b/src/anthropic/_response.py index 30167e9b..a563e25a 100644 --- a/src/anthropic/_response.py +++ b/src/anthropic/_response.py @@ -26,7 +26,7 @@ from ._types import NoneType from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base -from ._models import BaseModel, is_basemodel +from ._models import BaseModel, is_basemodel, add_request_id from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type from ._exceptions import AnthropicError, APIResponseValidationError @@ -339,8 +339,11 @@ class MyModel(BaseModel): if is_given(self._options.post_parser): parsed = self._options.post_parser(parsed) + if isinstance(parsed, BaseModel): + add_request_id(parsed, self.request_id) + self._parsed_by_type[cache_key] = parsed - return parsed + return cast(R, parsed) def read(self) -> bytes: """Read and return the binary response content.""" @@ -443,8 +446,11 @@ class MyModel(BaseModel): if is_given(self._options.post_parser): parsed = self._options.post_parser(parsed) + if isinstance(parsed, BaseModel): + add_request_id(parsed, self.request_id) + self._parsed_by_type[cache_key] = parsed - return parsed + return cast(R, parsed) async def read(self) -> bytes: """Read and return the binary response content.""" diff --git a/src/anthropic/_utils/_sync.py b/src/anthropic/_utils/_sync.py index d0d81033..8b3aaf2b 100644 --- a/src/anthropic/_utils/_sync.py +++ b/src/anthropic/_utils/_sync.py @@ -1,56 +1,62 @@ from __future__ import annotations +import sys +import asyncio import functools -from typing import TypeVar, Callable, Awaitable +import contextvars +from typing import Any, TypeVar, Callable, Awaitable from typing_extensions import ParamSpec -import anyio -import anyio.to_thread - -from ._reflection import function_has_argument - T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") -# copied from `asyncer`, https://github.com/tiangolo/asyncer -def asyncify( - function: Callable[T_ParamSpec, T_Retval], - *, - cancellable: bool = False, - limiter: anyio.CapacityLimiter | None = None, -) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: +if sys.version_info >= (3, 9): + to_thread = asyncio.to_thread +else: + # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread + # for Python 3.8 support + async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs + ) -> Any: + """Asynchronously run function *func* in a separate thread. + + Any *args and **kwargs supplied for this function are directly passed + to *func*. Also, the current :class:`contextvars.Context` is propagated, + allowing context variables from the main thread to be accessed in the + separate thread. + + Returns a coroutine that can be awaited to get the eventual result of *func*. + """ + loop = asyncio.events.get_running_loop() + ctx = contextvars.copy_context() + func_call = functools.partial(ctx.run, func, *args, **kwargs) + return await loop.run_in_executor(None, func_call) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments, and that when called, calls the original function - in a worker thread using `anyio.to_thread.run_sync()`. Internally, - `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports - keyword arguments additional to positional arguments and it adds better support for - autocompletion and inline errors for the arguments of the function called and the - return value. - - If the `cancellable` option is enabled and the task waiting for its completion is - cancelled, the thread will still run its course but its return value (or any raised - exception) will be ignored. + positional and keyword arguments. For python version 3.9 and above, it uses + asyncio.to_thread to run the function in a separate thread. For python version + 3.8, it uses locally defined copy of the asyncio.to_thread function which was + introduced in python 3.9. - Use it like this: + Usage: - ```Python - def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: - # Do work - return "Some result" + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result - result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") - print(result) + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) ``` ## Arguments `function`: a blocking regular callable (e.g. a function) - `cancellable`: `True` to allow cancellation of the operation - `limiter`: capacity limiter to use to limit the total amount of threads running - (if omitted, the default limiter is used) ## Return @@ -60,22 +66,6 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: """ async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: - partial_f = functools.partial(function, *args, **kwargs) - - # In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old - # `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid - # surfacing deprecation warnings. - if function_has_argument(anyio.to_thread.run_sync, "abandon_on_cancel"): - return await anyio.to_thread.run_sync( - partial_f, - abandon_on_cancel=cancellable, - limiter=limiter, - ) - - return await anyio.to_thread.run_sync( - partial_f, - cancellable=cancellable, - limiter=limiter, - ) + return await to_thread(function, *args, **kwargs) return wrapper diff --git a/src/anthropic/_utils/_transform.py b/src/anthropic/_utils/_transform.py index d7c05345..a6b62cad 100644 --- a/src/anthropic/_utils/_transform.py +++ b/src/anthropic/_utils/_transform.py @@ -316,6 +316,11 @@ async def _async_transform_recursive( # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] diff --git a/src/anthropic/_version.py b/src/anthropic/_version.py index 6058eb3b..a87bfc50 100644 --- a/src/anthropic/_version.py +++ b/src/anthropic/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "anthropic" -__version__ = "0.39.0" # x-release-please-version +__version__ = "0.40.0" # x-release-please-version diff --git a/src/anthropic/lib/bedrock/_client.py b/src/anthropic/lib/bedrock/_client.py index 70d1561a..89c601d9 100644 --- a/src/anthropic/lib/bedrock/_client.py +++ b/src/anthropic/lib/bedrock/_client.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import urllib.parse from typing import Any, Union, Mapping, TypeVar from typing_extensions import Self, override @@ -47,6 +48,7 @@ def _prepare_options(input_options: FinalRequestOptions) -> FinalRequestOptions: raise RuntimeError("Expected dictionary json_data for post /completions endpoint") model = options.json_data.pop("model", None) + model = urllib.parse.quote(str(model), safe=":") stream = options.json_data.pop("stream", False) if stream: options.url = f"/model/{model}/invoke-with-response-stream" diff --git a/src/anthropic/types/__init__.py b/src/anthropic/types/__init__.py index c67e5e3e..0125a215 100644 --- a/src/anthropic/types/__init__.py +++ b/src/anthropic/types/__init__.py @@ -22,6 +22,7 @@ from .tool_choice_param import ToolChoiceParam as ToolChoiceParam from .message_stop_event import MessageStopEvent as MessageStopEvent from .beta_error_response import BetaErrorResponse as BetaErrorResponse +from .content_block_param import ContentBlockParam as ContentBlockParam from .message_delta_event import MessageDeltaEvent as MessageDeltaEvent from .message_delta_usage import MessageDeltaUsage as MessageDeltaUsage from .message_start_event import MessageStartEvent as MessageStartEvent diff --git a/src/anthropic/types/content_block_param.py b/src/anthropic/types/content_block_param.py new file mode 100644 index 00000000..65e9bd4a --- /dev/null +++ b/src/anthropic/types/content_block_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .text_block_param import TextBlockParam +from .image_block_param import ImageBlockParam +from .tool_use_block_param import ToolUseBlockParam +from .tool_result_block_param import ToolResultBlockParam + +__all__ = ["ContentBlockParam"] + +ContentBlockParam: TypeAlias = Union[TextBlockParam, ImageBlockParam, ToolUseBlockParam, ToolResultBlockParam] diff --git a/src/anthropic/types/model.py b/src/anthropic/types/model.py index a50e8c84..71157772 100644 --- a/src/anthropic/types/model.py +++ b/src/anthropic/types/model.py @@ -19,6 +19,5 @@ "claude-3-haiku-20240307", "claude-2.1", "claude-2.0", - "claude-instant-1.2", ], ] diff --git a/src/anthropic/types/model_param.py b/src/anthropic/types/model_param.py index cb29b1b0..5d4a1560 100644 --- a/src/anthropic/types/model_param.py +++ b/src/anthropic/types/model_param.py @@ -21,6 +21,5 @@ "claude-3-haiku-20240307", "claude-2.1", "claude-2.0", - "claude-instant-1.2", ], ] diff --git a/tests/api_resources/beta/messages/test_batches.py b/tests/api_resources/beta/messages/test_batches.py index 12c5cae0..3ff7937e 100644 --- a/tests/api_resources/beta/messages/test_batches.py +++ b/tests/api_resources/beta/messages/test_batches.py @@ -39,33 +39,7 @@ def test_method_create(self, client: Anthropic) -> None: ], "model": "claude-3-5-sonnet-20241022", }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, + } ], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -86,96 +60,7 @@ def test_method_create_with_all_params(self, client: Anthropic) -> None: ], "model": "claude-3-5-sonnet-20241022", "metadata": {"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - "stop_sequences": ["string", "string", "string"], - "stream": False, - "system": [ - { - "text": "Today's date is 2024-06-01.", - "type": "text", - "cache_control": {"type": "ephemeral"}, - } - ], - "temperature": 1, - "tool_choice": { - "type": "auto", - "disable_parallel_tool_use": True, - }, - "tools": [ - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - ], - "top_k": 5, - "top_p": 0.7, - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - "metadata": {"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - "stop_sequences": ["string", "string", "string"], + "stop_sequences": ["string"], "stream": False, "system": [ { @@ -208,141 +93,14 @@ def test_method_create_with_all_params(self, client: Anthropic) -> None: "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - ], - "top_k": 5, - "top_p": 0.7, - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", } ], - "model": "claude-3-5-sonnet-20241022", - "metadata": {"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - "stop_sequences": ["string", "string", "string"], - "stream": False, - "system": [ - { - "text": "Today's date is 2024-06-01.", - "type": "text", - "cache_control": {"type": "ephemeral"}, - } - ], - "temperature": 1, - "tool_choice": { - "type": "auto", - "disable_parallel_tool_use": True, - }, - "tools": [ - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - ], "top_k": 5, "top_p": 0.7, }, - }, + } ], - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -362,33 +120,7 @@ def test_raw_response_create(self, client: Anthropic) -> None: ], "model": "claude-3-5-sonnet-20241022", }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, + } ], ) @@ -413,33 +145,7 @@ def test_streaming_response_create(self, client: Anthropic) -> None: ], "model": "claude-3-5-sonnet-20241022", }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, + } ], ) as response: assert not response.is_closed @@ -461,7 +167,7 @@ def test_method_retrieve(self, client: Anthropic) -> None: def test_method_retrieve_with_all_params(self, client: Anthropic) -> None: batch = client.beta.messages.batches.retrieve( message_batch_id="message_batch_id", - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -507,7 +213,7 @@ def test_method_list_with_all_params(self, client: Anthropic) -> None: after_id="after_id", before_id="before_id", limit=1, - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(SyncPage[BetaMessageBatch], batch, path=["response"]) @@ -542,7 +248,7 @@ def test_method_cancel(self, client: Anthropic) -> None: def test_method_cancel_with_all_params(self, client: Anthropic) -> None: batch = client.beta.messages.batches.cancel( message_batch_id="message_batch_id", - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -619,37 +325,11 @@ def test_path_params_results(self, client: Anthropic) -> None: class TestAsyncBatches: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_create(self, async_client: AsyncAnthropic) -> None: - batch = await async_client.beta.messages.batches.create( - requests=[ - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, + + @parametrize + async def test_method_create(self, async_client: AsyncAnthropic) -> None: + batch = await async_client.beta.messages.batches.create( + requests=[ { "custom_id": "my-custom-id-1", "params": { @@ -662,7 +342,7 @@ async def test_method_create(self, async_client: AsyncAnthropic) -> None: ], "model": "claude-3-5-sonnet-20241022", }, - }, + } ], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -683,96 +363,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAnthropic) ], "model": "claude-3-5-sonnet-20241022", "metadata": {"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - "stop_sequences": ["string", "string", "string"], - "stream": False, - "system": [ - { - "text": "Today's date is 2024-06-01.", - "type": "text", - "cache_control": {"type": "ephemeral"}, - } - ], - "temperature": 1, - "tool_choice": { - "type": "auto", - "disable_parallel_tool_use": True, - }, - "tools": [ - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - ], - "top_k": 5, - "top_p": 0.7, - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - "metadata": {"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - "stop_sequences": ["string", "string", "string"], + "stop_sequences": ["string"], "stream": False, "system": [ { @@ -805,141 +396,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncAnthropic) "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - ], - "top_k": 5, - "top_p": 0.7, - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - "metadata": {"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - "stop_sequences": ["string", "string", "string"], - "stream": False, - "system": [ - { - "text": "Today's date is 2024-06-01.", - "type": "text", - "cache_control": {"type": "ephemeral"}, } ], - "temperature": 1, - "tool_choice": { - "type": "auto", - "disable_parallel_tool_use": True, - }, - "tools": [ - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - ], "top_k": 5, "top_p": 0.7, }, - }, + } ], - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -959,33 +423,7 @@ async def test_raw_response_create(self, async_client: AsyncAnthropic) -> None: ], "model": "claude-3-5-sonnet-20241022", }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, + } ], ) @@ -1010,33 +448,7 @@ async def test_streaming_response_create(self, async_client: AsyncAnthropic) -> ], "model": "claude-3-5-sonnet-20241022", }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, - { - "custom_id": "my-custom-id-1", - "params": { - "max_tokens": 1024, - "messages": [ - { - "content": "Hello, world", - "role": "user", - } - ], - "model": "claude-3-5-sonnet-20241022", - }, - }, + } ], ) as response: assert not response.is_closed @@ -1058,7 +470,7 @@ async def test_method_retrieve(self, async_client: AsyncAnthropic) -> None: async def test_method_retrieve_with_all_params(self, async_client: AsyncAnthropic) -> None: batch = await async_client.beta.messages.batches.retrieve( message_batch_id="message_batch_id", - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) @@ -1104,7 +516,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncAnthropic) - after_id="after_id", before_id="before_id", limit=1, - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(AsyncPage[BetaMessageBatch], batch, path=["response"]) @@ -1139,7 +551,7 @@ async def test_method_cancel(self, async_client: AsyncAnthropic) -> None: async def test_method_cancel_with_all_params(self, async_client: AsyncAnthropic) -> None: batch = await async_client.beta.messages.batches.cancel( message_batch_id="message_batch_id", - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageBatch, batch, path=["response"]) diff --git a/tests/api_resources/beta/prompt_caching/test_messages.py b/tests/api_resources/beta/prompt_caching/test_messages.py index edd8ecf3..4ecb1624 100644 --- a/tests/api_resources/beta/prompt_caching/test_messages.py +++ b/tests/api_resources/beta/prompt_caching/test_messages.py @@ -43,7 +43,7 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No ], model="claude-3-5-sonnet-20241022", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, system=[ { @@ -75,47 +75,11 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No "name": "x", "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(PromptCachingBetaMessage, message, path=["response"]) @@ -185,7 +149,7 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No model="claude-3-5-sonnet-20241022", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], system=[ { "text": "Today's date is 2024-06-01.", @@ -216,47 +180,11 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No "name": "x", "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) message_stream.response.close() @@ -329,7 +257,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn ], model="claude-3-5-sonnet-20241022", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, system=[ { @@ -361,47 +289,11 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn "name": "x", "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(PromptCachingBetaMessage, message, path=["response"]) @@ -471,7 +363,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn model="claude-3-5-sonnet-20241022", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], system=[ { "text": "Today's date is 2024-06-01.", @@ -502,47 +394,11 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn "name": "x", "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) await message_stream.response.aclose() diff --git a/tests/api_resources/beta/test_messages.py b/tests/api_resources/beta/test_messages.py index 17c82308..ba7776cd 100644 --- a/tests/api_resources/beta/test_messages.py +++ b/tests/api_resources/beta/test_messages.py @@ -46,7 +46,7 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No ], model="claude-3-5-sonnet-20241022", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, system=[ { @@ -79,49 +79,11 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessage, message, path=["response"]) @@ -191,7 +153,7 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No model="claude-3-5-sonnet-20241022", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], system=[ { "text": "Today's date is 2024-06-01.", @@ -223,49 +185,11 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) message_stream.response.close() @@ -315,15 +239,7 @@ def test_method_count_tokens(self, client: Anthropic) -> None: { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", ) @@ -336,15 +252,7 @@ def test_method_count_tokens_with_all_params(self, client: Anthropic) -> None: { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", system=[ @@ -377,47 +285,9 @@ def test_method_count_tokens_with_all_params(self, client: Anthropic) -> None: "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, + } ], - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageTokensCount, message, path=["response"]) @@ -428,15 +298,7 @@ def test_raw_response_count_tokens(self, client: Anthropic) -> None: { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", ) @@ -453,15 +315,7 @@ def test_streaming_response_count_tokens(self, client: Anthropic) -> None: { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", ) as response: @@ -503,7 +357,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn ], model="claude-3-5-sonnet-20241022", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, system=[ { @@ -536,49 +390,11 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessage, message, path=["response"]) @@ -648,7 +464,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn model="claude-3-5-sonnet-20241022", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], system=[ { "text": "Today's date is 2024-06-01.", @@ -680,49 +496,11 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, + } ], top_k=5, top_p=0.7, - betas=["string", "string", "string"], + betas=["string"], ) await message_stream.response.aclose() @@ -772,15 +550,7 @@ async def test_method_count_tokens(self, async_client: AsyncAnthropic) -> None: { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", ) @@ -793,15 +563,7 @@ async def test_method_count_tokens_with_all_params(self, async_client: AsyncAnth { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", system=[ @@ -834,47 +596,9 @@ async def test_method_count_tokens_with_all_params(self, async_client: AsyncAnth "cache_control": {"type": "ephemeral"}, "description": "Get the current weather in a given location", "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "cache_control": {"type": "ephemeral"}, - "description": "Get the current weather in a given location", - "type": "custom", - }, + } ], - betas=["string", "string", "string"], + betas=["string"], ) assert_matches_type(BetaMessageTokensCount, message, path=["response"]) @@ -885,15 +609,7 @@ async def test_raw_response_count_tokens(self, async_client: AsyncAnthropic) -> { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", ) @@ -910,15 +626,7 @@ async def test_streaming_response_count_tokens(self, async_client: AsyncAnthropi { "content": "string", "role": "user", - }, - { - "content": "string", - "role": "user", - }, - { - "content": "string", - "role": "user", - }, + } ], model="string", ) as response: diff --git a/tests/api_resources/test_completions.py b/tests/api_resources/test_completions.py index cf87d9fe..24374a13 100644 --- a/tests/api_resources/test_completions.py +++ b/tests/api_resources/test_completions.py @@ -33,7 +33,7 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No model="string", prompt="\n\nHuman: Hello, world!\n\nAssistant:", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, temperature=1, top_k=5, @@ -87,7 +87,7 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No prompt="\n\nHuman: Hello, world!\n\nAssistant:", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], temperature=1, top_k=5, top_p=0.7, @@ -143,7 +143,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn model="string", prompt="\n\nHuman: Hello, world!\n\nAssistant:", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, temperature=1, top_k=5, @@ -197,7 +197,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn prompt="\n\nHuman: Hello, world!\n\nAssistant:", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], temperature=1, top_k=5, top_p=0.7, diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index 5219f7c4..533918dd 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -44,7 +44,7 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No ], model="claude-3-5-sonnet-20241022", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, system=[ { @@ -74,41 +74,7 @@ def test_method_create_with_all_params_overload_1(self, client: Anthropic) -> No }, "name": "x", "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, @@ -181,7 +147,7 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No model="claude-3-5-sonnet-20241022", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], system=[ { "text": "Today's date is 2024-06-01.", @@ -210,41 +176,7 @@ def test_method_create_with_all_params_overload_2(self, client: Anthropic) -> No }, "name": "x", "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, @@ -330,7 +262,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn ], model="claude-3-5-sonnet-20241022", metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], stream=False, system=[ { @@ -360,41 +292,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn }, "name": "x", "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, @@ -467,7 +365,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn model="claude-3-5-sonnet-20241022", stream=True, metadata={"user_id": "13803d75-b4b5-4c3e-b2a2-6f21399b021b"}, - stop_sequences=["string", "string", "string"], + stop_sequences=["string"], system=[ { "text": "Today's date is 2024-06-01.", @@ -496,41 +394,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn }, "name": "x", "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, - { - "input_schema": { - "type": "object", - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string", - }, - "unit": { - "description": "Unit for the output - one of (celsius, fahrenheit)", - "type": "string", - }, - }, - }, - "name": "x", - "description": "Get the current weather in a given location", - }, + } ], top_k=5, top_p=0.7, diff --git a/tests/lib/test_bedrock.py b/tests/lib/test_bedrock.py index c3af6875..280cb3ae 100644 --- a/tests/lib/test_bedrock.py +++ b/tests/lib/test_bedrock.py @@ -91,3 +91,36 @@ async def test_messages_retries_async(respx_mock: MockRouter) -> None: calls[1].request.url == "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke" ) + + +@pytest.mark.respx() +def test_application_inference_profile(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"}, headers={"retry-after-ms": "10"}), + httpx.Response(200, json={"foo": "bar"}), + ] + ) + + sync_client.messages.create( + max_tokens=1024, + messages=[ + { + "role": "user", + "content": "Say hello there!", + } + ], + model="arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/jf2sje1c0jnb", + ) + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 2 + + assert ( + calls[0].request.url + == "https://bedrock-runtime.us-east-1.amazonaws.com/model/arn:aws:bedrock:us-east-1:123456789012:application-inference-profile%2Fjf2sje1c0jnb/invoke" + ) + assert ( + calls[1].request.url + == "https://bedrock-runtime.us-east-1.amazonaws.com/model/arn:aws:bedrock:us-east-1:123456789012:application-inference-profile%2Fjf2sje1c0jnb/invoke" + ) diff --git a/tests/test_client.py b/tests/test_client.py index ef682a44..36825b9f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,11 +4,14 @@ import gc import os +import sys import json import asyncio import inspect +import subprocess import tracemalloc from typing import Any, Union, cast +from textwrap import dedent from unittest import mock from typing_extensions import Literal @@ -805,7 +808,7 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], - [-1100, "", 7.8], # test large number potentially overflowing + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -1779,7 +1782,7 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], - [-1100, "", 7.8], # test large number potentially overflowing + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -1987,3 +1990,38 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: ) as response: assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + def test_get_platform(self) -> None: + # A previous implementation of asyncify could leave threads unterminated when + # used with nest_asyncio. + # + # Since nest_asyncio.apply() is global and cannot be un-applied, this + # test is run in a separate process to avoid affecting other tests. + test_code = dedent(""" + import asyncio + import nest_asyncio + import threading + + from anthropic._utils import asyncify + from anthropic._base_client import get_platform + + async def test_main() -> None: + result = await asyncify(get_platform)() + print(result) + for thread in threading.enumerate(): + print(thread.name) + + nest_asyncio.apply() + asyncio.run(test_main()) + """) + with subprocess.Popen( + [sys.executable, "-c", test_code], + text=True, + ) as process: + try: + process.wait(2) + if process.returncode: + raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") + except subprocess.TimeoutExpired as e: + process.kill() + raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py index ae245e69..ba2abbd1 100644 --- a/tests/test_legacy_response.py +++ b/tests/test_legacy_response.py @@ -91,6 +91,26 @@ def test_response_parse_custom_model(client: Anthropic) -> None: assert obj.bar == 2 +def test_response_basemodel_request_id(client: Anthropic) -> None: + response = LegacyAPIResponse( + raw=httpx.Response( + 200, + headers={"request-id": "my-req-id"}, + content=json.dumps({"foo": "hello!", "bar": 2}), + ), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + obj = response.parse(to=CustomModel) + assert obj._request_id == "my-req-id" + assert obj.foo == "hello!" + assert obj.bar == 2 + assert obj.to_dict() == {"foo": "hello!", "bar": 2} + + def test_response_parse_annotated_type(client: Anthropic) -> None: response = LegacyAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), diff --git a/tests/test_models.py b/tests/test_models.py index 966e5a60..278485c2 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -561,6 +561,14 @@ class Model(BaseModel): m.model_dump(warnings=False) +def test_compat_method_no_error_for_warnings() -> None: + class Model(BaseModel): + foo: Optional[str] + + m = Model(foo="hello") + assert isinstance(model_dump(m, warnings=False), dict) + + def test_to_json() -> None: class Model(BaseModel): foo: Optional[str] = Field(alias="FOO", default=None) diff --git a/tests/test_response.py b/tests/test_response.py index 48c069e2..b334f6d8 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -156,6 +156,47 @@ async def test_async_response_parse_custom_model(async_client: AsyncAnthropic) - assert obj.bar == 2 +def test_response_basemodel_request_id(client: Anthropic) -> None: + response = APIResponse( + raw=httpx.Response( + 200, + headers={"request-id": "my-req-id"}, + content=json.dumps({"foo": "hello!", "bar": 2}), + ), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + obj = response.parse(to=CustomModel) + assert obj._request_id == "my-req-id" + assert obj.foo == "hello!" + assert obj.bar == 2 + assert obj.to_dict() == {"foo": "hello!", "bar": 2} + + +@pytest.mark.asyncio +async def test_async_response_basemodel_request_id(client: Anthropic) -> None: + response = AsyncAPIResponse( + raw=httpx.Response( + 200, + headers={"request-id": "my-req-id"}, + content=json.dumps({"foo": "hello!", "bar": 2}), + ), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + obj = await response.parse(to=CustomModel) + assert obj._request_id == "my-req-id" + assert obj.foo == "hello!" + assert obj.bar == 2 + assert obj.to_dict() == {"foo": "hello!", "bar": 2} + + def test_response_parse_annotated_type(client: Anthropic) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),