Skip to content

fix: route RequestBodyPlainText to request_body_data instead of request_body_json#971

Draft
Airbyte Support (Airbyte-Support) wants to merge 2 commits intomainfrom
devin/1775019092-fix-request-body-plain-text
Draft

fix: route RequestBodyPlainText to request_body_data instead of request_body_json#971
Airbyte Support (Airbyte-Support) wants to merge 2 commits intomainfrom
devin/1775019092-fix-request-body-plain-text

Conversation

@Airbyte-Support
Copy link
Copy Markdown

@Airbyte-Support Airbyte Support (Airbyte-Support) commented Apr 1, 2026

Summary

RequestBodyPlainText was incorrectly grouped with RequestBodyJsonObject in InterpolatedRequestOptionsProvider._resolve_request_body(), causing plain text body values to be assigned to request_body_json instead of request_body_data. This resulted in a ValueError("Request body json cannot be a string") whenever a user configured a plain-text request body in the Connector Builder — for example, when creating an asynchronous job with a non-JSON body.

The fix splits the combined elif branch so that RequestBodyPlainText routes to self.request_body_data (like RequestBodyUrlEncodedForm already does), while RequestBodyJsonObject continues to route to self.request_body_json.

Migration backward compatibility

The manifest migration (HttpRequesterRequestBodyJsonDataToRequestBody) previously converted string-valued request_body_json fields to RequestBodyPlainText. Before this fix, that worked by accident because RequestBodyPlainText was (incorrectly) routed to request_body_json. Now that RequestBodyPlainText correctly routes to request_body_data, the migration would break existing connectors that rely on string JSON templates being sent as JSON bodies.

The fix: string-valued request_body_json fields are now skipped by the migration and left as request_body_json, where InterpolatedNestedRequestInputProvider already handles them natively (interpolating the template string and parsing it via ast.literal_eval into a dict).

Changes to the migration:

  • should_migrate returns False when the only migratable key is a string request_body_json
  • _migrate_body_json returns early (no-op) for string values instead of converting to RequestBodyPlainText
  • validate accepts manifests where string request_body_json remains unmigrated

Review & Testing Checklist for Human

  • Migration edge cases: Verify that a manifest with both a string request_body_json and a request_body_data key migrates correctly — request_body_data should be migrated to RequestBodyUrlEncodedForm while the string request_body_json is left in place.
  • Existing connectors: Confirm that connectors currently using string-valued request_body_json (e.g., '{"nested": {"key": "{{ config.option }}"}}') continue to send JSON bodies after this change, not raw string data.
  • End-to-end in Connector Builder: Configure an async job creation request with a plain-text request body and confirm the request is sent correctly without the "Request body json cannot be a string" error.
  • String interpolation: Verify that {{ config['option'] }} still works correctly inside plain-text body values — the new test_plain_text_with_interpolation test covers this, but a manual sanity check is worthwhile.

Notes

  • The old test "test_string" in test_interpolated_request_json_using_request_body was encoding the buggy behavior: it expected RequestBodyPlainText with value '{"nested": { "key": "..." }}' to produce a parsed JSON dict. This test has been removed and replaced with two new cases in test_interpolated_request_data_using_request_body.
  • The migration test fixtures in conftest.py have been updated so the expected output for stream "D" (string request_body_json) retains the original request_body_json key rather than converting to RequestBodyPlainText.

Link to Devin session: https://app.devin.ai/sessions/8a5ed7c6c890468ca63a35469e5c58c1
Requested by: Airbyte Support (@Airbyte-Support)


Open with Devin

…st_body_json

Co-Authored-By: syed.khadeer@airbyte.io <cloud-support@airbyte.io>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This CDK Version

You can test this version of the CDK using the following:

# Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1775019092-fix-request-body-plain-text#egg=airbyte-python-cdk[dev]' --help

# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1775019092-fix-request-body-plain-text

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

@syedkhadeer-cmd syedkhadeer-cmd self-assigned this Apr 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

PyTest Results (Fast)

3 976 tests  +1   3 965 ✅ +1   7m 10s ⏱️ -22s
    1 suites ±0      11 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit d023230. ± Comparison against base commit 69cd63d.

