airbyte-cdk 6.33.3__py3-none-any.whl → 6.33.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,8 @@ properties:
40
40
  "$ref": "#/definitions/Spec"
41
41
  concurrency_level:
42
42
  "$ref": "#/definitions/ConcurrencyLevel"
43
+ api_budget:
44
+ "$ref": "#/definitions/HTTPAPIBudget"
43
45
  metadata:
44
46
  type: object
45
47
  description: For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.
@@ -794,7 +796,7 @@ definitions:
794
796
  description: This option is used to adjust the upper and lower boundaries of each datetime window to beginning and end of the provided target period (day, week, month)
795
797
  type: object
796
798
  required:
797
- - target
799
+ - target
798
800
  properties:
799
801
  target:
800
802
  title: Target
@@ -1365,6 +1367,170 @@ definitions:
1365
1367
  $parameters:
1366
1368
  type: object
1367
1369
  additional_properties: true
1370
+ HTTPAPIBudget:
1371
+ title: HTTP API Budget
1372
+ description: >
1373
+ Defines how many requests can be made to the API in a given time frame. `HTTPAPIBudget` extracts the remaining
1374
+ call count and the reset time from HTTP response headers using the header names provided by
1375
+ `ratelimit_remaining_header` and `ratelimit_reset_header`. Only requests using `HttpRequester`
1376
+ are rate-limited; custom components that bypass `HttpRequester` are not covered by this budget.
1377
+ type: object
1378
+ required:
1379
+ - type
1380
+ - policies
1381
+ properties:
1382
+ type:
1383
+ type: string
1384
+ enum: [HTTPAPIBudget]
1385
+ policies:
1386
+ title: Policies
1387
+ description: List of call rate policies that define how many calls are allowed.
1388
+ type: array
1389
+ items:
1390
+ anyOf:
1391
+ - "$ref": "#/definitions/FixedWindowCallRatePolicy"
1392
+ - "$ref": "#/definitions/MovingWindowCallRatePolicy"
1393
+ - "$ref": "#/definitions/UnlimitedCallRatePolicy"
1394
+ ratelimit_reset_header:
1395
+ title: Rate Limit Reset Header
1396
+ description: The HTTP response header name that indicates when the rate limit resets.
1397
+ type: string
1398
+ default: "ratelimit-reset"
1399
+ ratelimit_remaining_header:
1400
+ title: Rate Limit Remaining Header
1401
+ description: The HTTP response header name that indicates the number of remaining allowed calls.
1402
+ type: string
1403
+ default: "ratelimit-remaining"
1404
+ status_codes_for_ratelimit_hit:
1405
+ title: Status Codes for Rate Limit Hit
1406
+ description: List of HTTP status codes that indicate a rate limit has been hit.
1407
+ type: array
1408
+ items:
1409
+ type: integer
1410
+ default: [429]
1411
+ additionalProperties: true
1412
+ FixedWindowCallRatePolicy:
1413
+ title: Fixed Window Call Rate Policy
1414
+ description: A policy that allows a fixed number of calls within a specific time window.
1415
+ type: object
1416
+ required:
1417
+ - type
1418
+ - period
1419
+ - call_limit
1420
+ - matchers
1421
+ properties:
1422
+ type:
1423
+ type: string
1424
+ enum: [FixedWindowCallRatePolicy]
1425
+ period:
1426
+ title: Period
1427
+ description: The time interval for the rate limit window.
1428
+ type: string
1429
+ call_limit:
1430
+ title: Call Limit
1431
+ description: The maximum number of calls allowed within the period.
1432
+ type: integer
1433
+ matchers:
1434
+ title: Matchers
1435
+ description: List of matchers that define which requests this policy applies to.
1436
+ type: array
1437
+ items:
1438
+ "$ref": "#/definitions/HttpRequestRegexMatcher"
1439
+ additionalProperties: true
1440
+ MovingWindowCallRatePolicy:
1441
+ title: Moving Window Call Rate Policy
1442
+ description: A policy that allows a fixed number of calls within a moving time window.
1443
+ type: object
1444
+ required:
1445
+ - type
1446
+ - rates
1447
+ - matchers
1448
+ properties:
1449
+ type:
1450
+ type: string
1451
+ enum: [MovingWindowCallRatePolicy]
1452
+ rates:
1453
+ title: Rates
1454
+ description: List of rates that define the call limits for different time intervals.
1455
+ type: array
1456
+ items:
1457
+ "$ref": "#/definitions/Rate"
1458
+ matchers:
1459
+ title: Matchers
1460
+ description: List of matchers that define which requests this policy applies to.
1461
+ type: array
1462
+ items:
1463
+ "$ref": "#/definitions/HttpRequestRegexMatcher"
1464
+ additionalProperties: true
1465
+ UnlimitedCallRatePolicy:
1466
+ title: Unlimited Call Rate Policy
1467
+ description: A policy that allows unlimited calls for specific requests.
1468
+ type: object
1469
+ required:
1470
+ - type
1471
+ - matchers
1472
+ properties:
1473
+ type:
1474
+ type: string
1475
+ enum: [UnlimitedCallRatePolicy]
1476
+ matchers:
1477
+ title: Matchers
1478
+ description: List of matchers that define which requests this policy applies to.
1479
+ type: array
1480
+ items:
1481
+ "$ref": "#/definitions/HttpRequestRegexMatcher"
1482
+ additionalProperties: true
1483
+ Rate:
1484
+ title: Rate
1485
+ description: Defines a rate limit with a specific number of calls allowed within a time interval.
1486
+ type: object
1487
+ required:
1488
+ - limit
1489
+ - interval
1490
+ properties:
1491
+ limit:
1492
+ title: Limit
1493
+ description: The maximum number of calls allowed within the interval.
1494
+ type: integer
1495
+ interval:
1496
+ title: Interval
1497
+ description: The time interval for the rate limit.
1498
+ type: string
1499
+ examples:
1500
+ - "PT1H"
1501
+ - "P1D"
1502
+ additionalProperties: true
1503
+ HttpRequestRegexMatcher:
1504
+ title: HTTP Request Matcher
1505
+ description: >
1506
+ Matches HTTP requests based on method, base URL, URL path pattern, query parameters, and headers.
1507
+ Use `url_base` to specify the scheme and host (without trailing slash) and
1508
+ `url_path_pattern` to apply a regex to the request path.
1509
+ type: object
1510
+ properties:
1511
+ method:
1512
+ title: Method
1513
+ description: The HTTP method to match (e.g., GET, POST).
1514
+ type: string
1515
+ url_base:
1516
+ title: URL Base
1517
+ description: The base URL (scheme and host, e.g. "https://api.example.com") to match.
1518
+ type: string
1519
+ url_path_pattern:
1520
+ title: URL Path Pattern
1521
+ description: A regular expression pattern to match the URL path.
1522
+ type: string
1523
+ params:
1524
+ title: Parameters
1525
+ description: The query parameters to match.
1526
+ type: object
1527
+ additionalProperties: true
1528
+ headers:
1529
+ title: Headers
1530
+ description: The headers to match.
1531
+ type: object
1532
+ additionalProperties: true
1533
+ additionalProperties: true
1368
1534
  DefaultErrorHandler:
