From 18497d8411d13e7d231478067b4b0c441e1e7187 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 04:17:44 +0000 Subject: [PATCH 1/3] fix: download components.zip instead of components.py from registry The Airbyte connector registry serves custom components as components.zip archives, but PyAirbyte was trying to fetch components.py (which 404s). This caused all 56 manifest-only connectors with custom Python components to silently fail. Now downloads components.zip and extracts components.py from the archive before passing it to DeclarativeExecutor. Closes #997 Co-Authored-By: AJ Steers --- airbyte/_executors/util.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/airbyte/_executors/util.py b/airbyte/_executors/util.py index ca4dd7d2f..3353c3c07 100644 --- a/airbyte/_executors/util.py +++ b/airbyte/_executors/util.py @@ -2,8 +2,10 @@ from __future__ import annotations import hashlib +import io import sys import tempfile +import zipfile from pathlib import Path from typing import TYPE_CHECKING, Literal, cast @@ -33,7 +35,7 @@ "https://connectors.airbyte.com/files/metadata/airbyte/{source_name}/{version}/manifest.yaml" ) DEFAULT_COMPONENTS_URL = ( - "https://connectors.airbyte.com/files/metadata/airbyte/{source_name}/{version}/components.py" + "https://connectors.airbyte.com/files/metadata/airbyte/{source_name}/{version}/components.zip" ) @@ -44,12 +46,15 @@ def _try_get_manifest_connector_files( """Try to get source manifest and components.py from URLs. Returns tuple of (manifest_dict, components_py_content, components_py_checksum). - Components values are None if components.py is not found (404 is handled gracefully). + Components values are None if components.zip is not found (404 is handled gracefully). + + The registry serves custom components as `components.zip` archives. This function + downloads and extracts `components.py` from the zip archive. Raises: - `PyAirbyteInputError`: If `source_name` is `None`. - `AirbyteConnectorInstallationError`: If the manifest cannot be downloaded or parsed, - or if components.py cannot be downloaded (excluding 404 errors). + or if components.zip cannot be downloaded or extracted (excluding 404 errors). """ if source_name is None: raise exc.PyAirbyteInputError( @@ -104,13 +109,23 @@ def _try_get_manifest_connector_files( response.raise_for_status() except requests.exceptions.HTTPError as ex: raise exc.AirbyteConnectorInstallationError( - message="Failed to download the connector components.py file.", + message="Failed to download the connector components.zip file.", + context={ + "components_url": components_url, + }, + ) from ex + + try: + with zipfile.ZipFile(io.BytesIO(response.content)) as zf: + components_content = zf.read("components.py").decode("utf-8") + except (zipfile.BadZipFile, KeyError) as ex: + raise exc.AirbyteConnectorInstallationError( + message="Failed to extract components.py from components.zip.", context={ "components_url": components_url, }, ) from ex - components_content = response.text components_py_checksum = hashlib.md5(components_content.encode()).hexdigest() return manifest_dict, components_content, components_py_checksum From 039daf3dc5fdc1410c6957d90cab08824e8e5545 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 04:22:58 +0000 Subject: [PATCH 2/3] fix: also catch UnicodeDecodeError during zip extraction Co-Authored-By: AJ Steers --- airbyte/_executors/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte/_executors/util.py b/airbyte/_executors/util.py index 3353c3c07..a4632e545 100644 --- a/airbyte/_executors/util.py +++ b/airbyte/_executors/util.py @@ -118,7 +118,7 @@ def _try_get_manifest_connector_files( try: with zipfile.ZipFile(io.BytesIO(response.content)) as zf: components_content = zf.read("components.py").decode("utf-8") - except (zipfile.BadZipFile, KeyError) as ex: + except (zipfile.BadZipFile, KeyError, UnicodeDecodeError) as ex: raise exc.AirbyteConnectorInstallationError( message="Failed to extract components.py from components.zip.", context={ From 84591e575515dac9c359e381192a0abcdbd55f5a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 04:29:36 +0000 Subject: [PATCH 3/3] docs: update docstring opening line to reflect components.zip behavior Co-Authored-By: AJ Steers --- airbyte/_executors/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte/_executors/util.py b/airbyte/_executors/util.py index a4632e545..7a30a1bf1 100644 --- a/airbyte/_executors/util.py +++ b/airbyte/_executors/util.py @@ -43,7 +43,7 @@ def _try_get_manifest_connector_files( source_name: str, version: str | None = None, ) -> tuple[dict, str | None, str | None]: - """Try to get source manifest and components.py from URLs. + """Try to get the source manifest and components.zip from URLs and extract components.py. Returns tuple of (manifest_dict, components_py_content, components_py_checksum). Components values are None if components.zip is not found (404 is handled gracefully).