airbyte-cdk 6.33.0.dev0__py3-none-any.whl → 6.33.1.dev0__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.
Files changed (21) hide show
  1. airbyte_cdk/sources/declarative/auth/token.py +3 -8
  2. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +13 -2
  3. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +18 -8
  4. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +7 -6
  5. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +14 -8
  6. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +40 -36
  7. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +4 -2
  8. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +26 -18
  9. airbyte_cdk/sources/declarative/requesters/http_requester.py +5 -1
  10. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +6 -5
  11. airbyte_cdk/sources/declarative/requesters/request_option.py +83 -4
  12. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +7 -6
  13. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +4 -1
  14. airbyte_cdk/sources/streams/call_rate.py +63 -0
  15. airbyte_cdk/utils/mapping_helpers.py +86 -27
  16. {airbyte_cdk-6.33.0.dev0.dist-info → airbyte_cdk-6.33.1.dev0.dist-info}/METADATA +1 -1
  17. {airbyte_cdk-6.33.0.dev0.dist-info → airbyte_cdk-6.33.1.dev0.dist-info}/RECORD +21 -21
  18. {airbyte_cdk-6.33.0.dev0.dist-info → airbyte_cdk-6.33.1.dev0.dist-info}/LICENSE.txt +0 -0
  19. {airbyte_cdk-6.33.0.dev0.dist-info → airbyte_cdk-6.33.1.dev0.dist-info}/LICENSE_SHORT +0 -0
  20. {airbyte_cdk-6.33.0.dev0.dist-info → airbyte_cdk-6.33.1.dev0.dist-info}/WHEEL +0 -0
  21. {airbyte_cdk-6.33.0.dev0.dist-info → airbyte_cdk-6.33.1.dev0.dist-info}/entry_points.txt +0 -0
@@ -5,7 +5,7 @@
5
5
  import base64
6
6
  import logging
7
7
  from dataclasses import InitVar, dataclass
8
- from typing import Any, Mapping, Union
8
+ from typing import Any, Mapping, MutableMapping, Union
9
9
 
10
10
  import requests
11
11
  from cachetools import TTLCache, cached
@@ -45,11 +45,6 @@ class ApiKeyAuthenticator(DeclarativeAuthenticator):
45
45
  config: Config
46
46
  parameters: InitVar[Mapping[str, Any]]
47
47
 
48
- def __post_init__(self, parameters: Mapping[str, Any]) -> None:
49
- self._field_name = InterpolatedString.create(
50
- self.request_option.field_name, parameters=parameters
51
- )
52
-
53
48
  @property
54
49
  def auth_header(self) -> str:
55
50
  options = self._get_request_options(RequestOptionType.header)
@@ -60,9 +55,9 @@ class ApiKeyAuthenticator(DeclarativeAuthenticator):
60
55
  return self.token_provider.get_token()
61
56
 
62
57
  def _get_request_options(self, option_type: RequestOptionType) -> Mapping[str, Any]:
63
- options = {}
58
+ options: MutableMapping[str, Any] = {}
64
59
  if self.request_option.inject_into == option_type:
65
- options[self._field_name.eval(self.config)] = self.token
60
+ self.request_option.inject_into_request(options, self.token, self.config)
66
61
  return options
67
62
 
68
63
  def get_request_params(self) -> Mapping[str, Any]:
@@ -475,10 +475,21 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
475
475
  # Also a temporary hack. In the legacy Stream implementation, as part of the read,
476
476
  # set_initial_state() is called to instantiate incoming state on the cursor. Although we no
477
477
  # longer rely on the legacy low-code cursor for concurrent checkpointing, low-code components
478
- # like StopConditionPaginationStrategyDecorator and ClientSideIncrementalRecordFilterDecorator
479
- # still rely on a DatetimeBasedCursor that is properly initialized with state.
478
+ # like StopConditionPaginationStrategyDecorator still rely on a DatetimeBasedCursor that is
479
+ # properly initialized with state.
480
480
  if retriever.cursor:
481
481
  retriever.cursor.set_initial_state(stream_state=stream_state)
482
+
483
+ # Similar to above, the ClientSideIncrementalRecordFilterDecorator cursor is a separate instance
484
+ # from the one initialized on the SimpleRetriever, so it also must also have state initialized
485
+ # for semi-incremental streams using is_client_side_incremental to filter properly
486
+ if isinstance(retriever.record_selector, RecordSelector) and isinstance(
487
+ retriever.record_selector.record_filter, ClientSideIncrementalRecordFilterDecorator
488
+ ):
489
+ retriever.record_selector.record_filter._cursor.set_initial_state(
490
+ stream_state=stream_state
491
+ ) # type: ignore # After non-concurrent cursors are deprecated we can remove these cursor workarounds
492
+
482
493
  # We zero it out here, but since this is a cursor reference, the state is still properly
483
494
  # instantiated for the other components that reference it
484
495
  retriever.cursor = None
@@ -1478,7 +1478,7 @@ definitions:
1478
1478
  description: List of matchers that define which requests this policy applies to.
1479
1479
  type: array
1480
1480
  items:
1481
- "$ref": "#/definitions/HttpRequestMatcher"
1481
+ "$ref": "#/definitions/HttpRequestRegexMatcher"
1482
1482
  additionalProperties: true
1483
1483
  MovingWindowCallRatePolicy:
1484
1484
  title: Moving Window Call Rate Policy
@@ -1503,7 +1503,7 @@ definitions:
1503
1503
  description: List of matchers that define which requests this policy applies to.
1504
1504
  type: array
1505
1505
  items:
1506
- "$ref": "#/definitions/HttpRequestMatcher"
1506
+ "$ref": "#/definitions/HttpRequestRegexMatcher"
1507
1507
  additionalProperties: true
1508
1508
  UnlimitedCallRatePolicy:
1509
1509
  title: Unlimited Call Rate Policy
@@ -1521,7 +1521,7 @@ definitions:
1521
1521
  description: List of matchers that define which requests this policy applies to.
1522
1522
  type: array
1523
1523
  items:
1524
- "$ref": "#/definitions/HttpRequestMatcher"
1524
+ "$ref": "#/definitions/HttpRequestRegexMatcher"
1525
1525
  additionalProperties: true
1526
1526
  Rate:
1527
1527
  title: Rate
@@ -1541,7 +1541,7 @@ definitions:
1541
1541
  type: string
1542
1542
  format: duration
1543
1543
  additionalProperties: true
1544
- HttpRequestMatcher:
1544
+ HttpRequestRegexMatcher:
1545
1545
  title: HTTP Request Matcher
1546
1546
  description: >
1547
1547
  Matches HTTP requests based on method, base URL, URL path pattern, query parameters, and headers.
@@ -3054,25 +3054,35 @@ definitions:
3054
3054
  enum: [RequestPath]
3055
3055
  RequestOption:
3056
3056
  title: Request Option
3057
- description: Specifies the key field and where in the request a component's value should be injected.
3057
+ description: Specifies the key field or path and where in the request a component's value should be injected.
3058
3058
  type: object
3059
3059
  required:
3060
3060
  - type
3061
- - field_name
3062
3061
  - inject_into
3063
3062
  properties:
3064
3063
  type:
3065
3064
  type: string
3066
3065
  enum: [RequestOption]
3067
3066
  field_name:
3068
- title: Request Option
3069
- description: Configures which key should be used in the location that the descriptor is being injected into
3067
+ title: Field Name
3068
+ description: Configures which key should be used in the location that the descriptor is being injected into. We hope to eventually deprecate this field in favor of `field_path` for all request_options, but must currently maintain it for backwards compatibility in the Builder.
3070
3069
  type: string
