Skip to content

Commit a77db76

Browse files
jar-stripeclaude
andcommitted
Redesign Ref to use _APIRequestor instead of StripeClient
Ref now stores _requestor and uses requestor.request() for fetch, matching the Event.fetch_related_object pattern. Added _construct_from classmethod so the deserialization pipeline can instantiate Ref with the requestor. Updated _convert_to_stripe_object to handle Ref types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
1 parent d176033 commit a77db76

File tree

3 files changed

+66
-37
lines changed

3 files changed

+66
-37
lines changed

stripe/_util.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,21 @@ def _convert_to_stripe_object(
268268
else:
269269
klass = StripeObject
270270

271-
obj = klass._construct_from(
272-
values=resp,
273-
last_response=stripe_response,
274-
requestor=requestor,
275-
api_mode=api_mode,
276-
)
271+
from stripe.v2._ref import Ref
272+
273+
if issubclass(klass, Ref):
274+
obj = klass._construct_from(
275+
values=resp,
276+
requestor=requestor,
277+
api_mode=api_mode,
278+
)
279+
else:
280+
obj = klass._construct_from(
281+
values=resp,
282+
last_response=stripe_response,
283+
requestor=requestor,
284+
api_mode=api_mode,
285+
)
277286

278287
# We only need to update _retrieve_params when special params were
279288
# actually passed. Otherwise, leave it as is as the list / search result

stripe/v2/_ref.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
from typing_extensions import TYPE_CHECKING
66

7-
from stripe._util import get_api_mode
7+
from stripe._api_mode import ApiMode
88

99
if TYPE_CHECKING:
10-
from stripe._stripe_client import StripeClient
10+
from stripe._api_requestor import _APIRequestor
11+
from stripe._stripe_response import StripeResponse
1112

1213
T = TypeVar("T")
1314

@@ -30,50 +31,69 @@ class Ref(Generic[T]):
3031
url: str
3132
"""The URL path that can be used to fetch the referenced resource."""
3233

33-
_client: Optional["StripeClient"]
34+
_requestor: Optional["_APIRequestor"]
3435

3536
def __init__(
36-
self, parsed_body: Dict[str, Any], client: Optional["StripeClient"]
37+
self,
38+
parsed_body: Dict[str, Any],
39+
requestor: Optional["_APIRequestor"],
3740
) -> None:
3841
self.type = parsed_body["type"]
3942
self.id = parsed_body["id"]
4043
self.url = parsed_body["url"]
41-
self._client = client
44+
self._requestor = requestor
45+
46+
@classmethod
47+
def _construct_from(
48+
cls,
49+
*,
50+
values: Dict[str, Any],
51+
last_response: Optional["StripeResponse"] = None,
52+
requestor: "_APIRequestor",
53+
api_mode: ApiMode,
54+
) -> "Ref[Any]":
55+
return cls(values, requestor=requestor)
4256

4357
def fetch(self) -> T:
4458
"""
4559
Fetches the full API resource that this reference points to.
4660
47-
Raises ``RuntimeError`` if no client is associated with this ``Ref``.
61+
Raises ``RuntimeError`` if no requestor is associated with this ``Ref``.
4862
"""
49-
if self._client is None:
63+
if self._requestor is None:
5064
raise RuntimeError(
51-
"Cannot call fetch() on a Ref that has no associated StripeClient."
65+
"Cannot call fetch() on a Ref that has no associated requestor."
5266
)
53-
response = self._client.raw_request(
54-
"get",
55-
self.url,
56-
usage=["ref_fetch"],
67+
return cast(
68+
T,
69+
self._requestor.request(
70+
"get",
71+
self.url,
72+
base_address="api",
73+
usage=["ref_fetch"],
74+
),
5775
)
58-
return cast(T, self._client.deserialize(response, api_mode=get_api_mode(self.url)))
5976

6077
async def fetch_async(self) -> T:
6178
"""
6279
Fetches the full API resource that this reference points to,
6380
using an async HTTP request.
6481
65-
Raises ``RuntimeError`` if no client is associated with this ``Ref``.
82+
Raises ``RuntimeError`` if no requestor is associated with this ``Ref``.
6683
"""
67-
if self._client is None:
84+
if self._requestor is None:
6885
raise RuntimeError(
69-
"Cannot call fetch_async() on a Ref that has no associated StripeClient."
86+
"Cannot call fetch_async() on a Ref that has no associated requestor."
7087
)
71-
response = await self._client.raw_request_async(
72-
"get",
73-
self.url,
74-
usage=["ref_fetch", "ref_fetch_async"],
88+
return cast(
89+
T,
90+
await self._requestor.request_async(
91+
"get",
92+
self.url,
93+
base_address="api",
94+
usage=["ref_fetch", "ref_fetch_async"],
95+
),
7596
)
76-
return cast(T, self._client.deserialize(response, api_mode=get_api_mode(self.url)))
7797

7898
def __repr__(self) -> str:
7999
return f"<Ref type={self.type} id={self.id} url={self.url}>"

tests/test_v2_ref.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def test_construction(self):
1414
"id": "mtr_123",
1515
"url": "/v1/billing/meters/mtr_123",
1616
}
17-
ref: Ref[Meter] = Ref(parsed, client=None)
17+
ref: Ref[Meter] = Ref(parsed, requestor=None)
1818

1919
assert ref.type == "billing.meter"
2020
assert ref.id == "mtr_123"
@@ -26,29 +26,29 @@ def test_repr(self):
2626
"id": "mtr_123",
2727
"url": "/v1/billing/meters/mtr_123",
2828
}
29-
ref: Ref[Meter] = Ref(parsed, client=None)
29+
ref: Ref[Meter] = Ref(parsed, requestor=None)
3030
assert repr(ref) == "<Ref type=billing.meter id=mtr_123 url=/v1/billing/meters/mtr_123>"
3131

32-
def test_fetch_raises_without_client(self):
32+
def test_fetch_raises_without_requestor(self):
3333
parsed = {
3434
"type": "billing.meter",
3535
"id": "mtr_123",
3636
"url": "/v1/billing/meters/mtr_123",
3737
}
38-
ref: Ref[Meter] = Ref(parsed, client=None)
38+
ref: Ref[Meter] = Ref(parsed, requestor=None)
3939

40-
with pytest.raises(RuntimeError, match="no associated StripeClient"):
40+
with pytest.raises(RuntimeError, match="no associated requestor"):
4141
ref.fetch()
4242

43-
def test_fetch_async_raises_without_client(self):
43+
def test_fetch_async_raises_without_requestor(self):
4444
parsed = {
4545
"type": "billing.meter",
4646
"id": "mtr_123",
4747
"url": "/v1/billing/meters/mtr_123",
4848
}
49-
ref: Ref[Meter] = Ref(parsed, client=None)
49+
ref: Ref[Meter] = Ref(parsed, requestor=None)
5050

51-
with pytest.raises(RuntimeError, match="no associated StripeClient"):
51+
with pytest.raises(RuntimeError, match="no associated requestor"):
5252
import asyncio
5353
asyncio.get_event_loop().run_until_complete(ref.fetch_async())
5454

@@ -78,7 +78,7 @@ def test_fetch_v1_resource(self, http_client_mock: HTTPClientMock):
7878
"id": "mtr_123",
7979
"url": meter_path,
8080
}
81-
ref: Ref[Meter] = Ref(parsed, client=client)
81+
ref: Ref[Meter] = Ref(parsed, requestor=client._requestor)
8282
result = ref.fetch()
8383

8484
http_client_mock.assert_requested(
@@ -121,7 +121,7 @@ def test_fetch_v2_resource(self, http_client_mock: HTTPClientMock):
121121
"id": "evt_123",
122122
"url": event_path,
123123
}
124-
ref: Ref[Event] = Ref(parsed, client=client)
124+
ref: Ref[Event] = Ref(parsed, requestor=client._requestor)
125125
result = ref.fetch()
126126

127127
http_client_mock.assert_requested(

0 commit comments

Comments
 (0)