This pull request removes 4 and adds 5 tests. Note that renamed tests count towards both.
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_graphql_query-input_request_json13-expected_request_json13]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects-input_request_json11-expected_request_json11]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects_interpolated keys-input_request_json12-expected_request_json12]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_string-input_request_json10-expected_request_json10]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_data_using_request_body[test_plain_text_body-input_request_data5-plain text body content]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_data_using_request_body[test_plain_text_with_interpolation-input_request_data6-interpolate_me=5&option=OPTION]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_graphql_query-input_request_json12-expected_request_json12]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects-input_request_json10-expected_request_json10]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects_interpolated keys-input_request_json11-expected_request_json11]

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +104 to +105
elif self.request_body.type == "RequestBodyPlainText":
self.request_body_data = self.request_body.value
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Routing change for RequestBodyPlainText breaks backward compatibility with manifest migration

The manifest migration at airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py:54-55 converts request_body_json: <string> into RequestBodyPlainText. Before this PR, RequestBodyPlainText was routed to request_body_json, preserving the original semantics. After this PR, it routes to request_body_data. This means any connector whose manifest contains request_body_json as a string (e.g., conftest.py:719: "request_body_json": '{"nested": ...}') will, after the runtime migration converts it to RequestBodyPlainText, now have its body sent via the data= parameter (URL-encoded form / raw string) instead of the json= parameter (Content-Type: application/json) in http_requester.py:473-478. This silently changes the HTTP wire format for affected connectors, potentially breaking API integrations.

Prompt for agents
The migration at airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py lines 54-55 converts request_body_json string values to RequestBodyPlainText. Now that RequestBodyPlainText routes to request_body_data instead of request_body_json, the migration no longer preserves original semantics. Update the migration's _migrate_body_json method so that when request_body_json is a string, it migrates to a type that still routes to request_body_json (e.g., keep it as RequestBodyJsonObject with the string value, or introduce handling that preserves the JSON body semantics). Alternatively, if the intent is that string-valued request_body_json should indeed become plain text body data, update the migration test fixtures in unit_tests/manifest_migrations/conftest.py accordingly and document the breaking change.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This concern is addressed in the second commit (d023230d). The migration now skips string-valued request_body_json fields entirely — they are left as request_body_json where InterpolatedNestedRequestInputProvider handles them natively (interpolates the Jinja template, then parses via ast.literal_eval into a dict). This preserves the original JSON body semantics for existing connectors.

Changes:

  • should_migrate returns False when the only migratable key is a string request_body_json
  • _migrate_body_json returns early (no-op) for string values
  • validate accepts manifests where string request_body_json remains unmigrated
  • Test fixtures updated accordingly

Devin session

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

PyTest Results (Full)

3 979 tests  +1   3 967 ✅ +1   11m 15s ⏱️ -5s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit d023230. ± Comparison against base commit 69cd63d.

This pull request removes 4 and adds 5 tests. Note that renamed tests count towards both.
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_graphql_query-input_request_json13-expected_request_json13]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects-input_request_json11-expected_request_json11]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects_interpolated keys-input_request_json12-expected_request_json12]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_string-input_request_json10-expected_request_json10]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_data_using_request_body[test_plain_text_body-input_request_data5-plain text body content]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_data_using_request_body[test_plain_text_with_interpolation-input_request_data6-interpolate_me=5&option=OPTION]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_graphql_query-input_request_json12-expected_request_json12]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects-input_request_json10-expected_request_json10]
unit_tests.sources.declarative.requesters.request_options.test_interpolated_request_options_provider ‑ test_interpolated_request_json_using_request_body[test_nested_objects_interpolated keys-input_request_json11-expected_request_json11]

♻️ This comment has been updated with latest results.

…migration

String-valued request_body_json fields are no longer migrated to
RequestBodyPlainText (which now correctly routes to request_body_data).
Instead, they are left as request_body_json to preserve their original
JSON body semantics via InterpolatedNestedRequestInputProvider.

Co-Authored-By: syed.khadeer@airbyte.io <cloud-support@airbyte.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants