Skip to content

fix: (CDK) (Manifest Migration) - fix the request_body_json / request_body_data > request_body migration #521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,44 @@ def should_migrate(self, manifest: ManifestType) -> bool:
def migrate(self, manifest: ManifestType) -> None:
for key in self.original_keys:
if key == self.body_json_key and key in manifest:
manifest[self.replacement_key] = {
"type": "RequestBodyJson",
"value": manifest[key],
}
manifest.pop(key, None)
self._migrate_body_json(manifest, key)
elif key == self.body_data_key and key in manifest:
manifest[self.replacement_key] = {
"type": "RequestBodyData",
"value": manifest[key],
}
manifest.pop(key, None)
self._migrate_body_data(manifest, key)

def validate(self, manifest: ManifestType) -> bool:
return self.replacement_key in manifest and all(
key not in manifest for key in self.original_keys
)

def _migrate_body_json(self, manifest: ManifestType, key: str) -> None:
"""
Migrate the value of the request_body_json.
"""
query_key = "query"
text_type = "RequestBodyPlainText"
graph_ql_type = "RequestBodyGraphQL"
json_object_type = "RequestBodyJsonObject"

if isinstance(manifest[key], str):
self._migrate_value(manifest, key, text_type)
elif isinstance(manifest[key], dict):
if manifest[key].get(query_key) is not None:
self._migrate_value(manifest, key, graph_ql_type)
else:
self._migrate_value(manifest, key, json_object_type)

def _migrate_body_data(self, manifest: ManifestType, key: str) -> None:
"""
Migrate the value of the request_body_data.
"""
self._migrate_value(manifest, key, "RequestBodyUrlEncodedForm")

def _migrate_value(self, manifest: ManifestType, key: str, type: str) -> None:
"""
Migrate the value of the key to a specific type and update the manifest.
"""
manifest[self.replacement_key] = {
"type": type,
"value": manifest[key],
}
manifest.pop(key, None)
2 changes: 1 addition & 1 deletion airbyte_cdk/manifest_migrations/migrations/registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#

manifest_migrations:
- version: 6.48.2
- version: 6.48.3
migrations:
- name: http_requester_url_base_to_url
order: 1
Expand Down
101 changes: 78 additions & 23 deletions airbyte_cdk/sources/declarative/declarative_component_schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2048,31 +2048,39 @@ definitions:
title: Request Body Payload to be send as a part of the API request.
description: Specifies how to populate the body of the request with a payload. Can contain nested objects.
anyOf:
- "$ref": "#/definitions/RequestBody"
- "$ref": "#/definitions/RequestBodyPlainText"
- "$ref": "#/definitions/RequestBodyUrlEncodedForm"
- "$ref": "#/definitions/RequestBodyJsonObject"
- "$ref": "#/definitions/RequestBodyGraphQL"
interpolation_context:
- next_page_token
- stream_interval
- stream_partition
- stream_slice
examples:
- type: RequestBodyJson
- type: RequestBodyJsonObject
value:
sort_order: "ASC"
sort_field: "CREATED_AT"
- type: RequestBodyJson
- type: RequestBodyJsonObject
value:
key: "{{ config['value'] }}"
- type: RequestBodyJson
- type: RequestBodyJsonObject
value:
sort:
field: "updated_at"
order: "ascending"
- type: RequestBodyData
- type: RequestBodyPlainText
value: "plain_text_body"
- type: RequestBodyData
- type: RequestBodyUrlEncodedForm
value:
param1: "value1"
param2: "{{ config['param2_value'] }}"
- type: RequestBodyGraphQL
value:
query:
param1: "value1"
param2: "{{ config['param2_value'] }}"
request_headers:
title: Request Headers
description: Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.
Expand Down Expand Up @@ -4073,27 +4081,74 @@ definitions:
- type
- stream_template
- components_resolver
RequestBody:
RequestBodyPlainText:
title: Plain-text Body
description: Request body value is sent as plain text
type: object
description: The request body payload. Can be either URL encoded data or JSON.
required:
- type
- value
properties:
type:
anyOf:
- type: string
enum: [RequestBodyData]
- type: string
enum: [RequestBodyJson]
type: string
enum: [RequestBodyPlainText]
value:
anyOf:
- type: string
description: The request body payload as a string.
- type: object
description: The request body payload as a Non-JSON object (url-encoded data).
additionalProperties:
type: string
- type: object
description: The request body payload as a JSON object (json-encoded data).
additionalProperties: true
type: string
RequestBodyUrlEncodedForm:
title: URL-encoded Body
description: Request body value is converted into a url-encoded form
type: object
required:
- type
- value
properties:
type:
type: string
enum: [RequestBodyUrlEncodedForm]
value:
type: object
additionalProperties:
type: string
RequestBodyJsonObject:
title: Json Object Body
description: Request body value converted into a JSON object
type: object
required:
- type
- value
properties:
type:
type: string
enum: [RequestBodyJsonObject]
value:
type: object
additionalProperties: true
RequestBodyGraphQL:
title: GraphQL Body
description: Request body value converted into a GraphQL query object
type: object
required:
- type
- value
properties:
type:
type: string
enum: [RequestBodyGraphQL]
value:
"$ref": "#/definitions/RequestBodyGraphQlQuery"
RequestBodyGraphQlQuery:
title: GraphQL Query Body
description: Request body GraphQL query object
type: object
required:
- query
properties:
query:
type: object
additionalProperties: true
description: The GraphQL query to be executed
default: {}
additionalProperties: true
interpolation:
variables:
- title: config
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.