1369
1535
  title: Default Error Handler
1370
1536
  description: Component defining how to handle errors. Default behavior includes only retrying server errors (HTTP 5XX) and too many requests (HTTP 429) with an exponential backoff.
@@ -41,6 +41,7 @@ class RecordSelector(HttpSelector):
41
41
  _name: Union[InterpolatedString, str] = field(init=False, repr=False, default="")
42
42
  record_filter: Optional[RecordFilter] = None
43
43
  transformations: List[RecordTransformation] = field(default_factory=lambda: [])
44
+ transform_before_filtering: bool = False
44
45
 
45
46
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
46
47
  self._parameters = parameters
@@ -104,9 +105,17 @@ class RecordSelector(HttpSelector):
104
105
  Until we decide to move this logic away from the selector, we made this method public so that users like AsyncJobRetriever could
105
106
  share the logic of doing transformations on a set of records.
106
107
  """
107
- filtered_data = self._filter(all_data, stream_state, stream_slice, next_page_token)
108
- transformed_data = self._transform(filtered_data, stream_state, stream_slice)
109
- normalized_data = self._normalize_by_schema(transformed_data, schema=records_schema)
108
+ if self.transform_before_filtering:
109
+ transformed_data = self._transform(all_data, stream_state, stream_slice)
110
+ transformed_filtered_data = self._filter(
111
+ transformed_data, stream_state, stream_slice, next_page_token
112
+ )
113
+ else:
114
+ filtered_data = self._filter(all_data, stream_state, stream_slice, next_page_token)
115
+ transformed_filtered_data = self._transform(filtered_data, stream_state, stream_slice)
116
+ normalized_data = self._normalize_by_schema(
117
+ transformed_filtered_data, schema=records_schema
118
+ )
110
119
  for data in normalized_data:
111
120
  yield Record(data=data, stream_name=self.name, associated_slice=stream_slice)
112
121
 
@@ -137,6 +137,10 @@ class ManifestDeclarativeSource(DeclarativeSource):
137
137
  self._source_config, config
138
138
  )
139
139
 
140
+ api_budget_model = self._source_config.get("api_budget")
141
+ if api_budget_model:
142
+ self._constructor.set_api_budget(api_budget_model, config)
143
+
140
144
  source_streams = [
141
145
  self._constructor.create_component(
142
146
  DeclarativeStreamModel,
@@ -642,6 +642,48 @@ class OAuthAuthenticator(BaseModel):
642
642
  parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
643
643
 
644
644
 
645
+ class Rate(BaseModel):
646
+ class Config:
647
+ extra = Extra.allow
648
+
649
+ limit: int = Field(
650
+ ...,
651
+ description="The maximum number of calls allowed within the interval.",
652
+ title="Limit",
653
+ )
654
+ interval: str = Field(
655
+ ...,
656
+ description="The time interval for the rate limit.",
657
+ examples=["PT1H", "P1D"],
658
+ title="Interval",
659
+ )
660
+
661
+
662
+ class HttpRequestRegexMatcher(BaseModel):
663
+ class Config:
664
+ extra = Extra.allow
665
+
666
+ method: Optional[str] = Field(
667
+ None, description="The HTTP method to match (e.g., GET, POST).", title="Method"
668
+ )
669
+ url_base: Optional[str] = Field(
670
+ None,
671
+ description='The base URL (scheme and host, e.g. "https://api.example.com") to match.',
672
+ title="URL Base",
673
+ )
674
+ url_path_pattern: Optional[str] = Field(
675
+ None,
676
+ description="A regular expression pattern to match the URL path.",
677
+ title="URL Path Pattern",
678
+ )
679
+ params: Optional[Dict[str, Any]] = Field(
680
+ None, description="The query parameters to match.", title="Parameters"
681
+ )
682
+ headers: Optional[Dict[str, Any]] = Field(
683
+ None, description="The headers to match.", title="Headers"
684
+ )
685
+
686
+
645
687
  class DpathExtractor(BaseModel):
646
688
  type: Literal["DpathExtractor"]
647
689
  field_path: List[str] = Field(
@@ -1565,6 +1607,55 @@ class DatetimeBasedCursor(BaseModel):
1565
1607
  parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
1566
1608
 
1567
1609
 
1610
+ class FixedWindowCallRatePolicy(BaseModel):
1611
+ class Config:
1612
+ extra = Extra.allow
1613
+
1614
+ type: Literal["FixedWindowCallRatePolicy"]
1615
+ period: str = Field(
1616
+ ..., description="The time interval for the rate limit window.", title="Period"
1617
+ )
1618
+ call_limit: int = Field(
1619
+ ...,
1620
+ description="The maximum number of calls allowed within the period.",
1621
+ title="Call Limit",
1622
+ )
1623
+ matchers: List[HttpRequestRegexMatcher] = Field(
1624
+ ...,
1625
+ description="List of matchers that define which requests this policy applies to.",
1626
+ title="Matchers",
1627
+ )
1628
+
1629
+
1630
+ class MovingWindowCallRatePolicy(BaseModel):
1631
+ class Config:
1632
+ extra = Extra.allow
1633
+
1634
+ type: Literal["MovingWindowCallRatePolicy"]
1635
+ rates: List[Rate] = Field(
1636
+ ...,
1637
+ description="List of rates that define the call limits for different time intervals.",
1638
+ title="Rates",
1639
+ )
1640
+ matchers: List[HttpRequestRegexMatcher] = Field(
1641
+ ...,
1642
+ description="List of matchers that define which requests this policy applies to.",
1643
+ title="Matchers",
1644
+ )
1645
+
1646
+
1647
+ class UnlimitedCallRatePolicy(BaseModel):
1648
+ class Config:
1649
+ extra = Extra.allow
1650
+
1651
+ type: Literal["UnlimitedCallRatePolicy"]
1652
+ matchers: List[HttpRequestRegexMatcher] = Field(
1653
+ ...,
1654
+ description="List of matchers that define which requests this policy applies to.",
1655
+ title="Matchers",
1656
+ )
1657
+
1658
+
1568
1659
  class DefaultErrorHandler(BaseModel):
1569
1660
  type: Literal["DefaultErrorHandler"]
1570
1661
  backoff_strategies: Optional[
@@ -1696,6 +1787,39 @@ class CompositeErrorHandler(BaseModel):
1696
1787
  parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
1697
1788
 
1698
1789
 
1790
+ class HTTPAPIBudget(BaseModel):
1791
+ class Config:
1792
+ extra = Extra.allow
1793
+
1794
+ type: Literal["HTTPAPIBudget"]
1795
+ policies: List[
1796
+ Union[
1797
+ FixedWindowCallRatePolicy,
1798
+ MovingWindowCallRatePolicy,
1799
+ UnlimitedCallRatePolicy,
1800
+ ]
1801
+ ] = Field(
1802
+ ...,
1803
+ description="List of call rate policies that define how many calls are allowed.",
1804
+ title="Policies",
1805
+ )
1806
+ ratelimit_reset_header: Optional[str] = Field(
1807
+ "ratelimit-reset",
1808
+ description="The HTTP response header name that indicates when the rate limit resets.",
1809
+ title="Rate Limit Reset Header",
1810
+ )
1811
+ ratelimit_remaining_header: Optional[str] = Field(
1812
+ "ratelimit-remaining",
1813
+ description="The HTTP response header name that indicates the number of remaining allowed calls.",
1814
+ title="Rate Limit Remaining Header",
1815
+ )
1816
+ status_codes_for_ratelimit_hit: Optional[List[int]] = Field(
1817
+ [429],
1818
+ description="List of HTTP status codes that indicate a rate limit has been hit.",
1819
+ title="Status Codes for Rate Limit Hit",
1820
+ )
1821
+
1822
+
1699
1823
  class ZipfileDecoder(BaseModel):
1700
1824
  class Config:
1701
1825
  extra = Extra.allow
@@ -1724,6 +1848,7 @@ class DeclarativeSource1(BaseModel):
1724
1848
  definitions: Optional[Dict[str, Any]] = None
1725
1849
  spec: Optional[Spec] = None
1726
1850
  concurrency_level: Optional[ConcurrencyLevel] = None
1851
+ api_budget: Optional[HTTPAPIBudget] = None
1727
1852
  metadata: Optional[Dict[str, Any]] = Field(
1728
1853
  None,
1729
1854
  description="For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.",
@@ -1750,6 +1875,7 @@ class DeclarativeSource2(BaseModel):
1750
1875
  definitions: Optional[Dict[str, Any]] = None
1751
1876
  spec: Optional[Spec] = None
1752
1877
  concurrency_level: Optional[ConcurrencyLevel] = None
1878
+ api_budget: Optional[HTTPAPIBudget] = None
1753
1879
  metadata: Optional[Dict[str, Any]] = Field(
1754
1880
  None,
1755
1881
  description="For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.",
@@ -221,18 +221,27 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
221
221
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
222
222
  ExponentialBackoffStrategy as ExponentialBackoffStrategyModel,
223
223
  )
224
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
225
+ FixedWindowCallRatePolicy as FixedWindowCallRatePolicyModel,
226
+ )
224
227
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
225
228
  FlattenFields as FlattenFieldsModel,
226
229
  )
227
230
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
228
231
  GzipDecoder as GzipDecoderModel,
229
232
  )
233
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
234
+ HTTPAPIBudget as HTTPAPIBudgetModel,
235
+ )
230
236
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
231
237
  HttpComponentsResolver as HttpComponentsResolverModel,
232
238
  )
233
239
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
234
240
  HttpRequester as HttpRequesterModel,
235
241
  )
242
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
243
+ HttpRequestRegexMatcher as HttpRequestRegexMatcherModel,
244
+ )
236
245
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
237
246
  HttpResponseFilter as HttpResponseFilterModel,
238
247
  )
@@ -281,6 +290,9 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
281
290
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
282
291
  MinMaxDatetime as MinMaxDatetimeModel,
283
292
  )
293
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
294
+ MovingWindowCallRatePolicy as MovingWindowCallRatePolicyModel,
295
+ )
284
296
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
285
297
  NoAuth as NoAuthModel,
286
298
  )
@@ -299,6 +311,9 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
299
311
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
300
312
  ParentStreamConfig as ParentStreamConfigModel,
301
313
  )
314
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
315
+ Rate as RateModel,
316
+ )
302
317
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
303
318
  RecordFilter as RecordFilterModel,
304
319
  )
@@ -342,6 +357,9 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
342
357
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
343
358
  TypesMap as TypesMapModel,
344
359
  )
360
+ from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
361
+ UnlimitedCallRatePolicy as UnlimitedCallRatePolicyModel,
362
+ )
345
363
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import ValueType
346
364
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
347
365
  WaitTimeFromHeader as WaitTimeFromHeaderModel,
@@ -455,6 +473,15 @@ from airbyte_cdk.sources.message import (
455
473
  MessageRepository,
456
474
  NoopMessageRepository,
457
475
  )
476
+ from airbyte_cdk.sources.streams.call_rate import (
477
+ APIBudget,
478
+ FixedWindowCallRatePolicy,
479
+ HttpAPIBudget,
480
+ HttpRequestRegexMatcher,
481
+ MovingWindowCallRatePolicy,
482
+ Rate,
483
+ UnlimitedCallRatePolicy,
484
+ )
458
485
  from airbyte_cdk.sources.streams.concurrent.clamping import (
459
486
  ClampingEndProvider,
460
487
  ClampingStrategy,
@@ -506,6 +533,7 @@ class ModelToComponentFactory:
506
533
  self._evaluate_log_level(emit_connector_builder_messages)
507
534
  )
508
535
  self._connector_state_manager = connector_state_manager or ConnectorStateManager()
536
+ self._api_budget: Optional[Union[APIBudget, HttpAPIBudget]] = None
509
537
 
510
538
  def _init_mappings(self) -> None:
511
539
  self.PYDANTIC_MODEL_TO_CONSTRUCTOR: Mapping[Type[BaseModel], Callable[..., Any]] = {
@@ -590,6 +618,12 @@ class ModelToComponentFactory:
590
618
  StreamConfigModel: self.create_stream_config,
591
619
  ComponentMappingDefinitionModel: self.create_components_mapping_definition,
592
620
  ZipfileDecoderModel: self.create_zipfile_decoder,
621
+ HTTPAPIBudgetModel: self.create_http_api_budget,
622
+ FixedWindowCallRatePolicyModel: self.create_fixed_window_call_rate_policy,
623
+ MovingWindowCallRatePolicyModel: self.create_moving_window_call_rate_policy,
624
+ UnlimitedCallRatePolicyModel: self.create_unlimited_call_rate_policy,
625
+ RateModel: self.create_rate,
626
+ HttpRequestRegexMatcherModel: self.create_http_request_matcher,
593
627
  }
594
628
 
595
629
  # Needed for the case where we need to perform a second parse on the fields of a custom component
@@ -1902,6 +1936,8 @@ class ModelToComponentFactory:
1902
1936
  )
1903
1937
  )
1904
1938
 
1939
+ api_budget = self._api_budget
1940
+
1905
1941
  request_options_provider = InterpolatedRequestOptionsProvider(
1906
1942
  request_body_data=model.request_body_data,
1907
1943
  request_body_json=model.request_body_json,
@@ -1922,6 +1958,7 @@ class ModelToComponentFactory:
1922
1958
  path=model.path,
1923
1959
  authenticator=authenticator,
1924
1960
  error_handler=error_handler,
1961
+ api_budget=api_budget,
1925
1962
  http_method=HttpMethod[model.http_method.value],
1926
1963
  request_options_provider=request_options_provider,
1927
1964
  config=config,
@@ -2378,6 +2415,8 @@ class ModelToComponentFactory:
2378
2415
  if model.record_filter
2379
2416
  else None
2380
2417
  )
2418
+
2419
+ transform_before_filtering = False
2381
2420
  if client_side_incremental_sync:
2382
2421
  record_filter = ClientSideIncrementalRecordFilterDecorator(
2383
2422
  config=config,
@@ -2387,6 +2426,8 @@ class ModelToComponentFactory:
2387
2426
  else None,
2388
2427
  **client_side_incremental_sync,
2389
2428
  )
2429
+ transform_before_filtering = True
2430
+
2390
2431
  schema_normalization = (
2391
2432
  TypeTransformer(SCHEMA_TRANSFORMER_TYPE_MAPPING[model.schema_normalization])
2392
2433
  if isinstance(model.schema_normalization, SchemaNormalizationModel)
@@ -2401,6 +2442,7 @@ class ModelToComponentFactory:
2401
2442
  transformations=transformations or [],
2402
2443
  schema_normalization=schema_normalization,
2403
2444
  parameters=model.parameters or {},
2445
+ transform_before_filtering=transform_before_filtering,
2404
2446
  )
2405
2447
 
2406
2448
  @staticmethod
@@ -2921,3 +2963,84 @@ class ModelToComponentFactory:
2921
2963
  return isinstance(parser.inner_parser, JsonParser)
2922
2964
  else:
2923
2965
  return False
2966
+
2967
+ def create_http_api_budget(
2968
+ self, model: HTTPAPIBudgetModel, config: Config, **kwargs: Any
2969
+ ) -> HttpAPIBudget:
2970
+ policies = [
2971
+ self._create_component_from_model(model=policy, config=config)
2972
+ for policy in model.policies
2973
+ ]
2974
+
2975
+ return HttpAPIBudget(
2976
+ policies=policies,
2977
+ ratelimit_reset_header=model.ratelimit_reset_header or "ratelimit-reset",
2978
+ ratelimit_remaining_header=model.ratelimit_remaining_header or "ratelimit-remaining",
2979
+ status_codes_for_ratelimit_hit=model.status_codes_for_ratelimit_hit or [429],
2980
+ )
2981
+
2982
+ def create_fixed_window_call_rate_policy(
2983
+ self, model: FixedWindowCallRatePolicyModel, config: Config, **kwargs: Any
2984
+ ) -> FixedWindowCallRatePolicy:
2985
+ matchers = [
2986
+ self._create_component_from_model(model=matcher, config=config)
2987
+ for matcher in model.matchers
2988
+ ]
2989
+
2990
+ # Set the initial reset timestamp to 10 days from now.
2991
+ # This value will be updated by the first request.
2992
+ return FixedWindowCallRatePolicy(
2993
+ next_reset_ts=datetime.datetime.now() + datetime.timedelta(days=10),
2994
+ period=parse_duration(model.period),
2995
+ call_limit=model.call_limit,
2996
+ matchers=matchers,
2997
+ )
2998
+
2999
+ def create_moving_window_call_rate_policy(
3000
+ self, model: MovingWindowCallRatePolicyModel, config: Config, **kwargs: Any
3001
+ ) -> MovingWindowCallRatePolicy:
3002
+ rates = [
3003
+ self._create_component_from_model(model=rate, config=config) for rate in model.rates
3004
+ ]
3005
+ matchers = [
3006
+ self._create_component_from_model(model=matcher, config=config)
3007
+ for matcher in model.matchers
3008
+ ]
3009
+ return MovingWindowCallRatePolicy(
3010
+ rates=rates,
3011
+ matchers=matchers,
3012
+ )
3013
+
3014
+ def create_unlimited_call_rate_policy(
3015
+ self, model: UnlimitedCallRatePolicyModel, config: Config, **kwargs: Any
3016
+ ) -> UnlimitedCallRatePolicy:
3017
+ matchers = [
3018
+ self._create_component_from_model(model=matcher, config=config)
3019
+ for matcher in model.matchers
3020
+ ]
3021
+
3022
+ return UnlimitedCallRatePolicy(
3023
+ matchers=matchers,
3024
+ )
3025
+
3026
+ def create_rate(self, model: RateModel, config: Config, **kwargs: Any) -> Rate:
3027
+ return Rate(
3028
+ limit=model.limit,
3029
+ interval=parse_duration(model.interval),
3030
+ )
3031
+
3032
+ def create_http_request_matcher(
3033
+ self, model: HttpRequestRegexMatcherModel, config: Config, **kwargs: Any
3034
+ ) -> HttpRequestRegexMatcher:
3035
+ return HttpRequestRegexMatcher(
3036
+ method=model.method,
3037
+ url_base=model.url_base,
3038
+ url_path_pattern=model.url_path_pattern,
3039
+ params=model.params,
3040
+ headers=model.headers,
3041
+ )
3042
+
3043
+ def set_api_budget(self, component_definition: ComponentDefinition, config: Config) -> None:
3044
+ self._api_budget = self.create_component(
3045
+ model_type=HTTPAPIBudgetModel, component_definition=component_definition, config=config
3046
+ )
@@ -22,6 +22,7 @@ from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_req
22
22
  )
23
23
  from airbyte_cdk.sources.declarative.requesters.requester import HttpMethod, Requester
24
24
  from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
25
+ from airbyte_cdk.sources.streams.call_rate import APIBudget
25
26
  from airbyte_cdk.sources.streams.http import HttpClient
26
27
  from airbyte_cdk.sources.streams.http.error_handlers import ErrorHandler
27
28
  from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
@@ -55,6 +56,7 @@ class HttpRequester(Requester):
55
56
  http_method: Union[str, HttpMethod] = HttpMethod.GET
56
57
  request_options_provider: Optional[InterpolatedRequestOptionsProvider] = None
57
58
  error_handler: Optional[ErrorHandler] = None
59
+ api_budget: Optional[APIBudget] = None
58
60
  disable_retries: bool = False
59
61
  message_repository: MessageRepository = NoopMessageRepository()
60
62
  use_cache: bool = False
@@ -91,6 +93,7 @@ class HttpRequester(Requester):
91
93
  name=self.name,
92
94
  logger=self.logger,
93
95
  error_handler=self.error_handler,
96
+ api_budget=self.api_budget,
94
97
  authenticator=self._authenticator,
95
98
  use_cache=self.use_cache,
96
99
  backoff_strategy=backoff_strategies,