Skip to content

Commit 4a4dc9f

Browse files
authored
chore(internal): simplify http snapshots (#1092)
1 parent 143efcc commit 4a4dc9f

File tree

34 files changed

+3917
-794
lines changed

34 files changed

+3917
-794
lines changed

.inline-snapshot/external/cd8d3d185e7a993935ab650e0e5c6a7970758bac7d0487f9b1afaad2cc095f3c.json

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

CONTRIBUTING.md

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

100100
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:
101-
102101
```bash
103102
./scripts/test --inline-snapshot=fix -n0
104103
```
@@ -107,15 +106,14 @@ Some tests use [inline-snapshot](https://15r10nk.github.io/inline-snapshot/lates
107106
> `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.
108107
109108
In addition, some tests capture snapshots of the HTTP requests they make.
110-
To refresh these snapshots, run the tests with the `ANTHROPIC_LIVE=1` environment variable enabled.
111-
109+
To refresh these snapshots, run the tests with the `--http-record` flag:
112110
```bash
113-
ANTHROPIC_LIVE=1 ./scripts/test --inline-snapshot=fix
111+
./scripts/test --inline-snapshot=fix --http-record -n0
114112
```
115113

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

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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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-12-15",
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": 200,
54+
"headers": {
55+
"content-type": "text/event-stream; charset=utf-8",
56+
"connection": "keep-alive",
57+
"cache-control": "no-cache",
58+
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
59+
"server": "cloudflare",
60+
"x-robots-tag": "none",
61+
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
62+
"cf-cache-status": "DYNAMIC"
63+
},
64+
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01LtFpmR8SbmiK2kRA5sWXNi\",\"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"
65+
}
66+
}
67+
]
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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": "600",
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": "beta.messages.parse",
23+
"anthropic-beta": "structured-outputs-2025-12-15",
24+
"x-stainless-raw-response": "true",
25+
"x-stainless-retry-count": "0",
26+
"x-stainless-read-timeout": "600",
27+
"content-length": "432"
28+
},
29+
"body": {
30+
"max_tokens": 1024,
31+
"messages": [
32+
{
33+
"role": "user",
34+
"content": "Extract the user's name and age from the following text:\n\nMy name is John Doe and I am 30 years old."
35+
}
36+
],
37+
"model": "claude-sonnet-4-5",
38+
"output_config": {
39+
"format": {
40+
"schema": {
41+
"type": "object",
42+
"title": "User",
43+
"properties": {
44+
"name": {
45+
"type": "string",
46+
"title": "Name"
47+
},
48+
"age": {
49+
"type": "integer",
50+
"title": "Age"
51+
}
52+
},
53+
"additionalProperties": false,
54+
"required": [
55+
"name",
56+
"age"
57+
]
58+
},
59+
"type": "json_schema"
60+
}
61+
}
62+
}
63+
},
64+
"response": {
65+
"status_code": 200,
66+
"headers": {
67+
"content-type": "application/json",
68+
"connection": "keep-alive",
69+
"x-robots-tag": "none",
70+
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
71+
"server": "cloudflare",
72+
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
73+
"cf-cache-status": "DYNAMIC"
74+
},
75+
"body": {
76+
"model": "claude-sonnet-4-5-20250929",
77+
"id": "msg_01EojSKby3oqoP7mb4PHsMJ7",
78+
"type": "message",
79+
"role": "assistant",
80+
"content": [
81+
{
82+
"type": "text",
83+
"text": "{\"name\":\"John Doe\",\"age\":30}"
84+
}
85+
],
86+
"stop_reason": "end_turn",
87+
"stop_sequence": null,
88+
"usage": {
89+
"input_tokens": 222,
90+
"cache_creation_input_tokens": 0,
91+
"cache_read_input_tokens": 0,
92+
"cache_creation": {
93+
"ephemeral_5m_input_tokens": 0,
94+
"ephemeral_1h_input_tokens": 0
95+
},
96+
"output_tokens": 14,
97+
"service_tier": "standard",
98+
"inference_geo": "not_available"
99+
}
100+
}
101+
}
102+
}
103+
]

0 commit comments

Comments
 (0)