3071
3070
  examples:
3072
3071
  - segment_id
3073
3072
  interpolation_context:
3074
3073
  - config
3075
3074
  - parameters
3075
+ field_path:
3076
+ title: Field Path
3077
+ description: Configures a path to be used for nested structures in JSON body requests (e.g. GraphQL queries)
3078
+ type: array
3079
+ items:
3080
+ type: string
3081
+ examples:
3082
+ - ["data", "viewer", "id"]
3083
+ interpolation_context:
3084
+ - config
3085
+ - parameters
3076
3086
  inject_into:
3077
3087
  title: Inject Into
3078
3088
  description: Configures where the descriptor should be set on the HTTP requests. Note that request parameters that are already encoded in the URL path will not be duplicated.
@@ -365,14 +365,15 @@ class DatetimeBasedCursor(DeclarativeCursor):
365
365
  options: MutableMapping[str, Any] = {}
366
366
  if not stream_slice:
367
367
  return options
368
+
368
369
  if self.start_time_option and self.start_time_option.inject_into == option_type:
369
- options[self.start_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore # field_name is always casted to an interpolated string
370
- self._partition_field_start.eval(self.config)
371
- )
370
+ start_time_value = stream_slice.get(self._partition_field_start.eval(self.config))
371
+ self.start_time_option.inject_into_request(options, start_time_value, self.config)
372
+
372
373
  if self.end_time_option and self.end_time_option.inject_into == option_type:
373
- options[self.end_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore [union-attr]
374
- self._partition_field_end.eval(self.config)
375
- )
374
+ end_time_value = stream_slice.get(self._partition_field_end.eval(self.config))
375
+ self.end_time_option.inject_into_request(options, end_time_value, self.config)
376
+
376
377
  return options
377
378
 
378
379
  def should_be_synced(self, record: Record) -> bool:
@@ -657,7 +657,7 @@ class Rate(BaseModel):
657
657
  )
658
658
 
659
659
 
660
- class HttpRequestMatcher(BaseModel):
660
+ class HttpRequestRegexMatcher(BaseModel):
661
661
  class Config:
662
662
  extra = Extra.allow
663
663
 
@@ -1240,11 +1240,17 @@ class InjectInto(Enum):
1240
1240
 
1241
1241
  class RequestOption(BaseModel):
1242
1242
  type: Literal["RequestOption"]
