Fix VaultBackend.get_connection() breaking PythonVirtualenvOperator in Airflow 3#68305
Conversation
380aef2 to
ba7707d
Compare
ba7707d to
105c7b2
Compare
|
Force-pushed twice to update this branch after rebasing onto Drafted-by: Claude Code (Sonnet 4.6); reviewed by @seanmuth before posting |
105c7b2 to
39285a0
Compare
amoghrajesh
left a comment
There was a problem hiding this comment.
One comment, otherwise the root case is on point and the compat sdk swap is the right fix. Preemptive approval once kaxil's concerns are handled
…ualenv tasks Constructing airflow.models.connection.Connection (SQLAlchemy ORM) triggers lazy mapper initialisation for the entire Airflow model registry. In PythonVirtualenvOperator subprocesses, DagModel has not been imported so DagScheduleAssetNameReference cannot resolve its 'DagModel' relationship, raising sqlalchemy.exc.InvalidRequestError. This exception is silently swallowed in context.py's secrets-backend loop, making the connection appear undefined even when Vault returns a valid 200 response. Switch to airflow.providers.common.compat.sdk.Connection (Pydantic in Airflow 3, SQLAlchemy in Airflow 2) which does not trigger mapper init. Also update call sites to use keyword-only conn_id= to match the SDK Connection's constructor signature.
…ri= calls from_uri is a defined classmethod on airflow.sdk.Connection, and the uri= overload is visible via the manual __init__ overloads — neither suppression is needed.
Pydantic or future attrs validators can raise ValueError; catching only TypeError would let those slip through silently the same way this PR fixes the original silent failure.
9c4918b to
8e682f9
Compare
|
thought we fixed that test already, looking |
The attrs mypy plugin overrides the manually-written __init__ overloads, so mypy sees only the attrs-generated __init__ (no uri= param). The ignore is required on the Airflow 2 / pre-3.2 fallback path.
|
@seanmuth — this PR has 2 unresolved review thread(s) that still look like they need your attention. Once you've addressed them (push changes and/or reply in-thread), please resolve the threads or reply to confirm, and give the reviewer a nudge for another look. Thanks! See the PR quality criteria. Automated first-pass triage note drafted by an AI-assisted tool — may get things wrong; once addressed, a real Apache Airflow maintainer takes the next look. (why automated) Drafted-by: Claude Code (Opus 4.8); reviewed by @potiuk before posting |
On it now, last week was an on-call week for me, always causes stuff to fall of my plate. Will also add a quick confirmation that this works the way we're expecting it does 🫡 |
Replace the get_connection() override (which hard-coded the compat SDK Connection) with get_conn_value(), returning a URI string for conn_uri secrets or a JSON-serialized field dict otherwise. The base-class get_connection() then deserializes using _get_connection_class(), which the framework populates with the ORM Connection on the server and the SDK Connection in workers — fixing the mapper-init regression without breaking server-side commands like airflow connections get. Tests call _set_connection_class(SdkConnection) to simulate the framework injection, matching how initialize_secrets_backends() wires things up at runtime.
_set_connection_class was added after 3.0/2.11; guard test calls with hasattr and skip the two isinstance(SdkConnection) tests on older Airflow where class injection is unavailable.
…ersions On Airflow 3.0/2.11, BaseSecretsBackend.get_connection() did not accept team_name. Without a VaultBackend override, calling get_connection with team_name raised TypeError. The override delegates to get_conn_value() then uses deserialize_connection() when available (modern Airflow, class injection works), and falls back to the compat SDK Connection on older Airflow that predates _get_connection_class / deserialize_connection.
amoghrajesh
left a comment
There was a problem hiding this comment.
Sorry for the back and forth, @seanmuth. I guess we could just stick to what @kaxil mentioned the keep the diff manageable and scoped here, I think the diff with his suggestion would be close to:
def get_conn_value(self, conn_id: str, team_name: str | None = None) -> str | None:
response = self._get_team_or_global_secret(self.connections_path, team_name, conn_id)
if response is None:
return None
uri = response.get("conn_uri")
if uri:
return uri
return json.dumps(response)Return conn_uri verbatim or json.dumps(secret_dict) from get_conn_value() so the base-class get_connection() deserializes using the Connection class injected per execution context — fixing the mapper-init regression in workers without regressing server-side CLI commands. Tests assert on get_conn_value() string output directly, removing the _set_connection_class injection pattern. Add both conn_uri and field-based JWT auth tests to cover URI secrets.
kaxil
left a comment
There was a problem hiding this comment.
A couple of comments & PR title revision, then it looks good
amoghrajesh
left a comment
There was a problem hiding this comment.
LGTM apart from the open comments.
Hoist the repeated in-test json import to module level and qualify the get_conn_value docstring so the framework-injected Connection class behavior is scoped to Airflow 3.2+, where per-process injection exists (2.11/3.0/3.1 still import the ORM Connection directly).
|
I think I've addressed everything in the comments? Ready for another pass @kaxil @amoghrajesh |
amoghrajesh
left a comment
There was a problem hiding this comment.
All prior review feedback has been addressed, the get_conn_value() rework correctly delegates to _get_connection_class() injection, and the docstring caveat is in place and correct too. LGTM.
VaultBackend.get_connection()constructed the returnedConnectionusingairflow.models.connection.Connection— the SQLAlchemy ORM model. In Airflow 3,instantiating this model triggers lazy mapper initialisation for the entire
Airflow model registry. In
PythonVirtualenvOperatorsubprocesses (and otherisolated task contexts),
DagModelhas not been imported when the secretsbackend is consulted, so
DagScheduleAssetNameReferencecannot resolve its'DagModel'relationship string, raising:Because
context.py's secrets-backend loop catches all exceptions atDEBUGlevel (without
exc_info), this error is silently swallowed. The connectionappears undefined even when Vault returns a valid
200response with thecorrect data.
Fix: switch to
airflow.providers.common.compat.sdk.Connection, whichresolves to the Pydantic SDK
Connectionin Airflow 3 (no SQLAlchemy mapperinvolvement) and falls back to the SQLAlchemy model in Airflow 2. For the
URI path, use
Connection.from_uri()guarded byAIRFLOW_V_3_0_PLUStohandle both Airflow versions correctly.
Two new regression tests verify that
get_connection()returns an instance ofthe SDK
Connectiontype rather than the SQLAlchemy ORM model.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Sonnet 4.6) following the guidelines
Important
🛠️ Maintainer triage note for @seanmuth · by
@potiuk· 2026-06-17 18:53 UTCSome review feedback from
kaxilis waiting on you:The ball is in your court — you've been assigned to this PR. Reply or push a fix in each thread, then mark them resolved.
Automated triage — may be imperfect; a maintainer takes the next look.