From be59994b882352694fef7c5097e1965687f5f38d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:47:04 +0000 Subject: [PATCH] fix(registry): fall back to git commit timestamp for release_date Registry entries without a top-level releaseDate left ConnectorMetadata.release_date null even though the entry's generated.git.commit_timestamp is present. The entry-to-metadata conversion now uses the commit timestamp date as a fallback, and fetch_registry_version_date resolves the connector's current version from its own entry before checking releaseCandidates. Co-Authored-By: AJ Steers --- airbyte/_registry_utils.py | 29 ++++--- airbyte/registry.py | 8 +- .../unit_tests/test_mcp_connector_registry.py | 86 +++++++++++++++++++ 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/airbyte/_registry_utils.py b/airbyte/_registry_utils.py index b98c9eafc..f444f7cd5 100644 --- a/airbyte/_registry_utils.py +++ b/airbyte/_registry_utils.py @@ -83,12 +83,22 @@ def parse_changelog_html( # noqa: PLR0914 return versions +def registry_entry_commit_date(entry: dict) -> str | None: + """Return the YYYY-MM-DD date of a registry entry's git commit timestamp.""" + commit_timestamp = entry.get("generated", {}).get("git", {}).get("commit_timestamp") + if isinstance(commit_timestamp, str): + date_match = re.match(r"(\d{4}-\d{2}-\d{2})", commit_timestamp) + if date_match: + return date_match.group(1) + return None + + def fetch_registry_version_date(connector_name: str, version: str) -> str | None: """Fetch the release date for a specific version from the registry. Returns the release date string (YYYY-MM-DD) if found, None otherwise. """ - try: # noqa: PLR1702 + try: registry_url = "https://connectors.airbyte.com/files/registries/v0/oss_registry.json" response = requests.get(registry_url, timeout=10) response.raise_for_status() @@ -99,19 +109,18 @@ def fetch_registry_version_date(connector_name: str, version: str) -> str | None for connector in connector_list: docker_repo = connector.get("dockerRepository", "") if docker_repo == f"airbyte/{connector_name}": + if connector.get("dockerImageTag") == version: + current_version_date = registry_entry_commit_date(connector) + if current_version_date: + return current_version_date + releases = connector.get("releases", {}) release_candidates = releases.get("releaseCandidates", {}) if version in release_candidates: - version_data = release_candidates[version] - generated = version_data.get("generated", {}) - git_info = generated.get("git", {}) - commit_timestamp = git_info.get("commit_timestamp") - - if commit_timestamp: - date_match = re.match(r"(\d{4}-\d{2}-\d{2})", commit_timestamp) - if date_match: - return date_match.group(1) + candidate_date = registry_entry_commit_date(release_candidates[version]) + if candidate_date: + return candidate_date break else: diff --git a/airbyte/registry.py b/airbyte/registry.py index c715cd4c9..60130ad78 100644 --- a/airbyte/registry.py +++ b/airbyte/registry.py @@ -18,7 +18,11 @@ from pydantic import BaseModel, Field from airbyte import exceptions as exc -from airbyte._registry_utils import fetch_registry_version_date, parse_changelog_html +from airbyte._registry_utils import ( + fetch_registry_version_date, + parse_changelog_html, + registry_entry_commit_date, +) from airbyte._util.compat import StrEnum from airbyte._util.meta import is_docker_installed from airbyte.constants import AIRBYTE_OFFLINE_MODE @@ -224,7 +228,7 @@ def _registry_entry_to_connector_metadata(entry: dict) -> ConnectorMetadata: release_stage=entry.get("releaseStage"), source_type=entry.get("sourceType"), documentation_url=entry.get("documentationUrl"), - release_date=entry.get("releaseDate"), + release_date=entry.get("releaseDate") or registry_entry_commit_date(entry), github_issue_label=entry.get("githubIssueLabel"), ) diff --git a/tests/unit_tests/test_mcp_connector_registry.py b/tests/unit_tests/test_mcp_connector_registry.py index 8a098a85f..ea66dd356 100644 --- a/tests/unit_tests/test_mcp_connector_registry.py +++ b/tests/unit_tests/test_mcp_connector_registry.py @@ -646,3 +646,89 @@ def test_get_connector_info_spec_none_when_registry_has_no_spec() -> None: assert result.config_spec_jsonschema is None assert result.manifest_url is not None connector.install.assert_not_called() + + +def _registry_entry(**overrides: object) -> dict: + """Build a minimal registry entry for `source-faker`.""" + entry: dict = { + "dockerRepository": "airbyte/source-faker", + "dockerImageTag": "7.2.1", + "sourceDefinitionId": "source-faker-definition", + "name": "Faker", + "tags": [], + "suggestedStreams": {"streams": []}, + } + entry.update(overrides) + return entry + + +def test_registry_entry_release_date_falls_back_to_commit_timestamp() -> None: + """Entries without `releaseDate` use the version's git commit timestamp date.""" + from airbyte.registry import _registry_entry_to_connector_metadata + + entry = _registry_entry( + generated={"git": {"commit_timestamp": "2026-07-10T14:26:59-07:00"}}, + ) + metadata = _registry_entry_to_connector_metadata(entry) + + assert metadata.release_date == "2026-07-10" + + +def test_registry_entry_release_date_prefers_release_date_field() -> None: + """An explicit `releaseDate` wins over the commit timestamp.""" + from airbyte.registry import _registry_entry_to_connector_metadata + + entry = _registry_entry( + releaseDate="2026-01-01", + generated={"git": {"commit_timestamp": "2026-07-10T14:26:59-07:00"}}, + ) + metadata = _registry_entry_to_connector_metadata(entry) + + assert metadata.release_date == "2026-01-01" + + +def test_registry_entry_release_date_none_without_any_date() -> None: + """Entries with neither `releaseDate` nor a commit timestamp report None.""" + from airbyte.registry import _registry_entry_to_connector_metadata + + metadata = _registry_entry_to_connector_metadata(_registry_entry()) + + assert metadata.release_date is None + + +def _mock_registry_response(entry: dict) -> MagicMock: + response = MagicMock() + response.json.return_value = {"sources": [entry], "destinations": []} + response.raise_for_status = MagicMock() + return response + + +def test_fetch_registry_version_date_uses_current_version_commit_timestamp() -> None: + """The connector's own entry supplies the date for its current version.""" + from airbyte._registry_utils import fetch_registry_version_date + + entry = _registry_entry( + generated={"git": {"commit_timestamp": "2026-07-10T14:26:59-07:00"}}, + ) + with patch("airbyte._registry_utils.requests.get") as mock_get: + mock_get.return_value = _mock_registry_response(entry) + assert fetch_registry_version_date("source-faker", "7.2.1") == "2026-07-10" + + +def test_fetch_registry_version_date_reads_release_candidates() -> None: + """Non-current versions still resolve through `releases.releaseCandidates`.""" + from airbyte._registry_utils import fetch_registry_version_date + + entry = _registry_entry( + releases={ + "releaseCandidates": { + "8.0.0-rc.1": { + "generated": {"git": {"commit_timestamp": "2026-08-01T00:00:00Z"}}, + }, + }, + }, + ) + with patch("airbyte._registry_utils.requests.get") as mock_get: + mock_get.return_value = _mock_registry_response(entry) + assert fetch_registry_version_date("source-faker", "8.0.0-rc.1") == "2026-08-01" + assert fetch_registry_version_date("source-faker", "0.0.0") is None