1243
- field_name: str = Field(
1244
- ...,
1245
- description="Configures which key should be used in the location that the descriptor is being injected into",
1243
+ field_name: Optional[str] = Field(
1244
+ None,
1245
+ description="Configures which key should be used in the location that the descriptor is being injected into. We hope to eventually deprecate this field in favor of `field_path` for all request_options, but must currently maintain it for backwards compatibility in the Builder.",
1246
1246
  examples=["segment_id"],
1247
- title="Request Option",
1247
+ title="Field Name",
1248
+ )
1249
+ field_path: Optional[List[str]] = Field(
1250
+ None,
1251
+ description="Configures a path to be used for nested structures in JSON body requests (e.g. GraphQL queries)",
1252
+ examples=[["data", "viewer", "id"]],
1253
+ title="Field Path",
1248
1254
  )
1249
1255
  inject_into: InjectInto = Field(
1250
1256
  ...,
@@ -1636,7 +1642,7 @@ class FixedWindowCallRatePolicy(BaseModel):
1636
1642
  description="The maximum number of calls allowed within the period.",
1637
1643
  title="Call Limit",
1638
1644
  )
1639
- matchers: List[HttpRequestMatcher] = Field(
1645
+ matchers: List[HttpRequestRegexMatcher] = Field(
1640
1646
  ...,
1641
1647
  description="List of matchers that define which requests this policy applies to.",
1642
1648
  title="Matchers",
@@ -1653,7 +1659,7 @@ class MovingWindowCallRatePolicy(BaseModel):
1653
1659
  description="List of rates that define the call limits for different time intervals.",
1654
1660
  title="Rates",
1655
1661
  )
1656
- matchers: List[HttpRequestMatcher] = Field(
1662
+ matchers: List[HttpRequestRegexMatcher] = Field(
1657
1663
  ...,
1658
1664
  description="List of matchers that define which requests this policy applies to.",
1659
1665
  title="Matchers",
@@ -1665,7 +1671,7 @@ class UnlimitedCallRatePolicy(BaseModel):
1665
1671
  extra = Extra.allow
1666
1672
 
1667
1673
  type: Literal["UnlimitedCallRatePolicy"]
1668
- matchers: List[HttpRequestMatcher] = Field(
1674
+ matchers: List[HttpRequestRegexMatcher] = Field(
1669
1675
  ...,
1670
1676
  description="List of matchers that define which requests this policy applies to.",
1671
1677
  title="Matchers",
@@ -251,7 +251,7 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
251
251
  HttpRequester as HttpRequesterModel,
252
252
  )
253
253
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
254
- HttpRequestMatcher as HttpRequestMatcherModel,
254
+ HttpRequestRegexMatcher as HttpRequestRegexMatcherModel,
255
255
  )
256
256
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
257
257
  HttpResponseFilter as HttpResponseFilterModel,
@@ -494,7 +494,7 @@ from airbyte_cdk.sources.streams.call_rate import (
494
494
  APIBudget,
495
495
  FixedWindowCallRatePolicy,
496
496
  HttpAPIBudget,
497
- HttpRequestMatcher,
497
+ HttpRequestRegexMatcher,
498
498
  MovingWindowCallRatePolicy,
499
499
  Rate,
500
500
  UnlimitedCallRatePolicy,
@@ -644,7 +644,7 @@ class ModelToComponentFactory:
644
644
  MovingWindowCallRatePolicyModel: self.create_moving_window_call_rate_policy,
645
645
  UnlimitedCallRatePolicyModel: self.create_unlimited_call_rate_policy,
646
646
  RateModel: self.create_rate,
647
- HttpRequestMatcherModel: self.create_http_request_matcher,
647
+ HttpRequestRegexMatcherModel: self.create_http_request_matcher,
648
648
  }
649
649
 
650
650
  # Needed for the case where we need to perform a second parse on the fields of a custom component
@@ -771,8 +771,8 @@ class ModelToComponentFactory:
771
771
  }
772
772
  return names_to_types[value_type]
773
773
 
774
- @staticmethod
775
774
  def create_api_key_authenticator(
775
+ self,
776
776
  model: ApiKeyAuthenticatorModel,
777
777
  config: Config,
778
778
  token_provider: Optional[TokenProvider] = None,
@@ -794,10 +794,8 @@ class ModelToComponentFactory:
794
794
  )
795
795
 
796
796
  request_option = (
797
- RequestOption(
798
- inject_into=RequestOptionType(model.inject_into.inject_into.value),
799
- field_name=model.inject_into.field_name,
800
- parameters=model.parameters or {},
797
+ self._create_component_from_model(
798
+ model.inject_into, config, parameters=model.parameters or {}
801
799
  )
802
800
  if model.inject_into
803
801
  else RequestOption(
@@ -806,6 +804,7 @@ class ModelToComponentFactory:
806
804
  parameters=model.parameters or {},
807
805
  )
808
806
  )
807
+
809
808
  return ApiKeyAuthenticator(
810
809
  token_provider=(
811
810
  token_provider
@@ -887,7 +886,7 @@ class ModelToComponentFactory:
887
886
  token_provider=token_provider,
888
887
  )
889
888
  else:
890
- return ModelToComponentFactory.create_api_key_authenticator(
889
+ return self.create_api_key_authenticator(
891
890
  ApiKeyAuthenticatorModel(
892
891
  type="ApiKeyAuthenticator",
893
892
  api_token="",
@@ -1527,19 +1526,15 @@ class ModelToComponentFactory:
1527
1526
  )
1528
1527
 
1529
1528
  end_time_option = (
1530
- RequestOption(
1531
- inject_into=RequestOptionType(model.end_time_option.inject_into.value),
1532
- field_name=model.end_time_option.field_name,
1533
- parameters=model.parameters or {},
1529
+ self._create_component_from_model(
1530
+ model.end_time_option, config, parameters=model.parameters or {}
1534
1531
  )
1535
1532
  if model.end_time_option
1536
1533
  else None
1537
1534
  )
1538
1535
  start_time_option = (
1539
- RequestOption(
1540
- inject_into=RequestOptionType(model.start_time_option.inject_into.value),
1541
- field_name=model.start_time_option.field_name,
1542
- parameters=model.parameters or {},
1536
+ self._create_component_from_model(
1537
+ model.start_time_option, config, parameters=model.parameters or {}
1543
1538
  )
1544
1539
  if model.start_time_option
1545
1540
  else None
@@ -1610,19 +1605,15 @@ class ModelToComponentFactory:
1610
1605
  cursor_model = model.incremental_sync
1611
1606
 
1612
1607
  end_time_option = (
1613
- RequestOption(
1614
- inject_into=RequestOptionType(cursor_model.end_time_option.inject_into.value),
1615
- field_name=cursor_model.end_time_option.field_name,
1616
- parameters=cursor_model.parameters or {},
1608
+ self._create_component_from_model(
1609
+ cursor_model.end_time_option, config, parameters=cursor_model.parameters or {}
1617
1610
  )
1618
1611
  if cursor_model.end_time_option
1619
1612
  else None
1620
1613
  )
1621
1614
  start_time_option = (
1622
- RequestOption(
1623
- inject_into=RequestOptionType(cursor_model.start_time_option.inject_into.value),
1624
- field_name=cursor_model.start_time_option.field_name,
1625
- parameters=cursor_model.parameters or {},
1615
+ self._create_component_from_model(
1616
+ cursor_model.start_time_option, config, parameters=cursor_model.parameters or {}
1626
1617
  )
1627
1618
  if cursor_model.start_time_option
1628
1619
  else None
@@ -2191,16 +2182,11 @@ class ModelToComponentFactory:
2191
2182
  additional_jwt_payload=model.additional_jwt_payload,
2192
2183
  )
2193
2184
 
2194
- @staticmethod
2195
2185
  def create_list_partition_router(
2196
- model: ListPartitionRouterModel, config: Config, **kwargs: Any
2186
+ self, model: ListPartitionRouterModel, config: Config, **kwargs: Any
2197
2187
  ) -> ListPartitionRouter:
2198
2188
  request_option = (
2199
- RequestOption(
2200
- inject_into=RequestOptionType(model.request_option.inject_into.value),
2201
- field_name=model.request_option.field_name,
2202
- parameters=model.parameters or {},
2203
- )
2189
+ self._create_component_from_model(model.request_option, config)
2204
2190
  if model.request_option
2205
2191
  else None
2206
2192
  )
@@ -2396,7 +2382,25 @@ class ModelToComponentFactory:
2396
2382
  model: RequestOptionModel, config: Config, **kwargs: Any
2397
2383
  ) -> RequestOption:
2398
2384
  inject_into = RequestOptionType(model.inject_into.value)
2399
- return RequestOption(field_name=model.field_name, inject_into=inject_into, parameters={})
2385
+ field_path: Optional[List[Union[InterpolatedString, str]]] = (
2386
+ [
2387
+ InterpolatedString.create(segment, parameters=kwargs.get("parameters", {}))
2388
+ for segment in model.field_path
2389
+ ]
2390
+ if model.field_path
2391
+ else None
2392
+ )
2393
+ field_name = (
2394
+ InterpolatedString.create(model.field_name, parameters=kwargs.get("parameters", {}))
2395
+ if model.field_name
2396
+ else None
2397
+ )
2398
+ return RequestOption(
2399
+ field_name=field_name,
2400
+ field_path=field_path,
2401
+ inject_into=inject_into,
2402
+ parameters=kwargs.get("parameters", {}),
2403
+ )
2400
2404
 
2401
2405
  def create_record_selector(
2402
2406
  self,
@@ -3036,9 +3040,9 @@ class ModelToComponentFactory:
3036
3040
  )
3037
3041
 
3038
3042
  def create_http_request_matcher(
3039
- self, model: HttpRequestMatcherModel, config: Config, **kwargs: Any
3040
- ) -> HttpRequestMatcher:
3041
- return HttpRequestMatcher(
3043
+ self, model: HttpRequestRegexMatcherModel, config: Config, **kwargs: Any
3044
+ ) -> HttpRequestRegexMatcher:
3045
+ return HttpRequestRegexMatcher(
3042
3046
  method=model.method,
3043
3047
  url_base=model.url_base,
3044
3048
  url_path_pattern=model.url_path_pattern,
@@ -3,7 +3,7 @@
3
3
  #
4
4
 
5
5
  from dataclasses import InitVar, dataclass
6
- from typing import Any, Iterable, List, Mapping, Optional, Union
6
+ from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Union
7
7
 
8
8
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
9
9
  from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
@@ -100,7 +100,9 @@ class ListPartitionRouter(PartitionRouter):
100
100
  ):
101
101
  slice_value = stream_slice.get(self._cursor_field.eval(self.config))
102
102
  if slice_value:
103
- return {self.request_option.field_name.eval(self.config): slice_value} # type: ignore # field_name is always casted to InterpolatedString
103
+ options: MutableMapping[str, Any] = {}
104
+ self.request_option.inject_into_request(options, slice_value, self.config)
105
+ return options
104
106
  else:
105
107
  return {}
106
108
  else:
@@ -4,7 +4,7 @@
4
4
  import copy
5
5
  import logging
6
6
  from dataclasses import InitVar, dataclass
7
- from typing import TYPE_CHECKING, Any, Iterable, List, Mapping, Optional, Union
7
+ from typing import TYPE_CHECKING, Any, Iterable, List, Mapping, MutableMapping, Optional, Union
8
8
 
9
9
  import dpath
10
10
 
@@ -118,7 +118,7 @@ class SubstreamPartitionRouter(PartitionRouter):
118
118
  def _get_request_option(
119
119
  self, option_type: RequestOptionType, stream_slice: Optional[StreamSlice]
120
120
  ) -> Mapping[str, Any]:
121
- params = {}
121
+ params: MutableMapping[str, Any] = {}
122
122
  if stream_slice:
123
123
  for parent_config in self.parent_stream_configs:
124
124
  if (
@@ -128,13 +128,7 @@ class SubstreamPartitionRouter(PartitionRouter):
128
128
  key = parent_config.partition_field.eval(self.config) # type: ignore # partition_field is always casted to an interpolated string
129
129
  value = stream_slice.get(key)
130
130
  if value:
131
- params.update(
132
- {
133
- parent_config.request_option.field_name.eval( # type: ignore [union-attr]
134
- config=self.config
135
- ): value
136
- }
137
- )
131
+ parent_config.request_option.inject_into_request(params, value, self.config)
138
132
  return params
139
133
 
140
134
  def stream_slices(self) -> Iterable[StreamSlice]:
@@ -305,23 +299,33 @@ class SubstreamPartitionRouter(PartitionRouter):
305
299
 
306
300
  def _migrate_child_state_to_parent_state(self, stream_state: StreamState) -> StreamState:
307
301
  """
308
- Migrate the child stream state to the parent stream's state format.
302
+ Migrate the child or global stream state into the parent stream's state format.
303
+
304
+ This method converts the child stream state—or, if present, the global state—into a format that is
305
+ compatible with parent streams that use incremental synchronization. The migration occurs only for
306
+ parent streams with incremental dependencies. It filters out per-partition states and retains only the
307
+ global state in the form {cursor_field: cursor_value}.
309
308
 
310
- This method converts the global or child state into a format compatible with parent
311
- streams. The migration occurs only for parent streams with incremental dependencies.
312
- The method filters out per-partition states and retains only the global state in the
313
- format `{cursor_field: cursor_value}`.
309
+ The method supports multiple input formats:
310
+ - A simple global state, e.g.:
311
+ {"updated_at": "2023-05-27T00:00:00Z"}
312
+ - A state object that contains a "state" key (which is assumed to hold the global state), e.g.:
313
+ {"state": {"updated_at": "2023-05-27T00:00:00Z"}, ...}
314
+ In this case, the migration uses the first value from the "state" dictionary.
315
+ - Any per-partition state formats or other non-simple structures are ignored during migration.
314
316
 
315
317
  Args:
316
318
  stream_state (StreamState): The state to migrate. Expected formats include:
317
319
  - {"updated_at": "2023-05-27T00:00:00Z"}
318
- - {"states": [...] } (ignored during migration)
320
+ - {"state": {"updated_at": "2023-05-27T00:00:00Z"}, ...}
321
+ (In this format, only the first global state value is used, and per-partition states are ignored.)
319
322
 
320
323
  Returns:
321
324
  StreamState: A migrated state for parent streams in the format:
322
325
  {
323
326
  "parent_stream_name": {"parent_stream_cursor": "2023-05-27T00:00:00Z"}
324
327
  }
328
+ where each parent stream with an incremental dependency is assigned its corresponding cursor value.
325
329
 
326
330
  Example:
327
331
  Input: {"updated_at": "2023-05-27T00:00:00Z"}
@@ -332,11 +336,15 @@ class SubstreamPartitionRouter(PartitionRouter):
332
336
  substream_state_values = list(stream_state.values())
333
337
  substream_state = substream_state_values[0] if substream_state_values else {}
334
338
 
335
- # Ignore per-partition states or invalid formats
339
+ # Ignore per-partition states or invalid formats.
336
340
  if isinstance(substream_state, (list, dict)) or len(substream_state_values) != 1:
337
- return {}
341
+ # If a global state is present under the key "state", use its first value.
342
+ if "state" in stream_state and isinstance(stream_state["state"], dict):
343
+ substream_state = list(stream_state["state"].values())[0]
344
+ else:
345
+ return {}
338
346
 
339
- # Copy child state to parent streams with incremental dependencies
347
+ # Build the parent state for all parent streams with incremental dependencies.
340
348
  parent_state = {}
341
349
  if substream_state:
342
350
  for parent_config in self.parent_stream_configs:
@@ -202,6 +202,9 @@ class HttpRequester(Requester):
202
202
  Raise a ValueError if there's a key collision
203
203
  Returned merged mapping otherwise
204
204
  """
205
+
206
+ is_body_json = requester_method.__name__ == "get_request_body_json"
207
+
205
208
  return combine_mappings(
206
209
  [
207
210
  requester_method(
@@ -211,7 +214,8 @@ class HttpRequester(Requester):
211
214
  ),
212
215
  auth_options_method(),
213
216
  extra_options,
214
- ]
217
+ ],
218
+ allow_same_value_merge=is_body_json,
215
219
  )
216
220
 
217
221
  def _request_headers(
@@ -187,7 +187,7 @@ class DefaultPaginator(Paginator):
187
187
  def _get_request_options(
188
188
  self, option_type: RequestOptionType, next_page_token: Optional[Mapping[str, Any]]
189
189
  ) -> MutableMapping[str, Any]:
190
- options = {}
190
+ options: MutableMapping[str, Any] = {}
191
191
 
192
192
  token = next_page_token.get("next_page_token") if next_page_token else None
193
193
  if (
@@ -196,15 +196,16 @@ class DefaultPaginator(Paginator):
196
196
  and isinstance(self.page_token_option, RequestOption)
197
197
  and self.page_token_option.inject_into == option_type
198
198
  ):
199
- options[self.page_token_option.field_name.eval(config=self.config)] = token # type: ignore # field_name is always cast to an interpolated string
199
+ self.page_token_option.inject_into_request(options, token, self.config)
200
+
200
201
  if (
201
202
  self.page_size_option
202
203
  and self.pagination_strategy.get_page_size()
203
204
  and self.page_size_option.inject_into == option_type
204
205
  ):
205
- options[self.page_size_option.field_name.eval(config=self.config)] = ( # type: ignore [union-attr]
206
- self.pagination_strategy.get_page_size()
207
- ) # type: ignore # field_name is always cast to an interpolated string
206
+ page_size = self.pagination_strategy.get_page_size()
207
+ self.page_size_option.inject_into_request(options, page_size, self.config)
208
+
208
209
  return options
209
210
 
210
211
 
@@ -4,9 +4,10 @@
4
4
 
5
5
  from dataclasses import InitVar, dataclass
6
6
  from enum import Enum
7
- from typing import Any, Mapping, Union
7
+ from typing import Any, List, Literal, Mapping, MutableMapping, Optional, Union
8
8
 
9
9
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
10
+ from airbyte_cdk.sources.types import Config
10
11
 
11
12
 
12
13
  class RequestOptionType(Enum):
@@ -26,13 +27,91 @@ class RequestOption:
26
27
  Describes an option to set on a request
27
28
 
28
29
  Attributes:
29
- field_name (str): Describes the name of the parameter to inject
30
+ field_name (str): Describes the name of the parameter to inject. Mutually exclusive with field_path.
31
+ field_path (list(str)): Describes the path to a nested field as a list of field names.
32
+ Only valid for body_json injection type, and mutually exclusive with field_name.
30
33
  inject_into (RequestOptionType): Describes where in the HTTP request to inject the parameter
31
34
  """
32
35
 
33
- field_name: Union[InterpolatedString, str]
34
36
  inject_into: RequestOptionType
35
37
  parameters: InitVar[Mapping[str, Any]]
38
+ field_name: Optional[Union[InterpolatedString, str]] = None
39
+ field_path: Optional[List[Union[InterpolatedString, str]]] = None
36
40
 
37
41
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
38
- self.field_name = InterpolatedString.create(self.field_name, parameters=parameters)
42
+ # Validate inputs. We should expect either field_name or field_path, but not both
43
+ if self.field_name is None and self.field_path is None:
44
+ raise ValueError("RequestOption requires either a field_name or field_path")
45
+
46
+ if self.field_name is not None and self.field_path is not None:
47
+ raise ValueError(
48
+ "Only one of field_name or field_path can be provided to RequestOption"
49
+ )
50
+
51
+ # Nested field injection is only supported for body JSON injection
52
+ if self.field_path is not None and self.inject_into != RequestOptionType.body_json:
53
+ raise ValueError(
54
+ "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
55
+ )
56
+
57
+ # Convert field_name and field_path into InterpolatedString objects if they are strings
58
+ if self.field_name is not None:
59
+ self.field_name = InterpolatedString.create(self.field_name, parameters=parameters)
60
+ elif self.field_path is not None:
61
+ self.field_path = [
62
+ InterpolatedString.create(segment, parameters=parameters)
63
+ for segment in self.field_path
64
+ ]
65
+
66
+ @property
67
+ def _is_field_path(self) -> bool:
68
+ """Returns whether this option is a field path (ie, a nested field)"""
69
+ return self.field_path is not None
70
+
71
+ def inject_into_request(
72
+ self,
73
+ target: MutableMapping[str, Any],
74
+ value: Any,
75
+ config: Config,
76
+ ) -> None:
77
+ """
78
+ Inject a request option value into a target request structure using either field_name or field_path.
79
+ For non-body-json injection, only top-level field names are supported.
80
+ For body-json injection, both field names and nested field paths are supported.
81
+
82
+ Args:
83
+ target: The request structure to inject the value into
84
+ value: The value to inject
85
+ config: The config object to use for interpolation
86
+ """
87
+ if self._is_field_path:
88
+ if self.inject_into != RequestOptionType.body_json:
89
+ raise ValueError(
90
+ "Nested field injection is only supported for body JSON injection. Please use a top-level field_name for other injection types."
91
+ )
92
+
93
+ assert self.field_path is not None # for type checker
94
+ current = target
95
+ # Convert path segments into strings, evaluating any interpolated segments
96
+ # Example: ["data", "{{ config[user_type] }}", "id"] -> ["data", "admin", "id"]
97
+ *path_parts, final_key = [
98
+ str(
99
+ segment.eval(config=config)
100
+ if isinstance(segment, InterpolatedString)
101
+ else segment
102
+ )
103
+ for segment in self.field_path
104
+ ]
105
+
106
+ # Build a nested dictionary structure and set the final value at the deepest level
107
+ for part in path_parts:
108
+ current = current.setdefault(part, {})
109
+ current[final_key] = value
110
+ else:
111
+ # For non-nested fields, evaluate the field name if it's an interpolated string
112
+ key = (
113
+ self.field_name.eval(config=config)
114
+ if isinstance(self.field_name, InterpolatedString)
115
+ else self.field_name
116
+ )
117
+ target[str(key)] = value
@@ -80,12 +80,13 @@ class DatetimeBasedRequestOptionsProvider(RequestOptionsProvider):
80
80
  options: MutableMapping[str, Any] = {}
81
81
  if not stream_slice:
82
82
  return options
83
+
83
84
  if self.start_time_option and self.start_time_option.inject_into == option_type:
84
- options[self.start_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore # field_name is always casted to an interpolated string
85
- self._partition_field_start.eval(self.config)
86
- )
85
+ start_time_value = stream_slice.get(self._partition_field_start.eval(self.config))
86
+ self.start_time_option.inject_into_request(options, start_time_value, self.config)
87
+
87
88
  if self.end_time_option and self.end_time_option.inject_into == option_type:
88
- options[self.end_time_option.field_name.eval(config=self.config)] = stream_slice.get( # type: ignore [union-attr]
89
- self._partition_field_end.eval(self.config)
90
- )
89
+ end_time_value = stream_slice.get(self._partition_field_end.eval(self.config))
90
+ self.end_time_option.inject_into_request(options, end_time_value, self.config)
91
+
91
92
  return options
@@ -128,6 +128,9 @@ class SimpleRetriever(Retriever):
128
128
  Returned merged mapping otherwise
129
129
  """
130
130
  # FIXME we should eventually remove the usage of stream_state as part of the interpolation
131
+
132
+ is_body_json = paginator_method.__name__ == "get_request_body_json"
133
+
131
134
  mappings = [
132
135
  paginator_method(
133
136
  stream_state=stream_state,
@@ -143,7 +146,7 @@ class SimpleRetriever(Retriever):
143
146
  next_page_token=next_page_token,
144
147
  )
145
148
  )
146
- return combine_mappings(mappings)
149
+ return combine_mappings(mappings, allow_same_value_merge=is_body_json)
147
150
 
148
151
  def _request_headers(
149
152
  self,
@@ -100,6 +100,69 @@ class RequestMatcher(abc.ABC):
100
100
 
101
101
 
102
102
  class HttpRequestMatcher(RequestMatcher):
103
+ """Simple implementation of RequestMatcher for http requests case"""
104
+
105
+ def __init__(
106
+ self,
107
+ method: Optional[str] = None,
108
+ url: Optional[str] = None,
109
+ params: Optional[Mapping[str, Any]] = None,
110
+ headers: Optional[Mapping[str, Any]] = None,
111
+ ):
112
+ """Constructor
113
+
114
+ :param method:
115
+ :param url:
116
+ :param params:
117
+ :param headers:
118
+ """
119
+ self._method = method
120
+ self._url = url
121
+ self._params = {str(k): str(v) for k, v in (params or {}).items()}
122
+ self._headers = {str(k): str(v) for k, v in (headers or {}).items()}
123
+
124
+ @staticmethod
125
+ def _match_dict(obj: Mapping[str, Any], pattern: Mapping[str, Any]) -> bool:
126
+ """Check that all elements from pattern dict present and have the same values in obj dict
127
+
128
+ :param obj:
129
+ :param pattern:
130
+ :return:
131
+ """
132
+ return pattern.items() <= obj.items()
133
+
134
+ def __call__(self, request: Any) -> bool:
135
+ """
136
+
137
+ :param request:
138
+ :return: True if matches the provided request object, False - otherwise
139
+ """
140
+ if isinstance(request, requests.Request):
141
+ prepared_request = request.prepare()
142
+ elif isinstance(request, requests.PreparedRequest):
143
+ prepared_request = request
144
+ else:
145
+ return False
146
+
147
+ if self._method is not None:
148
+ if prepared_request.method != self._method:
149
+ return False
150
+ if self._url is not None and prepared_request.url is not None:
151
+ url_without_params = prepared_request.url.split("?")[0]
152
+ if url_without_params != self._url:
153
+ return False
154
+ if self._params is not None:
155
+ parsed_url = parse.urlsplit(prepared_request.url)
156
+ params = dict(parse.parse_qsl(str(parsed_url.query)))
157
+ if not self._match_dict(params, self._params):
158
+ return False
159
+ if self._headers is not None:
160
+ if not self._match_dict(prepared_request.headers, self._headers):
161
+ return False
162
+ return True
163
+
164
+
165
+ class HttpRequestRegexMatcher(RequestMatcher):
103
166
  """
104
167
  Extended RequestMatcher for HTTP requests that supports matching on:
105
168
  - HTTP method (case-insensitive)
@@ -3,43 +3,102 @@
3
3
  #
4
4
 
5
5
 
6
- from typing import Any, List, Mapping, Optional, Set, Union
6
+ import copy
7
+ from typing import Any, Dict, List, Mapping, Optional, Union
8
+
9
+
10
+ def _merge_mappings(
11
+ target: Dict[str, Any],
12
+ source: Mapping[str, Any],
13
+ path: Optional[List[str]] = None,
14
+ allow_same_value_merge: bool = False,
15
+ ) -> None:
16
+ """
17
+ Recursively merge two dictionaries, raising an error if there are any conflicts.
18
+ For body_json requests (allow_same_value_merge=True), a conflict occurs only when the same path has different values.
19
+ For other request types (allow_same_value_merge=False), any duplicate key is a conflict, regardless of value.
20
+
21
+ Args:
22
+ target: The dictionary to merge into
23
+ source: The dictionary to merge from
24
+ path: The current path in the nested structure (for error messages)
25
+ allow_same_value_merge: Whether to allow merging the same value into the same key. Set to false by default, should only be true for body_json injections
26
+ """
27
+ path = path or []
28
+ for key, source_value in source.items():
29
+ current_path = path + [str(key)]
30
+
31
+ if key in target:
32
+ target_value = target[key]
33
+ if isinstance(target_value, dict) and isinstance(source_value, dict):
34
+ # Only body_json supports nested_structures
35
+ if not allow_same_value_merge:
36
+ raise ValueError(f"Duplicate keys found: {'.'.join(current_path)}")
37
+ # If both are dictionaries, recursively merge them
38
+ _merge_mappings(target_value, source_value, current_path, allow_same_value_merge)
39
+
40
+ elif not allow_same_value_merge or target_value != source_value:
41
+ # If same key has different values, that's a conflict
42
+ raise ValueError(f"Duplicate keys found: {'.'.join(current_path)}")
43
+ else:
44
+ # No conflict, just copy the value (using deepcopy for nested structures)
45
+ target[key] = copy.deepcopy(source_value)
7
46
 
8
47
 
9
48
  def combine_mappings(
10
49
  mappings: List[Optional[Union[Mapping[str, Any], str]]],
50
+ allow_same_value_merge: bool = False,
11
51
  ) -> Union[Mapping[str, Any], str]:
12
52
  """
13
- Combine multiple mappings into a single mapping. If any of the mappings are a string, return
14
- that string. Raise errors in the following cases:
15
- * If there are duplicate keys across mappings
16
- * If there are multiple string mappings
17
- * If there are multiple mappings containing keys and one of them is a string
53
+ Combine multiple mappings into a single mapping.
54
+
55
+ For body_json requests (allow_same_value_merge=True):
56
+ - Supports nested structures (e.g., {"data": {"user": {"id": 1}}})
57
+ - Allows duplicate keys if their values match
58
+ - Raises error if same path has different values
59
+
60
+ For other request types (allow_same_value_merge=False):
61
+ - Only supports flat structures
62
+ - Any duplicate key raises an error, regardless of value
63
+
64
+ Args:
65
+ mappings: List of mappings to combine
66
+ allow_same_value_merge: Whether to allow duplicate keys with matching values.
67
+ Should only be True for body_json requests.
68
+
69
+ Returns:
70
+ A single mapping combining all inputs, or a string if there is exactly one
71
+ string mapping and no other non-empty mappings.
72
+
73
+ Raises:
74
+ ValueError: If there are:
75
+ - Multiple string mappings
76
+ - Both a string mapping and non-empty dictionary mappings
77
+ - Conflicting keys/paths based on allow_same_value_merge setting
18
78
  """
19
- all_keys: List[Set[str]] = []
20
- for part in mappings:
21
- if part is None:
22
- continue
23
- keys = set(part.keys()) if not isinstance(part, str) else set()
24
- all_keys.append(keys)
25
-
26
- string_options = sum(isinstance(mapping, str) for mapping in mappings)
27
- # If more than one mapping is a string, raise a ValueError
79
+ if not mappings:
80
+ return {}
81
+
82
+ # Count how many string options we have, ignoring None values
83
+ string_options = sum(isinstance(mapping, str) for mapping in mappings if mapping is not None)
28
84
  if string_options > 1:
29
85
  raise ValueError("Cannot combine multiple string options")
30
86
 
31
- if string_options == 1 and sum(len(keys) for keys in all_keys) > 0:
32
- raise ValueError("Cannot combine multiple options if one is a string")
87
+ # Filter out None values and empty mappings
88
+ non_empty_mappings = [
89
+ m for m in mappings if m is not None and not (isinstance(m, Mapping) and not m)
90
+ ]
33
91
 
34
- # If any mapping is a string, return it
35
- for mapping in mappings:
36
- if isinstance(mapping, str):
37
- return mapping
92
+ # If there is only one string option and no other non-empty mappings, return it
93
+ if string_options == 1:
94
+ if len(non_empty_mappings) > 1:
95
+ raise ValueError("Cannot combine multiple options if one is a string")
96
+ return next(m for m in non_empty_mappings if isinstance(m, str))
38
97
 
39
- # If there are duplicate keys across mappings, raise a ValueError
40
- intersection = set().union(*all_keys)
41
- if len(intersection) < sum(len(keys) for keys in all_keys):
42
- raise ValueError(f"Duplicate keys found: {intersection}")
98
+ # Start with an empty result and merge each mapping into it
99
+ result: Dict[str, Any] = {}
100
+ for mapping in non_empty_mappings:
101
+ if mapping and isinstance(mapping, Mapping):
102
+ _merge_mappings(result, mapping, allow_same_value_merge=allow_same_value_merge)
43
103
 
44
- # Return the combined mappings
45
- return {key: value for mapping in mappings if mapping for key, value in mapping.items()} # type: ignore # mapping can't be string here
104
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 6.33.0.dev0
3
+ Version: 6.33.1.dev0
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://airbyte.com
6
6
  License: MIT
@@ -55,7 +55,7 @@ airbyte_cdk/sources/declarative/auth/declarative_authenticator.py,sha256=nf-OmRU
55
55
  airbyte_cdk/sources/declarative/auth/jwt.py,sha256=SICqNsN2Cn_EgKadIgWuZpQxuMHyzrMZD_2-Uwy10rY,8539
56
56
  airbyte_cdk/sources/declarative/auth/oauth.py,sha256=fibXa-dqtM54jIUscWbz7DEA5uY6F2o1LfARjEeGRy0,13926
57
57
  airbyte_cdk/sources/declarative/auth/selective_authenticator.py,sha256=qGwC6YsCldr1bIeKG6Qo-A9a5cTdHw-vcOn3OtQrS4c,1540
58
- airbyte_cdk/sources/declarative/auth/token.py,sha256=r4u3WXyVa7WmiSZ9-eZXlrUI-pS0D4YWJnwjLzwV-Fk,11210
58
+ airbyte_cdk/sources/declarative/auth/token.py,sha256=2EnE78EhBOY9hbeZnQJ9AuFaM-G7dccU-oKo_LThRQk,11070
59
59
  airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=9CuSsmOoHkvlc4k-oZ3Jx5luAgfTMm1I_5HOZxw7wMU,3075
60
60
  airbyte_cdk/sources/declarative/checks/__init__.py,sha256=nsVV5Bo0E_tBNd8A4Xdsdb-75PpcLo5RQu2RQ_Gv-ME,806
61
61
  airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py,sha256=HUktywjI8pqOeED08UGqponUSwxs2TOAECTowlWlrRE,2138
@@ -63,11 +63,11 @@ airbyte_cdk/sources/declarative/checks/check_stream.py,sha256=dAA-UhmMj0WLXCkRQr
63
63
  airbyte_cdk/sources/declarative/checks/connection_checker.py,sha256=MBRJo6WJlZQHpIfOGaNOkkHUmgUl_4wDM6VPo41z5Ss,1383
64
64
  airbyte_cdk/sources/declarative/concurrency_level/__init__.py,sha256=5XUqrmlstYlMM0j6crktlKQwALek0uiz2D3WdM46MyA,191
65
65
  airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py,sha256=YIwCTCpOr_QSNW4ltQK0yUGWInI8PKNY216HOOegYLk,2101
66
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=x5a_Wv0chLAo2knBg-yQZoo5zUyfDsOuF4tEz3oHehc,27421
66
+ airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=7uqf_zQd2T08AYdMDJ80Zt0W1QHqZd-dvltXC-3g8W4,28136
67
67
  airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=l9LG7Qm6e5r_qgqfVKnx3mXYtg1I9MmMjomVIPfU4XA,177
68
68
  airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=SX9JjdesN1edN2WVUVMzU_ptqp2QB1OnsnjZ4mwcX7w,2579
69
69
  airbyte_cdk/sources/declarative/datetime/min_max_datetime.py,sha256=0BHBtDNQZfvwM45-tY5pNlTcKAFSGGNxemoi0Jic-0E,5785
70
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=MOzhkZUsmcFiSP1ES_eEQyAta_ms5zG5IEA9XwooR8M,147035
70
+ airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=_pL5eEPf9N7U5kyzfB6YJG2xUHAPNTrJSApLYlQS9vw,147556
71
71
  airbyte_cdk/sources/declarative/declarative_source.py,sha256=nF7wBqFd3AQmEKAm4CnIo29CJoQL562cJGSCeL8U8bA,1531
72
72
  airbyte_cdk/sources/declarative/declarative_stream.py,sha256=venZjfpvtqr3oFSuvMBWtn4h9ayLhD4L65ACuXCDZ64,10445
73
73
  airbyte_cdk/sources/declarative/decoders/__init__.py,sha256=KSpQetKGqPCv-38QgcVJ5kzM5nzbFldTSsYDCS3Xf0Y,1035
@@ -89,7 +89,7 @@ airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py,sha256=
89
89
  airbyte_cdk/sources/declarative/extractors/type_transformer.py,sha256=d6Y2Rfg8pMVEEnHllfVksWZdNVOU55yk34O03dP9muY,1626
90
90
  airbyte_cdk/sources/declarative/incremental/__init__.py,sha256=U1oZKtBaEC6IACmvziY9Wzg7Z8EgF4ZuR7NwvjlB_Sk,1255
91
91
  airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py,sha256=5dbO47TFmC5Oz8TZ8DKXwXeZElz70xy2v2HJlZr5qVs,17751
92
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=_UzUnSIUsDbRgbFTXgSyZEFb4ws-KdhdQPWO8mFbV7U,22028
92
+ airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py,sha256=5Bl_2EeA4as0e3J23Yxp8Q8BXzh0nJ2NcGSgj3V0h2o,21954
93
93
  airbyte_cdk/sources/declarative/incremental/declarative_cursor.py,sha256=5Bhw9VRPyIuCaD0wmmq_L3DZsa-rJgtKSEUzSd8YYD0,536
94
94
  airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py,sha256=9HO-QbL9akvjq2NP7l498RwLA4iQZlBMQW1tZbt34I8,15943
95
95
  airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py,sha256=9IAJTCiRUXvhFFz-IhZtYh_KfAjLHqthsYf2jErQRls,17728
@@ -109,20 +109,20 @@ airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW
109
109
  airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py,sha256=iemy3fKLczcU0-Aor7tx5jcT6DRedKMqyK7kCOp01hg,3924
110
110
  airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
111
111
  airbyte_cdk/sources/declarative/models/__init__.py,sha256=nUFxNCiKeYRVXuZEKA7GD-lTHxsiKcQ8FitZjKhPIvE,100
112
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=gRRQoZ4VAuyxtBALvHQnGMF9rOLgPSLSBp0yzWW-w4Y,103695
112
+ airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=4C2CQ3IkDI39X0Pkf5j-X5pfKUVTrfwjy6wAJoaI6cE,104148
113
113
  airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
114
114
  airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=958MMX6_ZOJUlDDdNr9Krosgi2bCKGx2Z765M2Woz18,5505
115
115
  airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=Rir9_z3Kcd5Es0-LChrzk-0qubAsiK_RSEnLmK2OXm8,553
116
116
  airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=CXwTfD3wSQq3okcqwigpprbHhSURUokh4GK2OmOyKC8,9132
117
117
  airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=IWUOdF03o-aQn0Occo1BJCxU0Pz-QILk5L67nzw2thw,6803
118
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=ee3BYsYxpGH3D6fLb8BWpGG1HFHcAgFRsCJXUO98nbA,134255
118
+ airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=5werqhabiFoYHEiHfn6v0cnzrX0t2jwI3kGa_u0xjn4,134157
119
119
  airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=HJ-Syp3p7RpyR_OK0X_a2kSyISfu3W-PKrRI16iY0a8,957
120
120
  airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py,sha256=VelO7zKqKtzMJ35jyFeg0ypJLQC0plqqIBNXoBW1G2E,3001
121
121
  airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
122
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha256=t7pRdFWfFWJtQQG19c9PVeMODyO2BknRTakpM5U9N-8,4844
122
+ airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha256=tmGGpMoOBmaMfhVZq53AEWxoHm2lmNVi6hA2_IVEnAA,4882
123
123
  airbyte_cdk/sources/declarative/partition_routers/partition_router.py,sha256=YyEIzdmLd1FjbVP3QbQ2VFCLW_P-OGbVh6VpZShp54k,2218
124
124
  airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py,sha256=SKzKjSyfccq4dxGIh-J6ejrgkCHzaiTIazmbmeQiRD4,1942
125
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=pEz-P6D5TGtP4isNfmtakgKD95PqMLo6fasCVLIguWk,16760
125
+ airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=LlWj-Ofs-xfjlqmDzH8OYpyblP2Pb8bPDdR9g1UZyt0,17693
126
126
  airbyte_cdk/sources/declarative/requesters/README.md,sha256=eL1I4iLkxaw7hJi9S9d18_XcRl-R8lUSjqBVJJzvXmg,2656
127
127
  airbyte_cdk/sources/declarative/requesters/__init__.py,sha256=d7a3OoHbqaJDyyPli3nqqJ2yAW_SLX6XDaBAKOwvpxw,364
128
128
  airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py,sha256=SkEDcJxlT1683rNx93K9whoS0OyUukkuOfToGtgpF58,776
@@ -139,9 +139,9 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_
139
139
  airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py,sha256=Tan66odx8VHzfdyyXMQkXz2pJYksllGqvxmpoajgcK4,669
140
140
  airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py,sha256=E-fQbt4ShfxZVoqfnmOx69C6FUPWZz8BIqI3DN9Kcjs,7935
141
141
  airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=3GtOefPH08evlSUxaILkiKLTHbIspFY4qd5B3ZqNE60,10063
142
- airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=F1lydK00pbImprvauxzsFiy_IpQI59iErqtuf2vES1Q,14866
142
+ airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=Ek5hS60-CYjvEaFD-bI7qA-bPgbOPb9hTbMBU4n5zNs,14994
143
143
  airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
144
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=FnSl3qPvv5wD6ieAI2Ic5c4dqBk-3fRe4tCaWzq3YwM,11840
144
+ airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=dSm_pKGOZjzvg-X_Vif-MjrnlUG23fCa69bocq8dVIs,11693
145
145
  airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py,sha256=j6j9QRPaTbKQ2N661RFVKthhkWiodEp6ut0tKeEd0Ng,2019
146
146
  airbyte_cdk/sources/declarative/requesters/paginators/paginator.py,sha256=OlN-y0PEOMzlUNUh3pzonoTpIJpGwkP4ibFengvpLVU,2230
147
147
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py,sha256=2gly8fuZpDNwtu1Qg6oE2jBLGqQRdzSLJdnpk_iDV6I,767
@@ -150,9 +150,9 @@ airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_incremen
150
150
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py,sha256=Z2i6a-oKMmOTxHxsTVSnyaShkJ3u8xZw1xIJdx2yxss,2731
151
151
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py,sha256=ZBshGQNr5Bb_V8dqnWRISqdXFcjm1CKIXnlfbRhNl8g,1308
152
152
  airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py,sha256=LoKXdUbSgHEtSwtA8DFrnX6SpQbRVVwreY8NguTKTcI,2229
153
- airbyte_cdk/sources/declarative/requesters/request_option.py,sha256=_qmv8CLQQ3fERt6BuMZeRu6tZXscPoeARx1VJdWMQ_M,1055
153
+ airbyte_cdk/sources/declarative/requesters/request_option.py,sha256=Bl0gxGWudmwT3FXBozTN00WYle2jd6ry_S1YylCnwqM,4825
154
154
  airbyte_cdk/sources/declarative/requesters/request_options/__init__.py,sha256=WCwpKqM4wKqy-DHJaCHbKAlFqRVOqMi9K5qonxIfi_Y,809
155
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py,sha256=FLkg0uzC9bc-zFnALWr0FLYpKsz8iK2xQsd4UOyeW08,3706
155
+ airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py,sha256=31nG6_0igidJFQon37-WeQkTpG3g2A5ZmlluI3ilZdE,3632
156
156
  airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py,sha256=SRROdPJZ5kuqHLOlkh115pWP9nDGfDxRYPgH9oD3hPo,1798
157
157
  airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py,sha256=UW4cAtzkQ261AyLI1cmCL2WLdI3ZDYGUTmrqKB9W3u8,2422
158
158
  airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py,sha256=Vr2-qa8iHC0vJ4cCtPl7lAUlhrnl4lUuPLMSFrzxMIg,3024
@@ -167,7 +167,7 @@ airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py,sha256=Aio
167
167
  airbyte_cdk/sources/declarative/retrievers/__init__.py,sha256=ix9m1dkR69DcXCXUKC5RK_ZZM7ojTLBQ4IkWQTfmfCk,456
168
168
  airbyte_cdk/sources/declarative/retrievers/async_retriever.py,sha256=2oQn_vo7uJKp4pdMnsF5CG5Iwc9rkPeEOLoAm_9bcus,3222
169
169
  airbyte_cdk/sources/declarative/retrievers/retriever.py,sha256=XPLs593Xv8c5cKMc37XzUAYmzlXd1a7eSsspM-CMuWA,1696
170
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=kgnhVQxRlFqJs2-rDu2-QH-p-GzQU3nKmSp6_aq8u0s,24550
170
+ airbyte_cdk/sources/declarative/retrievers/simple_retriever.py,sha256=uvsBqSUimi85YfSjPuOUoAlewwtvaYwgsLg2EDcswLE,24665
171
171
  airbyte_cdk/sources/declarative/schema/__init__.py,sha256=xU45UvM5O4c1PSM13UHpCdh5hpW3HXy9vRRGEiAC1rg,795
172
172
  airbyte_cdk/sources/declarative/schema/default_schema_loader.py,sha256=KTACrIE23a83wsm3Rd9Eb4K6-20lrGqYxTHNp9yxsso,1820
173
173
  airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py,sha256=J8Q_iJYhcSQLWyt0bTZCbDAGpxt9G8FCc6Q9jtGsNzw,10703
@@ -249,7 +249,7 @@ airbyte_cdk/sources/message/repository.py,sha256=SG7avgti_-dj8FcRHTTrhgLLGJbElv1
249
249
  airbyte_cdk/sources/source.py,sha256=KIBBH5VLEb8BZ8B9aROlfaI6OLoJqKDPMJ10jkAR7nk,3611
250
250
  airbyte_cdk/sources/streams/__init__.py,sha256=8fzTKpRTnSx5PggXgQPKJzHNZUV2BCA40N-dI6JM1xI,256
251
251
  airbyte_cdk/sources/streams/availability_strategy.py,sha256=_RU4JITrxMEN36g1RDHMu0iSw0I_3yWGfo5N8_YRvOg,3247
252
- airbyte_cdk/sources/streams/call_rate.py,sha256=sf_AvWe6R_CqGkn1dnwQiDROBEvRESrPUv-zQeQ91Es,21779
252
+ airbyte_cdk/sources/streams/call_rate.py,sha256=WHMJ0-8xf0w_DiI0RkhPA8KEaPzaojNb23AEnKfcuS4,23939
253
253
  airbyte_cdk/sources/streams/checkpoint/__init__.py,sha256=3oy7Hd4ivVWTZlN6dKAf4Fv_G7U5iZrvhO9hT871UIo,712
254
254
  airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py,sha256=6HMT2NI-FQuaW0nt95NcyWrt5rZN4gF-Arx0sxdgbv4,15221
255
255
  airbyte_cdk/sources/streams/checkpoint/cursor.py,sha256=3e-3c-54k8U7Awno7DMmAD9ndbnl9OM48EnbEgeDUO0,3499
@@ -342,7 +342,7 @@ airbyte_cdk/utils/datetime_format_inferrer.py,sha256=Ne2cpk7Tx3eZDEW2Q3O7jnNOY9g
342
342
  airbyte_cdk/utils/datetime_helpers.py,sha256=8mqzZ67Or2PBp7tLtrhh6XFv4wFzYsjCL_DOQJRaftI,17751
343
343
  airbyte_cdk/utils/event_timing.py,sha256=aiuFmPU80buLlNdKq4fDTEqqhEIelHPF6AalFGwY8as,2557
344
344
  airbyte_cdk/utils/is_cloud_environment.py,sha256=DayV32Irh-SdnJ0MnjvstwCJ66_l5oEsd8l85rZtHoc,574
345
- airbyte_cdk/utils/mapping_helpers.py,sha256=H7BH-Yr8hSRG4W0zZcQ0ZzKOY5QFn8fNkGcEsE3xZN8,1736
345
+ airbyte_cdk/utils/mapping_helpers.py,sha256=4EOyUzNAGkq-M0QF5rPeBfT4v_eV7qBrEaAtsTH1k8Y,4309
346
346
  airbyte_cdk/utils/message_utils.py,sha256=OTzbkwN7AdMDA3iKYq1LKwfPFxpyEDfdgEF9BED3dkU,1366
347
347
  airbyte_cdk/utils/oneof_option_config.py,sha256=N8EmWdYdwt0FM7fuShh6H8nj_r4KEL9tb2DJJtwsPow,1180
348
348
  airbyte_cdk/utils/print_buffer.py,sha256=PhMOi0C4Z91kWKrSvCQXcp8qRh1uCimpIdvrg6voZIA,2810
@@ -351,9 +351,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
351
351
  airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
352
352
  airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
353
353
  airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
354
- airbyte_cdk-6.33.0.dev0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
355
- airbyte_cdk-6.33.0.dev0.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
356
- airbyte_cdk-6.33.0.dev0.dist-info/METADATA,sha256=U-Epy8I3SzZzuU46_xkRtIpomk90rBI5BSigUTU4Sa4,6015
357
- airbyte_cdk-6.33.0.dev0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
358
- airbyte_cdk-6.33.0.dev0.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
359
- airbyte_cdk-6.33.0.dev0.dist-info/RECORD,,
354
+ airbyte_cdk-6.33.1.dev0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
355
+ airbyte_cdk-6.33.1.dev0.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
356
+ airbyte_cdk-6.33.1.dev0.dist-info/METADATA,sha256=mQGuCOSysZqTcr0t3XkeCBinHgcB5qnbdctfA9INpaY,6015
357
+ airbyte_cdk-6.33.1.dev0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
358
+ airbyte_cdk-6.33.1.dev0.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
359
+ airbyte_cdk-6.33.1.dev0.dist-info/RECORD,,