# generated by datamodel-codegen:
# filename: declarative_component_schema.yaml

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


class RequestBody(BaseModel):
type: Optional[Union[Literal["RequestBodyData"], Literal["RequestBodyJson"]]] = None
value: Optional[Union[str, Dict[str, str], Dict[str, Any]]] = None
class RequestBodyPlainText(BaseModel):
type: Literal["RequestBodyPlainText"]
value: str


class RequestBodyUrlEncodedForm(BaseModel):
type: Literal["RequestBodyUrlEncodedForm"]
value: Dict[str, str]


class RequestBodyJsonObject(BaseModel):
type: Literal["RequestBodyJsonObject"]
value: Dict[str, Any]


class RequestBodyGraphQlQuery(BaseModel):
class Config:
extra = Extra.allow

query: Dict[str, Any] = Field(..., description="The GraphQL query to be executed")


class AddedFieldDefinition(BaseModel):
Expand Down Expand Up @@ -1908,6 +1923,11 @@ class Spec(BaseModel):
)


class RequestBodyGraphQL(BaseModel):
type: Literal["RequestBodyGraphQL"]
value: RequestBodyGraphQlQuery


class CompositeErrorHandler(BaseModel):
type: Literal["CompositeErrorHandler"]
error_handlers: List[Union[CompositeErrorHandler, DefaultErrorHandler]] = Field(
Expand Down Expand Up @@ -2305,24 +2325,43 @@ class HttpRequester(BaseModelWithDeprecations):
],
title="Request Body JSON Payload",
)
request_body: Optional[RequestBody] = Field(
request_body: Optional[
Union[
RequestBodyPlainText,
RequestBodyUrlEncodedForm,
RequestBodyJsonObject,
RequestBodyGraphQL,
]
] = Field(
None,
description="Specifies how to populate the body of the request with a payload. Can contain nested objects.",
examples=[
{
"type": "RequestBodyJson",
"type": "RequestBodyJsonObject",
"value": {"sort_order": "ASC", "sort_field": "CREATED_AT"},
},
{"type": "RequestBodyJson", "value": {"key": "{{ config['value'] }}"}},
{
"type": "RequestBodyJson",
"type": "RequestBodyJsonObject",
"value": {"key": "{{ config['value'] }}"},
},
{
"type": "RequestBodyJsonObject",
"value": {"sort": {"field": "updated_at", "order": "ascending"}},
},
{"type": "RequestBodyData", "value": "plain_text_body"},
{"type": "RequestBodyPlainText", "value": "plain_text_body"},
{
"type": "RequestBodyData",
"type": "RequestBodyUrlEncodedForm",
"value": {"param1": "value1", "param2": "{{ config['param2_value'] }}"},
},
{
"type": "RequestBodyGraphQL",
"value": {
"query": {
"param1": "value1",
"param2": "{{ config['param2_value'] }}",
}
},
},
],
title="Request Body Payload to be send as a part of the API request.",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

from airbyte_cdk.sources.declarative.interpolation.interpolated_nested_mapping import NestedMapping
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
RequestBody,
RequestBodyGraphQL,
RequestBodyJsonObject,
RequestBodyPlainText,
RequestBodyUrlEncodedForm,
)
from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_nested_request_input_provider import (
InterpolatedNestedRequestInputProvider,
Expand Down Expand Up @@ -41,7 +44,14 @@ class InterpolatedRequestOptionsProvider(RequestOptionsProvider):
config: Config = field(default_factory=dict)
request_parameters: Optional[RequestInput] = None
request_headers: Optional[RequestInput] = None
request_body: Optional[RequestBody] = None
request_body: Optional[
Union[
RequestBodyGraphQL,
RequestBodyJsonObject,
RequestBodyPlainText,
RequestBodyUrlEncodedForm,
]
] = None
request_body_data: Optional[RequestInput] = None
request_body_json: Optional[NestedMapping] = None
query_properties_key: Optional[str] = None
Expand Down Expand Up @@ -83,14 +93,18 @@ def _resolve_request_body(self) -> None:
based on the type specified in `self.request_body`. If neither is provided, both are initialized as empty
dictionaries. Raises a ValueError if both `request_body_data` and `request_body_json` are set simultaneously.
Raises:
ValueError: If both `request_body_data` and `request_body_json` are provided.
ValueError: if an unsupported request body type is provided.
"""
# Resolve the request body to either data or json
if self.request_body is not None and self.request_body.type is not None:
if self.request_body.type == "RequestBodyData":
if self.request_body.type == "RequestBodyUrlEncodedForm":
self.request_body_data = self.request_body.value
elif self.request_body.type == "RequestBodyJson":
elif self.request_body.type == "RequestBodyGraphQL":
self.request_body_json = {"query": self.request_body.value.query}
elif self.request_body.type in ("RequestBodyJsonObject", "RequestBodyPlainText"):
self.request_body_json = self.request_body.value
else:
raise ValueError(f"Unsupported request body type: {self.request_body.type}")

def get_request_params(
self,
Expand Down
Loading
Loading