Skip to content

feat(declarative): support XML inside GzipDecoder and ZipfileDecoder - #1131

Draft
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787677605-gzip-xml-decoder
Draft

feat(declarative): support XML inside GzipDecoder and ZipfileDecoder#1131
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787677605-gzip-xml-decoder

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Resolves https://github.com/airbytehq/product-request-backlog/issues/527:

Manifest sources that download gzip-compressed XML (e.g. presigned S3 objects served with Content-Type: application/gzip and no Content-Encoding) could not be configured at all: GzipDecoder.decoder did not accept XmlDecoder in the schema, and even if it had, ModelToComponentFactory._get_parser raised ValueError: Decoder type ... does not have parser associated to it for XmlDecoderModel because the composite raw decoder framework had no XML parser.

This adds XmlParser to the composite framework and allows XML as an inner decoder of both GzipDecoder and ZipfileDecoder:

class XmlParser(Parser):
    encoding: Optional[str] = "utf-8"

    def parse(self, data: BufferedIOBase) -> PARSER_OUTPUT_TYPE:
        # xmltodict.parse over the raw (already decompressed) bytes,
        # ExpatError -> warn + yield {} , same semantics as XmlDecoder

XmlParser reads raw bytes rather than response.text, so it composes with GzipParser, which already detects gzip payloads by magic bytes (#1124) rather than trusting headers. That means all three response shapes work through create_gzip_decoder: Content-Encoding: gzip, Content-Type: application/gzip, and a gzip body with neither header.

Manifest that now works:

decoder:
  type: GzipDecoder
  decoder:
    type: XmlDecoder

Note this is CDK/manifest-level support; the Connector Builder UI may need a separate change to surface XML as a gzip inner decoder in the dropdown.

Declarative-First Evaluation

No custom Python component was added to any connector. The gap was in the CDK's built-in decoder framework itself — no declarative combination of existing decoders can decompress gzip and then parse XML, since XmlDecoder operates on response.text. The fix extends the built-in composite parser framework (following the existing CsvParser/JsonParser pattern) so the behavior remains fully declarative for connector authors.

Test Coverage

  • unit_tests/sources/declarative/decoders/test_composite_decoder.py: XmlParser output (attributes as @id, repeated elements, malformed XML yielding {}), GzipParser(XmlParser()) composition, and a requests_mock end-to-end decode.
  • unit_tests/sources/declarative/parsers/test_model_to_component_factory.py: test_create_gzip_decoder_handles_xml_inner_decoder builds the model through create_gzip_decoder and parametrizes over Content-Type: application/gzip, application/x-gzip, application/xml (no compression header) and Content-Encoding: gzip, with emit_connector_builder_messages both on and off.

These tests fail on main — constructing GzipDecoderModel(decoder=XmlDecoderModel(...)) raises a pydantic ValidationError there, and XmlParser does not exist.

Local verification:

poetry run pytest unit_tests/sources/declarative -q   # 1802 passed, 1 skipped
poetry run ruff check . && poetry run ruff format --check .
poetry run mypy --config-file mypy.ini airbyte_cdk    # only the pre-existing pytz stub error

Not a breaking change: no field is removed, renamed, or retyped, no stream/state/cursor behavior changes, and every previously valid decoder configuration remains valid — the schema unions only gain an additional allowed member.

Requested by Devin Bot via the /ai-fix workflow.

Link to Devin session: https://app.devin.ai/sessions/37af92dfe8b948098f0304c7977821f8

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 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, CI, and merge conflict monitoring

@github-actions

Copy link
Copy Markdown

👋 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/1787677605-gzip-xml-decoder#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/1787677605-gzip-xml-decoder

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.

Copilot AI left a comment

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.

Pull request overview

This PR extends the CDK’s declarative “composite raw decoder” framework to support parsing XML after decompression, enabling manifests like GzipDecoder(XmlDecoder) and ZipfileDecoder(XmlDecoder) to work (including cases where gzip must be detected by payload magic bytes rather than HTTP headers).

Changes:

  • Adds a new XmlParser (byte-oriented) to the composite parser framework and wires it into ModelToComponentFactory._get_parser.
  • Expands declarative schema unions so XmlDecoder is a valid inner decoder for GzipDecoder and ZipfileDecoder.
  • Adds unit tests covering XmlParser behavior, GzipParser(XmlParser()) composition, and end-to-end factory creation for gzip+xml.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py Adds XmlParser that parses XML from raw bytes to enable composition with gzip/zip parsers.
airbyte_cdk/sources/declarative/decoders/__init__.py Exposes XmlParser from the declarative decoders package.
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py Adds XmlDecoderModel -> XmlParser mapping in _get_parser so composite decoders can build XML parsing pipelines.
airbyte_cdk/sources/declarative/models/declarative_component_schema.py Updates pydantic model unions to allow XmlDecoder inside GzipDecoder and ZipfileDecoder.
airbyte_cdk/sources/declarative/declarative_component_schema.yaml Mirrors schema union updates for declarative component YAML schema.
unit_tests/sources/declarative/decoders/test_composite_decoder.py Adds direct XmlParser tests + gzip composition + mocked-request decode test.
unit_tests/sources/declarative/parsers/test_model_to_component_factory.py Adds factory-level test ensuring create_gzip_decoder can accept an XML inner decoder across header variations and builder/sync modes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

Copy link
Copy Markdown

PyTest Results (Fast)

4 376 tests  +13   4 365 ✅ +13   10m 42s ⏱️ + 1m 55s
    1 suites ± 0      11 💤 ± 0 
    1 files   ± 0       0 ❌ ± 0 

Results for commit f3ab903. ± Comparison against base commit 4855c2d.

@github-actions

Copy link
Copy Markdown

PyTest Results (Full)

4 379 tests  +13   4 367 ✅ +13   13m 31s ⏱️ -29s
    1 suites ± 0      12 💤 ± 0 
    1 files   ± 0       0 ❌ ± 0 

Results for commit f3ab903. ± Comparison against base commit 4855c2d.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants