Skip to content

Commit 11678b3

Browse files
committed
chore(internal): simplify http snapshots
1 parent c6499b9 commit 11678b3

35 files changed

Lines changed: 3996 additions & 798 deletions

File tree

.inline-snapshot/external/cd8d3d185e7a993935ab650e0e5c6a7970758bac7d0487f9b1afaad2cc095f3c.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"python.analysis.importFormat": "relative",
2+
"python.analysis.importFormat": "relative",
3+
"python.testing.pytestArgs": ["tests"],
4+
"python.testing.unittestEnabled": false,
5+
"python.testing.pytestEnabled": true
36
}

CONTRIBUTING.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ $ ./scripts/test
9999
### Snapshots
100100

101101
Some tests use [inline-snapshot](https://15r10nk.github.io/inline-snapshot/latest/). To update them after making changes, rerun the tests with the `--inline-snapshot=fix` and `-n0` options:
102-
103102
```bash
104103
./scripts/test --inline-snapshot=fix -n0
105104
```
@@ -108,15 +107,14 @@ Some tests use [inline-snapshot](https://15r10nk.github.io/inline-snapshot/lates
108107
> `inline-snapshot` is incompatible with [pytest-xdist](https://github.com/pytest-dev/pytest-xdist), so you need to disable parallel execution `(-n0)` when using the `--inline-snapshot` option.
109108
110109
In addition, some tests capture snapshots of the HTTP requests they make.
111-
To refresh these snapshots, run the tests with the `ANTHROPIC_LIVE=1` environment variable enabled.
112-
110+
To refresh these snapshots, run the tests with the `--http-record` flag:
113111
```bash
114-
ANTHROPIC_LIVE=1 ./scripts/test --inline-snapshot=fix
112+
./scripts/test --inline-snapshot=fix --http-record -n0
115113
```
116114

117115
> [!NOTE]
118-
> Sometimes it makes sense to update only the inline snapshots `(--inline-snapshot=fix)` without refreshing the HTTP snapshots `(ANTHROPIC_LIVE=1)`.
119-
> This is useful when the endpoint hasnt changed, but your code handles the response differently and the assertions need updating.
116+
> Sometimes it makes sense to update only the inline snapshots `(--inline-snapshot=fix)` without refreshing the HTTP snapshots `(--http-record)`.
117+
> This is useful when the endpoint hasn't changed, but your code handles the response differently and the assertions need updating.
120118
121119
## Linting and formatting
122120

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ dev = [
7474
"pytest-xdist>=3.6.1",
7575
"inline-snapshot>=0.28.0",
7676
"griffe>=1",
77+
"http-snapshot[httpx]==0.1.8",
7778
]
7879
pydantic-v1 = [
7980
"pydantic>=1.9.0,<2",

tests/conftest.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
import os
66
import logging
7-
from typing import TYPE_CHECKING, Iterator, AsyncIterator
7+
from typing import TYPE_CHECKING, Any, Iterator, AsyncIterator
88

99
import httpx
1010
import pytest
11+
import inline_snapshot
12+
from http_snapshot import SnapshotSerializerOptions
1113
from pytest_asyncio import is_async_test
14+
from http_snapshot.httpx import HttpxSyncSnapshotClient, HttpxAsyncSnapshotClient
1215

1316
from anthropic import Anthropic, AsyncAnthropic, DefaultAioHttpClient
1417
from anthropic._utils import is_dict
@@ -20,6 +23,18 @@
2023

2124
logging.getLogger("anthropic").setLevel(logging.DEBUG)
2225

26+
SNAPSHOT_RESPONSE_HEADERS_EXCLUDE = [
27+
"date",
28+
"request-id",
29+
"anthropic-organization-id",
30+
"x-envoy-upstream-service-time",
31+
"cf-ray",
32+
]
33+
34+
SNAPSHOT_REQUEST_HEADERS_EXCLUDE = [
35+
"x-api-key",
36+
]
37+
2338

2439
# automatically add `pytest.mark.asyncio()` to all of our async tests
2540
# so we don't have to add that boilerplate everywhere
@@ -58,6 +73,41 @@ def client(request: FixtureRequest) -> Iterator[Anthropic]:
5873
yield client
5974

6075

76+
@pytest.fixture
77+
def http_snapshot_serializer_options() -> SnapshotSerializerOptions:
78+
return SnapshotSerializerOptions(
79+
exclude_response_headers=SNAPSHOT_RESPONSE_HEADERS_EXCLUDE,
80+
exclude_request_headers=SNAPSHOT_REQUEST_HEADERS_EXCLUDE,
81+
include_request=True,
82+
)
83+
84+
85+
@pytest.fixture(scope="function")
86+
def snapshot_client(
87+
is_recording: bool,
88+
http_snapshot_serializer_options: SnapshotSerializerOptions,
89+
http_snapshot: inline_snapshot.Snapshot[Any],
90+
) -> Iterator[Anthropic]:
91+
with HttpxSyncSnapshotClient(
92+
http_snapshot, is_recording, serializer_options=http_snapshot_serializer_options
93+
) as snapshot_client:
94+
with Anthropic(http_client=snapshot_client, api_key=None if is_recording else api_key) as client:
95+
yield client
96+
97+
98+
@pytest.fixture(scope="function")
99+
async def async_snapshot_client(
100+
is_recording: bool,
101+
http_snapshot_serializer_options: SnapshotSerializerOptions,
102+
http_snapshot: inline_snapshot.Snapshot[Any],
103+
) -> AsyncIterator[AsyncAnthropic]:
104+
async with HttpxAsyncSnapshotClient(
105+
http_snapshot, is_recording, serializer_options=http_snapshot_serializer_options
106+
) as snapshot_client:
107+
client = AsyncAnthropic(http_client=snapshot_client, api_key=None if is_recording else api_key)
108+
yield client
109+
110+
61111
@pytest.fixture(scope="session")
62112
async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncAnthropic]:
63113
param = getattr(request, "param", True)

tests/lib/_parse/__inline_snapshot__/test_beta_messages/TestAsyncMessages.test_parse_uses_output_config/044ce19d-3e9c-42d2-90e7-759c978cd94b.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

tests/lib/_parse/__inline_snapshot__/test_beta_messages/TestAsyncMessages.test_stream_with_raw_schema/48aac7c3-f271-47b3-854b-af4ed31e10bb.json

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
[
2+
{
3+
"request": {
4+
"method": "POST",
5+
"url": "https://api.anthropic.com/v1/messages?beta=true",
6+
"headers": {
7+
"host": "api.anthropic.com",
8+
"accept-encoding": "gzip, deflate",
9+
"connection": "keep-alive",
10+
"x-stainless-timeout": "NOT_GIVEN",
11+
"accept": "application/json",
12+
"content-type": "application/json",
13+
"user-agent": "AsyncAnthropic/Python 0.82.0",
14+
"x-stainless-lang": "python",
15+
"x-stainless-package-version": "0.82.0",
16+
"x-stainless-os": "MacOS",
17+
"x-stainless-arch": "arm64",
18+
"x-stainless-runtime": "CPython",
19+
"x-stainless-runtime-version": "3.9.18",
20+
"x-stainless-async": "async:asyncio",
21+
"anthropic-version": "2023-06-01",
22+
"x-stainless-helper-method": "stream",
23+
"x-stainless-stream-helper": "beta.messages",
24+
"anthropic-beta": "structured-outputs-2025-11-13",
25+
"x-stainless-retry-count": "0",
26+
"x-stainless-read-timeout": "600",
27+
"content-length": "276"
28+
},
29+
"body": {
30+
"max_tokens": 1024,
31+
"messages": [
32+
{
33+
"role": "user",
34+
"content": "Extract order IDs from the following text:\n\nOrder 12345\nOrder 67890"
35+
}
36+
],
37+
"model": "claude-sonnet-4-5",
38+
"output_config": {
39+
"format": {
40+
"type": "json_schema",
41+
"schema": {
42+
"type": "array",
43+
"items": {
44+
"type": "integer"
45+
}
46+
}
47+
}
48+
},
49+
"stream": true
50+
}
51+
},
52+
"response": {
53+
"status_code": 429,
54+
"headers": {
55+
"content-type": "application/json",
56+
"content-length": "606",
57+
"connection": "keep-alive",
58+
"x-should-retry": "true",
59+
"x-robots-tag": "none",
60+
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
61+
"server": "cloudflare",
62+
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
63+
"cf-cache-status": "DYNAMIC"
64+
},
65+
"body": {
66+
"type": "error",
67+
"error": {
68+
"type": "rate_limit_error",
69+
"message": "This request would exceed your organization's rate limit of 20,000,000 prompt bytes per hour (org: 5576611f-a1ee-427c-9800-298d9579899c, model: claude-sonnet-4-5-20250929). For details, refer to: https://docs.claude.com/en/api/rate-limits. You can see the response headers for current usage. Please reduce the prompt length or the maximum tokens requested, or try again later. You may also contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase."
70+
},
71+
"request_id": "req_011CYK5kgv9zS6EpxPDLPkLb"
72+
}
73+
}
74+
},
75+
{
76+
"request": {
77+
"method": "POST",
78+
"url": "https://api.anthropic.com/v1/messages?beta=true",
79+
"headers": {
80+
"host": "api.anthropic.com",
81+
"accept-encoding": "gzip, deflate",
82+
"connection": "keep-alive",
83+
"x-stainless-timeout": "NOT_GIVEN",
84+
"accept": "application/json",
85+
"content-type": "application/json",
86+
"user-agent": "AsyncAnthropic/Python 0.82.0",
87+
"x-stainless-lang": "python",
88+
"x-stainless-package-version": "0.82.0",
89+
"x-stainless-os": "MacOS",
90+
"x-stainless-arch": "arm64",
91+
"x-stainless-runtime": "CPython",
92+
"x-stainless-runtime-version": "3.9.18",
93+
"x-stainless-async": "async:asyncio",
94+
"anthropic-version": "2023-06-01",
95+
"x-stainless-helper-method": "stream",
96+
"x-stainless-stream-helper": "beta.messages",
97+
"anthropic-beta": "structured-outputs-2025-11-13",
98+
"x-stainless-retry-count": "1",
99+
"x-stainless-read-timeout": "600",
100+
"content-length": "276"
101+
},
102+
"body": {
103+
"max_tokens": 1024,
104+
"messages": [
105+
{
106+
"role": "user",
107+
"content": "Extract order IDs from the following text:\n\nOrder 12345\nOrder 67890"
108+
}
109+
],
110+
"model": "claude-sonnet-4-5",
111+
"output_config": {
112+
"format": {
113+
"type": "json_schema",
114+
"schema": {
115+
"type": "array",
116+
"items": {
117+
"type": "integer"
118+
}
119+
}
120+
}
121+
},
122+
"stream": true
123+
}
124+
},
125+
"response": {
126+
"status_code": 200,
127+
"headers": {
128+
"content-type": "text/event-stream; charset=utf-8",
129+
"connection": "keep-alive",
130+
"cache-control": "no-cache",
131+
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
132+
"server": "cloudflare",
133+
"x-robots-tag": "none",
134+
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
135+
"cf-cache-status": "DYNAMIC"
136+
},
137+
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01RKp6d9GFfSNpDdeCX3f8Td\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":135,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"[\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"12\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"345,\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"67890]\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":135,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":10} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n"
138+
}
139+
}
140+
]

0 commit comments

Comments
 (0)