Skip to content

Commit 1b448ba

Browse files
devin-ai-integration[bot]bot_apk
andcommitted
fix(cdk): sync injected api_budget onto LimiterSession and guard with APIBudget isinstance
Co-Authored-By: bot_apk <apk@cognition.ai>
1 parent 0787f79 commit 1b448ba

2 files changed

Lines changed: 33 additions & 6 deletions

File tree

airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,10 +1884,14 @@ def create_custom_component(self, model: Any, config: Config, **kwargs: Any) ->
18841884

18851885
@staticmethod
18861886
def _sync_injected_api_budget_with_http_client(custom_requester: HttpRequester) -> None:
1887-
"""
1888-
Custom requesters can replace `_http_client` in `__post_init__` without forwarding `api_budget`.
1889-
If the factory injected a manifest-level budget and the replacement client kept the default empty
1890-
budget, sync the active client back to the requester's injected budget.
1887+
"""Align an injected `api_budget` with the active `HttpClient` on custom requesters.
1888+
1889+
Custom requesters can replace `_http_client` in `__post_init__` without forwarding
1890+
`api_budget`. If the factory injected a manifest-level budget and the replacement
1891+
client kept the default empty `APIBudget`, point both the client and its underlying
1892+
`LimiterSession`/`CachedLimiterSession` at the injected budget so rate-limiting is
1893+
actually enforced at request time. Non-`APIBudget` implementations (custom
1894+
`AbstractAPIBudget` subclasses) are left untouched.
18911895
"""
18921896
http_client = getattr(custom_requester, "_http_client", None)
18931897
http_client_api_budget = getattr(http_client, "_api_budget", None)
@@ -1901,8 +1905,17 @@ def _sync_injected_api_budget_with_http_client(custom_requester: HttpRequester)
19011905
):
19021906
return
19031907

1904-
if len(getattr(http_client_api_budget, "_policies", [])) == 0:
1908+
if (
1909+
isinstance(http_client_api_budget, APIBudget)
1910+
and len(http_client_api_budget._policies) == 0
1911+
):
19051912
http_client._api_budget = injected_api_budget
1913+
http_client_session = getattr(http_client, "_session", None)
1914+
if (
1915+
http_client_session is not None
1916+
and getattr(http_client_session, "_api_budget", None) is http_client_api_budget
1917+
):
1918+
http_client_session._api_budget = injected_api_budget
19061919

19071920
@staticmethod
19081921
def _get_class_from_fully_qualified_class_name(

unit_tests/sources/declarative/parsers/test_model_to_component_factory.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4364,8 +4364,11 @@ def test_api_budget_propagated_to_custom_requester_subclass_of_http_requester():
43644364
assert len(custom_requester.api_budget._policies) == 1
43654365
policy = custom_requester.api_budget._policies[0]
43664366
assert isinstance(policy, MovingWindowCallRatePolicy)
4367-
# Also verify the underlying HttpClient received the same budget
4367+
# Verify the underlying HttpClient AND its LimiterSession both received the same
4368+
# budget: rate-limiting is enforced on the session at send() time, so asserting only
4369+
# on the client field is insufficient to prove the policies are actually active.
43684370
assert custom_requester._http_client._api_budget is custom_requester.api_budget
4371+
assert custom_requester._http_client._session._api_budget is custom_requester.api_budget
43694372

43704373

43714374
def test_api_budget_propagated_to_custom_requester_that_replaces_http_client():
@@ -4414,6 +4417,10 @@ def test_api_budget_propagated_to_custom_requester_that_replaces_http_client():
44144417
assert isinstance(custom_requester, HttpRequester)
44154418
assert custom_requester.api_budget is not None
44164419
assert custom_requester._http_client._api_budget is custom_requester.api_budget
4420+
# The LimiterSession holds its own reference to the budget (captured at client
4421+
# construction time) and is what actually enforces rate limits on send(). Assert
4422+
# it was synced too, otherwise the injected budget is effectively inert.
4423+
assert custom_requester._http_client._session._api_budget is custom_requester.api_budget
44174424

44184425

44194426
def test_api_budget_not_overwriting_non_empty_budget_on_replaced_http_client():
@@ -4458,6 +4465,13 @@ def test_api_budget_not_overwriting_non_empty_budget_on_replaced_http_client():
44584465
assert custom_requester.api_budget is not None
44594466
assert custom_requester._http_client._api_budget is not custom_requester.api_budget
44604467
assert len(custom_requester._http_client._api_budget._policies) == 1
4468+
# The client's own budget must remain wired into its LimiterSession as well, so
4469+
# the sync step never silently swaps an intentionally-installed budget out from
4470+
# under the active session.
4471+
assert (
4472+
custom_requester._http_client._session._api_budget
4473+
is custom_requester._http_client._api_budget
4474+
)
44614475

44624476

44634477
def test_api_budget_not_propagated_to_non_http_requester_custom_components():

0 commit comments

Comments
 (0)