diff --git a/airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py b/airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py index 648b8d9a3..c63066953 100644 --- a/airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py +++ b/airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py @@ -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) diff --git a/airbyte_cdk/manifest_migrations/migrations/registry.yaml b/airbyte_cdk/manifest_migrations/migrations/registry.yaml index 02074541b..415b82b86 100644 --- a/airbyte_cdk/manifest_migrations/migrations/registry.yaml +++ b/airbyte_cdk/manifest_migrations/migrations/registry.yaml @@ -3,7 +3,7 @@ # manifest_migrations: - - version: 6.48.2 + - version: 6.48.3 migrations: - name: http_requester_url_base_to_url order: 1 diff --git a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index bce67193a..9d2b6d6b6 100644 --- a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -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. @@ -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 diff --git a/airbyte_cdk/sources/declarative/models/declarative_component_schema.py b/airbyte_cdk/sources/declarative/models/declarative_component_schema.py index 095325308..720fa00ed 100644 --- a/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +++ b/airbyte_cdk/sources/declarative/models/declarative_component_schema.py @@ -1,5 +1,3 @@ -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. - # generated by datamodel-codegen: # filename: declarative_component_schema.yaml @@ -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): @@ -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( @@ -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.", ) diff --git a/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py b/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py index 8e64d6b94..97ef78d48 100644 --- a/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +++ b/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py @@ -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, @@ -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 @@ -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, diff --git a/unit_tests/manifest_migrations/conftest.py b/unit_tests/manifest_migrations/conftest.py index 939cb15e5..f296af10d 100644 --- a/unit_tests/manifest_migrations/conftest.py +++ b/unit_tests/manifest_migrations/conftest.py @@ -197,7 +197,7 @@ def manifest_with_url_base_to_migrate_to_url() -> Dict[str, Any]: @pytest.fixture def expected_manifest_with_url_base_migrated_to_url() -> Dict[str, Any]: return { - "version": "6.48.2", + "version": "6.48.3", "type": "DeclarativeSource", "check": {"type": "CheckStream", "stream_names": ["A"]}, "definitions": { @@ -494,13 +494,13 @@ def expected_manifest_with_url_base_migrated_to_url() -> Dict[str, Any]: "applied_migrations": [ { "from_version": "0.0.0", - "to_version": "6.48.2", + "to_version": "6.48.3", "migration": "HttpRequesterUrlBaseToUrl", "migrated_at": "2025-04-01T00:00:00+00:00", # time freezed in the test }, { "from_version": "0.0.0", - "to_version": "6.48.2", + "to_version": "6.48.3", "migration": "HttpRequesterPathToUrl", "migrated_at": "2025-04-01T00:00:00+00:00", # time freezed in the test }, @@ -512,7 +512,7 @@ def expected_manifest_with_url_base_migrated_to_url() -> Dict[str, Any]: @pytest.fixture def manifest_with_migrated_url_base_and_path_is_joined_to_url() -> Dict[str, Any]: return { - "version": "6.48.2", + "version": "6.48.3", "type": "DeclarativeSource", "check": {"type": "CheckStream", "stream_names": ["A"]}, "definitions": {}, @@ -636,6 +636,13 @@ def manifest_with_request_body_json_and_data_to_migrate_to_request_body() -> Dic "$ref": "#/definitions/requester_A", "path": "/path_to_A", "http_method": "GET", + # this requester has a `request_body_data` key, + # to be migrated to the `request_body` key + "request_body_data": { + "test_key": "{{ config['config_key'] }}", + "test_key_2": "test_value_2", + "test_key_3": 123, + }, }, "record_selector": { "type": "RecordSelector", @@ -656,6 +663,9 @@ def manifest_with_request_body_json_and_data_to_migrate_to_request_body() -> Dic "$ref": "#/definitions/requester_A", "path": "path_to_A", "http_method": "GET", + # this requester has a `request_body_data` key, + # to be migrated to the `request_body` key + "request_body_data": "&test_key=TestValue&test_key_2=test_value_2", }, "record_selector": { "type": "RecordSelector", @@ -703,6 +713,8 @@ def manifest_with_request_body_json_and_data_to_migrate_to_request_body() -> Dic # ! the double-slash is intentional here for the test. "path": "//path_to_B", "http_method": "GET", + # the `request_body_json` is expected to be migrated to the `request_body` key + "request_body_json": """{"nested": { "key": "{{ config['option'] }}" }}""", }, "record_selector": { "type": "RecordSelector", @@ -723,6 +735,14 @@ def manifest_with_request_body_json_and_data_to_migrate_to_request_body() -> Dic "$ref": "#/definitions/requester_B", "path": "/path_to_B", "http_method": "GET", + # the `request_body_json` is expected to be migrated to the `request_body` key, + # this example holds the GraphQL query object. + "request_body_json": { + "query": { + "field": "{{ config['query_field'] }}", + "value": "{{ config['query_value'] }}", + } + }, }, "record_selector": { "type": "RecordSelector", @@ -741,20 +761,10 @@ def manifest_with_request_body_json_and_data_to_migrate_to_request_body() -> Dic "requester_A": { "type": "HttpRequester", "url_base": "https://example.com/v1/", - # this requester has a `request_body_json` key, - # to be migrated to the `request_body` key - "request_body_data": { - "test_key": "{{ config['config_key'] }}", - "test_key_2": "test_value_2", - "test_key_3": 123, - }, }, "requester_B": { "type": "HttpRequester", "url_base": "https://example.com/v2/", - # for this requester, the `request_body_json` key is not present, - # but the `request_body_data` key is present in the stream `C` itself. - # it should also be migrated to the `request_body` key }, }, "streams": [ @@ -822,7 +832,7 @@ def manifest_with_request_body_json_and_data_to_migrate_to_request_body() -> Dic @pytest.fixture def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: return { - "version": "6.48.2", + "version": "6.48.3", "type": "DeclarativeSource", "check": {"type": "CheckStream", "stream_names": ["A"]}, "definitions": { @@ -837,7 +847,7 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "http_method": "GET", "url": "https://example.com/v1/path_to_A", "request_body": { - "type": "RequestBodyData", + "type": "RequestBodyUrlEncodedForm", "value": { "test_key": "{{ config['config_key'] }}", "test_key_2": "test_value_2", @@ -870,12 +880,8 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "http_method": "GET", "url": "https://example.com/v1/path_to_A", "request_body": { - "type": "RequestBodyData", - "value": { - "test_key": "{{ config['config_key'] }}", - "test_key_2": "test_value_2", - "test_key_3": 123, - }, + "type": "RequestBodyUrlEncodedForm", + "value": "&test_key=TestValue&test_key_2=test_value_2", }, }, "record_selector": { @@ -903,7 +909,7 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "http_method": "GET", "url": "https://example.com/v2/path_to_B", "request_body": { - "type": "RequestBodyJson", + "type": "RequestBodyJsonObject", "value": { "reportType": "test_report", "groupBy": "GROUP", @@ -935,6 +941,10 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "type": "HttpRequester", "http_method": "GET", "url": "https://example.com/v2/path_to_B", + "request_body": { + "type": "RequestBodyPlainText", + "value": '{"nested": { "key": "{{ config[\'option\'] }}" }}', + }, }, "record_selector": { "type": "RecordSelector", @@ -960,6 +970,15 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "type": "HttpRequester", "http_method": "GET", "url": "https://example.com/v2/path_to_B", + "request_body": { + "type": "RequestBodyGraphQL", + "value": { + "query": { + "field": "{{ config['query_field'] }}", + "value": "{{ config['query_value'] }}", + } + }, + }, }, "record_selector": { "type": "RecordSelector", @@ -977,18 +996,7 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: }, }, }, - "requester_A": { - "type": "HttpRequester", - "url": "https://example.com/v1/", - "request_body": { - "type": "RequestBodyData", - "value": { - "test_key": "{{ config['config_key'] }}", - "test_key_2": "test_value_2", - "test_key_3": 123, - }, - }, - }, + "requester_A": {"type": "HttpRequester", "url": "https://example.com/v1/"}, "requester_B": {"type": "HttpRequester", "url": "https://example.com/v2/"}, }, "streams": [ @@ -1002,7 +1010,7 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "http_method": "GET", "url": "https://example.com/v1/path_to_A", "request_body": { - "type": "RequestBodyData", + "type": "RequestBodyUrlEncodedForm", "value": { "test_key": "{{ config['config_key'] }}", "test_key_2": "test_value_2", @@ -1035,12 +1043,8 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "http_method": "GET", "url": "https://example.com/v1/path_to_A", "request_body": { - "type": "RequestBodyData", - "value": { - "test_key": "{{ config['config_key'] }}", - "test_key_2": "test_value_2", - "test_key_3": 123, - }, + "type": "RequestBodyUrlEncodedForm", + "value": "&test_key=TestValue&test_key_2=test_value_2", }, }, "record_selector": { @@ -1068,7 +1072,7 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "http_method": "GET", "url": "https://example.com/v2/path_to_B", "request_body": { - "type": "RequestBodyJson", + "type": "RequestBodyJsonObject", "value": { "reportType": "test_report", "groupBy": "GROUP", @@ -1100,6 +1104,10 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "type": "HttpRequester", "http_method": "GET", "url": "https://example.com/v2/path_to_B", + "request_body": { + "type": "RequestBodyPlainText", + "value": '{"nested": { "key": "{{ config[\'option\'] }}" }}', + }, }, "record_selector": { "type": "RecordSelector", @@ -1125,6 +1133,15 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "type": "HttpRequester", "http_method": "GET", "url": "https://example.com/v2/path_to_B", + "request_body": { + "type": "RequestBodyGraphQL", + "value": { + "query": { + "field": "{{ config['query_field'] }}", + "value": "{{ config['query_value'] }}", + } + }, + }, }, "record_selector": { "type": "RecordSelector", @@ -1178,19 +1195,19 @@ def expected_manifest_with_migrated_to_request_body() -> Dict[str, Any]: "applied_migrations": [ { "from_version": "0.0.0", - "to_version": "6.48.2", + "to_version": "6.48.3", "migration": "HttpRequesterUrlBaseToUrl", "migrated_at": "2025-04-01T00:00:00+00:00", }, { "from_version": "0.0.0", - "to_version": "6.48.2", + "to_version": "6.48.3", "migration": "HttpRequesterPathToUrl", "migrated_at": "2025-04-01T00:00:00+00:00", }, { "from_version": "0.0.0", - "to_version": "6.48.2", + "to_version": "6.48.3", "migration": "HttpRequesterRequestBodyJsonDataToRequestBody", "migrated_at": "2025-04-01T00:00:00+00:00", }, diff --git a/unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py b/unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py index 012e6eb32..b112791e7 100644 --- a/unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py +++ b/unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py @@ -4,7 +4,13 @@ import pytest -from airbyte_cdk.sources.declarative.models.declarative_component_schema import RequestBody +from airbyte_cdk.sources.declarative.models.declarative_component_schema import ( + RequestBodyGraphQL, + RequestBodyGraphQlQuery, + RequestBodyJsonObject, + RequestBodyPlainText, + RequestBodyUrlEncodedForm, +) from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_request_options_provider import ( InterpolatedRequestOptionsProvider, ) @@ -137,57 +143,115 @@ def test_interpolated_request_json(test_name, input_request_json, expected_reque [ ( "test_static_json", - {"a_static_request_param": "a_static_value"}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", value={"a_static_request_param": "a_static_value"} + ), {"a_static_request_param": "a_static_value"}, ), ( "test_value_depends_on_stream_slice", - {"read_from_slice": "{{ stream_slice['start_date'] }}"}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", + value={"read_from_slice": "{{ stream_slice['start_date'] }}"}, + ), {"read_from_slice": "2020-01-01"}, ), ( "test_value_depends_on_next_page_token", - {"read_from_token": "{{ next_page_token['offset'] }}"}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", + value={"read_from_token": "{{ next_page_token['offset'] }}"}, + ), {"read_from_token": 12345}, ), ( "test_value_depends_on_config", - {"read_from_config": "{{ config['option'] }}"}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", value={"read_from_config": "{{ config['option'] }}"} + ), {"read_from_config": "OPTION"}, ), ( "test_interpolated_keys", - {"{{ stream_interval['start_date'] }}": 123, "{{ config['option'] }}": "ABC"}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", + value={"{{ stream_interval['start_date'] }}": 123, "{{ config['option'] }}": "ABC"}, + ), {"2020-01-01": 123, "OPTION": "ABC"}, ), - ("test_boolean_false_value", {"boolean_false": "{{ False }}"}, {"boolean_false": False}), - ("test_integer_falsy_value", {"integer_falsy": "{{ 0 }}"}, {"integer_falsy": 0}), - ("test_number_falsy_value", {"number_falsy": "{{ 0.0 }}"}, {"number_falsy": 0.0}), - ("test_string_falsy_value", {"string_falsy": "{{ '' }}"}, {}), - ("test_none_value", {"none_value": "{{ None }}"}, {}), + ( + "test_boolean_false_value", + RequestBodyJsonObject( + type="RequestBodyJsonObject", value={"boolean_false": "{{ False }}"} + ), + {"boolean_false": False}, + ), + ( + "test_integer_falsy_value", + RequestBodyJsonObject( + type="RequestBodyJsonObject", + value={"integer_falsy": "{{ 0 }}"}, + ), + {"integer_falsy": 0}, + ), + ( + "test_number_falsy_value", + RequestBodyJsonObject( + type="RequestBodyJsonObject", value={"number_falsy": "{{ 0.0 }}"} + ), + {"number_falsy": 0.0}, + ), + ( + "test_string_falsy_value", + RequestBodyJsonObject(type="RequestBodyJsonObject", value={"string_falsy": "{{ '' }}"}), + {}, + ), + ( + "test_none_value", + RequestBodyJsonObject(type="RequestBodyJsonObject", value={"none_value": "{{ None }}"}), + {}, + ), ( "test_string", - """{"nested": { "key": "{{ config['option'] }}" }}""", + RequestBodyPlainText( + type="RequestBodyPlainText", + value="""{"nested": { "key": "{{ config['option'] }}" }}""", + ), {"nested": {"key": "OPTION"}}, ), ( "test_nested_objects", - {"nested": {"key": "{{ config['option'] }}"}}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", value={"nested": {"key": "{{ config['option'] }}"}} + ), {"nested": {"key": "OPTION"}}, ), ( "test_nested_objects_interpolated keys", - {"nested": {"{{ stream_interval['start_date'] }}": "{{ config['option'] }}"}}, + RequestBodyJsonObject( + type="RequestBodyJsonObject", + value={"nested": {"{{ stream_interval['start_date'] }}": "{{ config['option'] }}"}}, + ), {"nested": {"2020-01-01": "OPTION"}}, ), + ( + "test_graphql_query", + RequestBodyGraphQL( + type="RequestBodyGraphQL", + value=RequestBodyGraphQlQuery( + query={"query_key": "{{ config['option'] }}", "query_key_2": "value"} + ), + ), + {"query": {"query_key": "OPTION", "query_key_2": "value"}}, + ), ], ) def test_interpolated_request_json_using_request_body( test_name, input_request_json, expected_request_json -): +) -> None: provider = InterpolatedRequestOptionsProvider( config=config, - request_body=RequestBody(type="RequestBodyJson", value=input_request_json), + request_body=input_request_json, parameters={}, ) actual_request_json = provider.get_request_body_json( @@ -240,33 +304,54 @@ def test_interpolated_request_data(test_name, input_request_data, expected_reque [ ( "test_static_map_data", - {"a_static_request_param": "a_static_value"}, + RequestBodyUrlEncodedForm( + type="RequestBodyUrlEncodedForm", + value={"a_static_request_param": "a_static_value"}, + ), {"a_static_request_param": "a_static_value"}, ), ( "test_map_depends_on_stream_slice", - {"read_from_slice": "{{ stream_slice['start_date'] }}"}, + RequestBodyUrlEncodedForm( + type="RequestBodyUrlEncodedForm", + value={"read_from_slice": "{{ stream_slice['start_date'] }}"}, + ), {"read_from_slice": "2020-01-01"}, ), ( "test_map_depends_on_config", - {"read_from_config": "{{ config['option'] }}"}, + RequestBodyUrlEncodedForm( + type="RequestBodyUrlEncodedForm", + value={"read_from_config": "{{ config['option'] }}"}, + ), {"read_from_config": "OPTION"}, ), - ("test_defaults_to_empty_dict", None, {}), + ( + "test_defaults_to_empty_dict", + RequestBodyUrlEncodedForm( + type="RequestBodyUrlEncodedForm", + value={}, + ), + {}, + ), ( "test_interpolated_keys", - {"{{ stream_interval['start_date'] }} - {{ next_page_token['offset'] }}": "ABC"}, + RequestBodyUrlEncodedForm( + type="RequestBodyUrlEncodedForm", + value={ + "{{ stream_interval['start_date'] }} - {{ next_page_token['offset'] }}": "ABC" + }, + ), {"2020-01-01 - 12345": "ABC"}, ), ], ) def test_interpolated_request_data_using_request_body( test_name, input_request_data, expected_request_data -): +) -> None: provider = InterpolatedRequestOptionsProvider( config=config, - request_body=RequestBody(type="RequestBodyData", value=input_request_data), + request_body=input_request_data, parameters={}, )