Skip to content

Fix VaultBackend.get_connection() breaking PythonVirtualenvOperator in Airflow 3#68305

Merged
amoghrajesh merged 10 commits into
apache:mainfrom
seanmuth:fix-vault-connection-sdk-compat
Jul 14, 2026
Merged

Fix VaultBackend.get_connection() breaking PythonVirtualenvOperator in Airflow 3#68305
amoghrajesh merged 10 commits into
apache:mainfrom
seanmuth:fix-vault-connection-sdk-compat

Conversation

@seanmuth

@seanmuth seanmuth commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

VaultBackend.get_connection() constructed the returned Connection using
airflow.models.connection.Connection — the SQLAlchemy ORM model. In Airflow 3,
instantiating this model triggers lazy mapper initialisation for the entire
Airflow model registry. In PythonVirtualenvOperator subprocesses (and other
isolated task contexts), DagModel has not been imported when the secrets
backend is consulted, so DagScheduleAssetNameReference cannot resolve its
'DagModel' relationship string, raising:

sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize …
Triggering mapper: 'mapped class DagScheduleAssetNameReference->dag_schedule_asset_name_reference'.
Original exception was: … expression 'DagModel' failed to locate a name ('DagModel').

Because context.py's secrets-backend loop catches all exceptions at DEBUG
level (without exc_info), this error is silently swallowed. The connection
appears undefined even when Vault returns a valid 200 response with the
correct data.

Fix: switch to airflow.providers.common.compat.sdk.Connection, which
resolves to the Pydantic SDK Connection in Airflow 3 (no SQLAlchemy mapper
involvement) and falls back to the SQLAlchemy model in Airflow 2. For the
URI path, use Connection.from_uri() guarded by AIRFLOW_V_3_0_PLUS to
handle both Airflow versions correctly.

Two new regression tests verify that get_connection() returns an instance of
the SDK Connection type rather than the SQLAlchemy ORM model.


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Sonnet 4.6)

Generated-by: Claude Code (Sonnet 4.6) following the guidelines


Important

🛠️ Maintainer triage note for @seanmuth · by @potiuk · 2026-06-17 18:53 UTC

Some review feedback from kaxil is 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.

@seanmuth seanmuth requested a review from hussein-awala as a code owner June 9, 2026 19:03
@boring-cyborg boring-cyborg Bot added area:providers area:secrets provider:hashicorp Hashicorp provider related issues labels Jun 9, 2026
@seanmuth seanmuth force-pushed the fix-vault-connection-sdk-compat branch from 380aef2 to ba7707d Compare June 9, 2026 19:17
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
@kaxil kaxil requested a review from amoghrajesh June 9, 2026 20:18
@seanmuth seanmuth force-pushed the fix-vault-connection-sdk-compat branch from ba7707d to 105c7b2 Compare June 9, 2026 20:31
@seanmuth

seanmuth commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Force-pushed twice to update this branch after rebasing onto main. Both times the pre-push hook ran against all files that diverged between the remote and local tip (including upstream commits pulled in by the rebase), hitting pre-existing failures in unrelated files (providers/common/ai, etc.) — not caused by changes in this PR.


Drafted-by: Claude Code (Sonnet 4.6); reviewed by @seanmuth before posting

@seanmuth seanmuth force-pushed the fix-vault-connection-sdk-compat branch from 105c7b2 to 39285a0 Compare June 9, 2026 21:15
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated

@amoghrajesh amoghrajesh 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.

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

Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
seanmuth added 3 commits June 11, 2026 00:53
…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.
@kaxil kaxil force-pushed the fix-vault-connection-sdk-compat branch from 9c4918b to 8e682f9 Compare June 10, 2026 23:53
@seanmuth

Copy link
Copy Markdown
Contributor Author

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.
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated

@amoghrajesh amoghrajesh 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.

Requesting changes to avoid accidental merge, @kaxil's suggestion is right on this one. We can merge once that's done.

@seanmuth pls validate if the fix works practically too, theory wise it seems perfect.

Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
@potiuk

potiuk commented Jun 25, 2026

Copy link
Copy Markdown
Member

@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

@seanmuth

Copy link
Copy Markdown
Contributor Author

Requesting changes to avoid accidental merge, @kaxil's suggestion is right on this one. We can merge once that's done.

@seanmuth pls validate if the fix works practically too, theory wise it seems perfect.

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 🫡

seanmuth added 3 commits June 26, 2026 10:20
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.
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
Comment thread providers/hashicorp/tests/unit/hashicorp/secrets/test_vault.py Outdated

@amoghrajesh amoghrajesh 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.

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)

Comment thread providers/hashicorp/tests/unit/hashicorp/secrets/test_vault.py
Comment thread providers/hashicorp/src/airflow/providers/hashicorp/secrets/vault.py Outdated
Comment thread providers/hashicorp/tests/unit/hashicorp/secrets/test_vault.py Outdated
Comment thread providers/hashicorp/tests/unit/hashicorp/secrets/test_vault.py Outdated
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.
Comment thread providers/hashicorp/tests/unit/hashicorp/secrets/test_vault.py Outdated

@kaxil kaxil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments & PR title revision, then it looks good

@amoghrajesh amoghrajesh 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.

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).
@seanmuth

seanmuth commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

I think I've addressed everything in the comments? Ready for another pass @kaxil @amoghrajesh

@amoghrajesh amoghrajesh 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.

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.

@amoghrajesh amoghrajesh merged commit 4112a4a into apache:main Jul 14, 2026
79 checks passed
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.

4 participants