Skip to content

Commit a5273a5

Browse files
author
Baz
authored
fix: (CDK) (Manifest Migration) - fix the request_body_json / request_body_data > request_body migration (#521)
1 parent a1dd40b commit a5273a5

File tree

7 files changed

+354
-119
lines changed

7 files changed

+354
-119
lines changed

airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,44 @@ def should_migrate(self, manifest: ManifestType) -> bool:
3333
def migrate(self, manifest: ManifestType) -> None:
3434
for key in self.original_keys:
3535
if key == self.body_json_key and key in manifest:
36-
manifest[self.replacement_key] = {
37-
"type": "RequestBodyJson",
38-
"value": manifest[key],
39-
}
40-
manifest.pop(key, None)
36+
self._migrate_body_json(manifest, key)
4137
elif key == self.body_data_key and key in manifest:
42-
manifest[self.replacement_key] = {
43-
"type": "RequestBodyData",
44-
"value": manifest[key],
45-
}
46-
manifest.pop(key, None)
38+
self._migrate_body_data(manifest, key)
4739

4840
def validate(self, manifest: ManifestType) -> bool:
4941
return self.replacement_key in manifest and all(
5042
key not in manifest for key in self.original_keys
5143
)
44+
45+
def _migrate_body_json(self, manifest: ManifestType, key: str) -> None:
46+
"""
47+
Migrate the value of the request_body_json.
48+
"""
49+
query_key = "query"
50+
text_type = "RequestBodyPlainText"
51+
graph_ql_type = "RequestBodyGraphQL"
52+
json_object_type = "RequestBodyJsonObject"
53+
54+
if isinstance(manifest[key], str):
55+
self._migrate_value(manifest, key, text_type)
56+
elif isinstance(manifest[key], dict):
57+
if manifest[key].get(query_key) is not None:
58+
self._migrate_value(manifest, key, graph_ql_type)
59+
else:
60+
self._migrate_value(manifest, key, json_object_type)
61+
62+
def _migrate_body_data(self, manifest: ManifestType, key: str) -> None:
63+
"""
64+
Migrate the value of the request_body_data.
65+
"""
66+
self._migrate_value(manifest, key, "RequestBodyUrlEncodedForm")
67+
68+
def _migrate_value(self, manifest: ManifestType, key: str, type: str) -> None:
69+
"""
70+
Migrate the value of the key to a specific type and update the manifest.
71+
"""
72+
manifest[self.replacement_key] = {
73+
"type": type,
74+
"value": manifest[key],
75+
}
76+
manifest.pop(key, None)

airbyte_cdk/manifest_migrations/migrations/registry.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
manifest_migrations:
6-
- version: 6.48.2
6+
- version: 6.48.3
77
migrations:
88
- name: http_requester_url_base_to_url
99
order: 1

airbyte_cdk/sources/declarative/declarative_component_schema.yaml

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,31 +2048,39 @@ definitions:
20482048
title: Request Body Payload to be send as a part of the API request.
20492049
description: Specifies how to populate the body of the request with a payload. Can contain nested objects.
20502050
anyOf:
2051-
- "$ref": "#/definitions/RequestBody"
2051+
- "$ref": "#/definitions/RequestBodyPlainText"
2052+
- "$ref": "#/definitions/RequestBodyUrlEncodedForm"
2053+
- "$ref": "#/definitions/RequestBodyJsonObject"
2054+
- "$ref": "#/definitions/RequestBodyGraphQL"
20522055
interpolation_context:
20532056
- next_page_token
20542057
- stream_interval
20552058
- stream_partition
20562059
- stream_slice
20572060
examples:
2058-
- type: RequestBodyJson
2061+
- type: RequestBodyJsonObject
20592062
value:
20602063
sort_order: "ASC"
20612064
sort_field: "CREATED_AT"
2062-
- type: RequestBodyJson
2065+
- type: RequestBodyJsonObject
20632066
value:
20642067
key: "{{ config['value'] }}"
2065-
- type: RequestBodyJson
2068+
- type: RequestBodyJsonObject
20662069
value:
20672070
sort:
20682071
field: "updated_at"
20692072
order: "ascending"
2070-
- type: RequestBodyData
2073+
- type: RequestBodyPlainText
20712074
value: "plain_text_body"
2072-
- type: RequestBodyData
2075+
- type: RequestBodyUrlEncodedForm
20732076
value:
20742077
param1: "value1"
20752078
param2: "{{ config['param2_value'] }}"
2079+
- type: RequestBodyGraphQL
2080+
value:
2081+
query:
2082+
param1: "value1"
2083+
param2: "{{ config['param2_value'] }}"
20762084
request_headers:
20772085
title: Request Headers
20782086
description: Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.
@@ -4073,27 +4081,74 @@ definitions:
40734081
- type
40744082
- stream_template
40754083
- components_resolver
4076-
RequestBody:
4084+
RequestBodyPlainText:
4085+
title: Plain-text Body
4086+
description: Request body value is sent as plain text
40774087
type: object
4078-
description: The request body payload. Can be either URL encoded data or JSON.
4088+
required:
4089+
- type
4090+
- value
40794091
properties:
40804092
type:
4081-
anyOf:
4082-
- type: string
4083-
enum: [RequestBodyData]
4084-
- type: string
4085-
enum: [RequestBodyJson]
4093+
type: string
4094+
enum: [RequestBodyPlainText]
40864095
value:
4087-
anyOf:
4088-
- type: string
4089-
description: The request body payload as a string.
4090-
- type: object
4091-
description: The request body payload as a Non-JSON object (url-encoded data).
4092-
additionalProperties:
4093-
type: string
4094-
- type: object
4095-
description: The request body payload as a JSON object (json-encoded data).
4096-
additionalProperties: true
4096+
type: string
4097+
RequestBodyUrlEncodedForm:
4098+
title: URL-encoded Body
4099+
description: Request body value is converted into a url-encoded form
4100+
type: object
4101+
required:
4102+
- type
4103+
- value
4104+
properties:
4105+
type:
4106+
type: string
4107+
enum: [RequestBodyUrlEncodedForm]
4108+
value:
4109+
type: object
4110+
additionalProperties:
4111+
type: string
4112+
RequestBodyJsonObject:
4113+
title: Json Object Body
4114+
description: Request body value converted into a JSON object
4115+
type: object
4116+
required:
4117+
- type
4118+
- value
4119+
properties:
4120+
type:
4121+
type: string
4122+
enum: [RequestBodyJsonObject]
4123+
value:
4124+
type: object
4125+
additionalProperties: true
4126+
RequestBodyGraphQL:
4127+
title: GraphQL Body
4128+
description: Request body value converted into a GraphQL query object
4129+
type: object
4130+
required:
4131+
- type
4132+
- value
4133+
properties:
4134+
type:
4135+
type: string
4136+
enum: [RequestBodyGraphQL]
4137+
value:
4138+
"$ref": "#/definitions/RequestBodyGraphQlQuery"
4139+
RequestBodyGraphQlQuery:
4140+
title: GraphQL Query Body
4141+
description: Request body GraphQL query object
4142+
type: object
4143+
required:
4144+
- query
4145+
properties:
4146+
query:
4147+
type: object
4148+
additionalProperties: true
4149+
description: The GraphQL query to be executed
4150+
default: {}
4151+
additionalProperties: true
40974152
interpolation:
40984153
variables:
40994154
- title: config

airbyte_cdk/sources/declarative/models/declarative_component_schema.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
2-
31
# generated by datamodel-codegen:
42
# filename: declarative_component_schema.yaml
53

@@ -1501,9 +1499,26 @@ class ConfigComponentsResolver(BaseModel):
15011499
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
15021500

15031501

1504-
class RequestBody(BaseModel):
1505-
type: Optional[Union[Literal["RequestBodyData"], Literal["RequestBodyJson"]]] = None
1506-
value: Optional[Union[str, Dict[str, str], Dict[str, Any]]] = None
1502+
class RequestBodyPlainText(BaseModel):
1503+
type: Literal["RequestBodyPlainText"]
1504+
value: str
1505+
1506+
1507+
class RequestBodyUrlEncodedForm(BaseModel):
1508+
type: Literal["RequestBodyUrlEncodedForm"]
1509+
value: Dict[str, str]
1510+
1511+
1512+
class RequestBodyJsonObject(BaseModel):
1513+
type: Literal["RequestBodyJsonObject"]
1514+
value: Dict[str, Any]
1515+
1516+
1517+
class RequestBodyGraphQlQuery(BaseModel):
1518+
class Config:
1519+
extra = Extra.allow
1520+
1521+
query: Dict[str, Any] = Field(..., description="The GraphQL query to be executed")
15071522

15081523

15091524
class AddedFieldDefinition(BaseModel):
@@ -1908,6 +1923,11 @@ class Spec(BaseModel):
19081923
)
19091924

