diff --git a/shared/secrets_backend/src/airflow_shared/secrets_backend/base.py b/shared/secrets_backend/src/airflow_shared/secrets_backend/base.py index e61197d2baa7a..77c08be45f1bd 100644 --- a/shared/secrets_backend/src/airflow_shared/secrets_backend/base.py +++ b/shared/secrets_backend/src/airflow_shared/secrets_backend/base.py @@ -122,6 +122,13 @@ def _get_connection_class(self) -> type: def _deserialize_connection_value(conn_class: type, conn_id: str, value: str): value = value.strip() if value[0] == "{": + # JSON secrets stored by Airflow 2 backends may lack both "conn_type" + # and "uri" (e.g. {"host": ..., "login": ..., "password": ...}). This is + # valid: conn_type is intentionally optional on the SDK Connection model + # for Airflow 2 -> 3 migration compatibility. + # Check: https://github.com/apache/airflow/pull/61728 + # TODO: Remove this compatibility once the minimum supported Airflow + # version in providers is 3.0. return conn_class.from_json(value=value, conn_id=conn_id) # type: ignore[attr-defined] # TODO: Only sdk has from_uri defined on it. Is it worthwhile developing the core path or not? diff --git a/shared/secrets_backend/tests/secrets_backend/test_base.py b/shared/secrets_backend/tests/secrets_backend/test_base.py index e14e620424392..32d9993ae1ee5 100644 --- a/shared/secrets_backend/tests/secrets_backend/test_base.py +++ b/shared/secrets_backend/tests/secrets_backend/test_base.py @@ -124,6 +124,21 @@ def test_deserialize_connection_json(self, sample_conn_json): assert conn.conn_id == "test_conn" assert conn._kwargs["conn_type"] == "mysql" + def test_deserialize_connection_json_without_conn_type(self): + """ + Guards the Airflow 2 -> 3 migration compatibility established in + https://github.com/apache/airflow/pull/61728. + """ + backend = _TestBackend() + backend._set_connection_class(MockConnection) + + conn = backend.deserialize_connection( + "test_conn", '{"host": "example.com", "login": "admin", "password": "secret"}' + ) + assert isinstance(conn, MockConnection) + assert conn.conn_id == "test_conn" + assert conn._kwargs == {"host": "example.com", "login": "admin", "password": "secret"} + def test_deserialize_connection_uri(self, sample_conn_uri): """Test deserialize_connection with URI format through _TestBackend.""" backend = _TestBackend()