19101925

1926+
class RequestBodyGraphQL(BaseModel):
1927+
type: Literal["RequestBodyGraphQL"]
1928+
value: RequestBodyGraphQlQuery
1929+
1930+
19111931
class CompositeErrorHandler(BaseModel):
19121932
type: Literal["CompositeErrorHandler"]
19131933
error_handlers: List[Union[CompositeErrorHandler, DefaultErrorHandler]] = Field(
@@ -2305,24 +2325,43 @@ class HttpRequester(BaseModelWithDeprecations):
23052325
],
23062326
title="Request Body JSON Payload",
23072327
)
2308-
request_body: Optional[RequestBody] = Field(
2328+
request_body: Optional[
2329+
Union[
2330+
RequestBodyPlainText,
2331+
RequestBodyUrlEncodedForm,
2332+
RequestBodyJsonObject,
2333+
RequestBodyGraphQL,
2334+
]
2335+
] = Field(
23092336
None,
23102337
description="Specifies how to populate the body of the request with a payload. Can contain nested objects.",
23112338
examples=[
23122339
{
2313-
"type": "RequestBodyJson",
2340+
"type": "RequestBodyJsonObject",
23142341
"value": {"sort_order": "ASC", "sort_field": "CREATED_AT"},
23152342
},
2316-
{"type": "RequestBodyJson", "value": {"key": "{{ config['value'] }}"}},
23172343
{
2318-
"type": "RequestBodyJson",
2344+
"type": "RequestBodyJsonObject",
2345+
"value": {"key": "{{ config['value'] }}"},
2346+
},
2347+
{
2348+
"type": "RequestBodyJsonObject",
23192349
"value": {"sort": {"field": "updated_at", "order": "ascending"}},
23202350
},
2321-
{"type": "RequestBodyData", "value": "plain_text_body"},
2351+
{"type": "RequestBodyPlainText", "value": "plain_text_body"},
23222352
{
2323-
"type": "RequestBodyData",
2353+
"type": "RequestBodyUrlEncodedForm",
23242354
"value": {"param1": "value1", "param2": "{{ config['param2_value'] }}"},
23252355
},
2356+
{
2357+
"type": "RequestBodyGraphQL",
2358+
"value": {
2359+
"query": {
2360+
"param1": "value1",
2361+
"param2": "{{ config['param2_value'] }}",
2362+
}
2363+
},
2364+
},
23262365
],
23272366
title="Request Body Payload to be send as a part of the API request.",
23282367
)

airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
from airbyte_cdk.sources.declarative.interpolation.interpolated_nested_mapping import NestedMapping
99
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
10-
RequestBody,
10+
RequestBodyGraphQL,
11+
RequestBodyJsonObject,
12+
RequestBodyPlainText,
13+
RequestBodyUrlEncodedForm,
1114
)
1215
from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_nested_request_input_provider import (
1316
InterpolatedNestedRequestInputProvider,
@@ -41,7 +44,14 @@ class InterpolatedRequestOptionsProvider(RequestOptionsProvider):
4144
config: Config = field(default_factory=dict)
4245
request_parameters: Optional[RequestInput] = None
4346
request_headers: Optional[RequestInput] = None
44-
request_body: Optional[RequestBody] = None
47+
request_body: Optional[
48+
Union[
49+
RequestBodyGraphQL,
50+
RequestBodyJsonObject,
51+
RequestBodyPlainText,
52+
RequestBodyUrlEncodedForm,
53+
]
54+
] = None
4555
request_body_data: Optional[RequestInput] = None
4656
request_body_json: Optional[NestedMapping] = None
4757
query_properties_key: Optional[str] = None
@@ -83,14 +93,18 @@ def _resolve_request_body(self) -> None:
8393
based on the type specified in `self.request_body`. If neither is provided, both are initialized as empty
8494
dictionaries. Raises a ValueError if both `request_body_data` and `request_body_json` are set simultaneously.
8595
Raises:
86-
ValueError: If both `request_body_data` and `request_body_json` are provided.
96+
ValueError: if an unsupported request body type is provided.
8797
"""
8898
# Resolve the request body to either data or json
8999
if self.request_body is not None and self.request_body.type is not None:
90-
if self.request_body.type == "RequestBodyData":
100+
if self.request_body.type == "RequestBodyUrlEncodedForm":
91101
self.request_body_data = self.request_body.value
92-
elif self.request_body.type == "RequestBodyJson":
102+
elif self.request_body.type == "RequestBodyGraphQL":
103+
self.request_body_json = {"query": self.request_body.value.query}
104+
elif self.request_body.type in ("RequestBodyJsonObject", "RequestBodyPlainText"):
93105
self.request_body_json = self.request_body.value
106+
else:
107+
raise ValueError(f"Unsupported request body type: {self.request_body.type}")
94108

95109
def get_request_params(
96110
self,

0 commit comments

Comments
 (0)