Skip to content

Add asyncpg SQLAlchemy engine factory for Entra authentication - #48368

Open
pabloacan wants to merge 4 commits into
Azure:mainfrom
pabloacan:feature/GH-48365-asyncpg-sqlalchemy-entra-authentication
Open

Add asyncpg SQLAlchemy engine factory for Entra authentication#48368
pabloacan wants to merge 4 commits into
Azure:mainfrom
pabloacan:feature/GH-48365-asyncpg-sqlalchemy-entra-authentication

Conversation

@pabloacan

Copy link
Copy Markdown

Description

Adds create_asyncpg_engine to azure-postgresql-auth for SQLAlchemy async engines using asyncpg and AsyncTokenCredential implementations such as azure.identity.aio.DefaultAzureCredential.

The factory retrieves Microsoft Entra connection information asynchronously whenever SQLAlchemy opens a physical pooled connection, then passes the Entra principal and token to asyncpg.connect.

Adds the asyncpg optional dependency, documentation, release history, and unit coverage.

Fixes #48365.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

Tests: 15 passed, 5 deselected with tests/test_sqlalchemy_async.py -m 'not live_test_only'; live Azure tests were not run.

Copilot AI balanced review requested due to automatic review settings July 30, 2026 14:50
@github-actions github-actions Bot added Community Contribution Community members are working on the issue customer-reported Issues that are reported by GitHub users external to the Azure organization. labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution @pabloacan! We will review the pull request and get back to you soon.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
8 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI 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.

🟡 Not ready to approve

Conflicting connection arguments can override the fetched Entra credentials, and required documentation and test coverage remain incomplete.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Adds asyncpg-backed SQLAlchemy engine creation using asynchronous Microsoft Entra credentials.

Changes:

  • Adds create_asyncpg_engine and public export.
  • Adds asyncpg dependencies, documentation, and changelog entry.
  • Adds mocked unit coverage for credential injection and failures.
File summaries
File Description
tests/test_sqlalchemy_async.py Adds asyncpg integration tests.
README.md Documents installation and usage.
pyproject.toml Adds asyncpg and SQLAlchemy asyncio extras.
dev_requirements.txt Adds asyncpg for development.
CHANGELOG.md Records the new factory.
sqlalchemy/asyncpg.py Implements the engine factory.
sqlalchemy/__init__.py Exports the new API.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 4
  • Review effort level: Medium

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +214 to +216
```python
from azure.identity.aio import DefaultAzureCredential
from azure_postgresql_auth.sqlalchemy import create_asyncpg_engine
Comment on lines +71 to +75
connection_kwargs = {
**connect_args,
"user": connect_args.get("user", entra_conninfo["user"]),
"password": connect_args.get("password", entra_conninfo["password"]),
}
Comment on lines +147 to +148
class TestSqlalchemyAsyncpgEntraAuthentication:
"""Tests for asyncpg SQLAlchemy Entra authentication integration."""
"""

from .async_entra_connection import enable_entra_authentication_async
from .asyncpg import create_asyncpg_engine
Copilot AI review requested due to automatic review settings July 30, 2026 14:56
@pabloacan

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Industria de Diseño Textil, S.A."

@pabloacan

Copy link
Copy Markdown
Author

Unlike the existing do_connect integration, this helper must create the driver connection directly through asyncpg.connect. As a result, it is responsible for forwarding the SQLAlchemy URL and connect_args values needed to recreate a physical connection.

This is intentional: SQLAlchemy's do_connect event is synchronous, including when used through an AsyncEngine, so it cannot await azure.identity.aio token acquisition. Calling a synchronous credential from that hook would block the event loop.

Using SQLAlchemy's async_creator lets the helper await get_entra_conninfo_async before opening each new physical pooled connection. Existing pooled connections are still reused normally; the asynchronous token lookup only happens when SQLAlchemy needs to create a replacement connection.

Copilot AI 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.

🟡 Not ready to approve

Credential precedence, SQLAlchemy compatibility, documentation, and required test coverage need correction.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Comments suppressed due to low confidence (5)

sdk/postgresql/azure-postgresql-auth/azure_postgresql_auth/sqlalchemy/asyncpg.py:74

  • Always use the freshly acquired Entra principal and token here. As written, any username or password present in the URL or connect_args silently takes precedence, so a conventional URL such as postgresql+asyncpg://user@host/db authenticates as that user and an accidentally retained password bypasses Entra authentication entirely.
            "user": connect_args.get("user", entra_conninfo["user"]),
            "password": connect_args.get("password", entra_conninfo["password"]),

sdk/postgresql/azure-postgresql-auth/README.md:216

  • This standalone example calls text(...) without importing it, so copying the documented example raises NameError before any connection is made.
```python
from azure.identity.aio import DefaultAzureCredential
from azure_postgresql_auth.sqlalchemy import create_asyncpg_engine

sdk/postgresql/azure-postgresql-auth/tests/test_sqlalchemy_async.py:148

  • The linked issue explicitly requires concurrent-creation unit coverage and a live test using DefaultAzureCredential, but the new asyncpg test class only exercises one mocked connection and credential validation/failure. The existing concurrent/live tests below cover the older psycopg event-hook path, so they cannot detect asyncpg factory integration or concurrency regressions.
class TestSqlalchemyAsyncpgEntraAuthentication:
    """Tests for asyncpg SQLAlchemy Entra authentication integration."""

sdk/postgresql/azure-postgresql-auth/azure_postgresql_auth/sqlalchemy/init.py:26

  • Exporting this new public helper leaves the module-level Requirements and Functions documentation above stale: it still lists only the two event-hook helpers and gives no asyncpg installation requirement. Update that overview so generated module documentation exposes the new supported integration.
from .async_entra_connection import enable_entra_authentication_async
from .asyncpg import create_asyncpg_engine

sdk/postgresql/azure-postgresql-auth/pyproject.toml:40

  • async_creator was added in SQLAlchemy 2.0.16, but this extra still permits 2.0.0–2.0.15. Environments resolved to those declared-supported versions will reject the new keyword when this factory calls create_async_engine; raise the minimum to the first version that provides this API.
    "sqlalchemy[asyncio]>=2.0.0",
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@github-actions

Copy link
Copy Markdown
Contributor
[Pilot] PR Pipeline Failure Analysis

A CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green.

What failed

The pylint validation check for sdk/postgresql/azure-postgresql-auth failed with exit code 16 (build log). Three docstring violations were detected in azure_postgresql_auth/sqlalchemy/asyncpg.py at line 27 (function create_asyncpg_engine):

  • C4740 (docstring-missing-type): Parameter types are missing for url, credential, and kwargs
  • C4743 (docstring-should-be-keyword): kwargs is not listed as a named parameter — it should use :keyword type myarg: format
  • C4742 (docstring-missing-rtype): Return type is missing from the docstring

Recommended next steps

  • In azure_postgresql_auth/sqlalchemy/asyncpg.py, update the docstring for create_asyncpg_engine to add :type url:, :type credential:, and :rtype: entries, and change the kwargs entry to use :keyword type kwargs: format. See the [Azure SDK Python docstring guidelines]((azure.github.io/redacted)
  • Run azpysdk pylint . locally from sdk/postgresql/azure-postgresql-auth/ to verify the fix before pushing.
  • See the CI troubleshooting guide: https://aka.ms/ci-fix
  • Push new commits to address the failures; this comment updates automatically on the next failing run.
Raw pipeline analysis (azsdk ci analyze)
Failed Tasks
--------------------------------------------------------------------------------
### Errors:

[azure-postgresql-auth :: pylint] ************* Module azure_postgresql_auth.sqlalchemy.asyncpg
azure_postgresql_auth/sqlalchemy/asyncpg.py:27: [C4740(docstring-missing-type), create_asyncpg_engine] Param types missing in docstring: "url, credential, kwargs". See details: (azure.github.io/redacted)
azure_postgresql_auth/sqlalchemy/asyncpg.py:27: [C4743(docstring-should-be-keyword), create_asyncpg_engine] "kwargs" not found as a parameter. Use :keyword type myarg: if a keyword argument. See details: (azure.github.io/redacted)
azure_postgresql_auth/sqlalchemy/asyncpg.py:27: [C4742(docstring-missing-rtype), create_asyncpg_engine] A return type is missing in the docstring. See details: (azure.github.io/redacted)

-----------------------------------
Your code has been rated at 9.88/10

[ERROR] azure-sdk-tools: azure-postgresql-auth main package exited with linting error 16.
Please see this link for more information https://aka.ms/azsdk/python/pylint-guide

SUMMARY
PACKAGE                                                  CHECK   STATUS      DURATION(s)
------------------------------------------------------------------------------------
/mnt/vss/_work/1/s/sdk/postgresql/azure-postgresql-auth  pylint  FAIL(16)    9.86

Total checks: 1 | Failed: 1 | Worst exit code: 16

### Pipeline: https://dev.azure.com/azure-sdk/public/_build/results?buildId=6640198

Copilot detected the failing pipeline and generated the analysis above. To have it attempt a fix automatically, reply with @copilot please fix the failing pipeline on this PR.

Generated by Pipeline Analysis - Next Steps · 27.5 AIC · ⌖ 6.21 AIC · ⊞ 6.6K ·

Copilot AI review requested due to automatic review settings August 4, 2026 08:04

Copilot AI 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.

🟡 Not ready to approve

The dependency floor is incompatible, credentials can bypass Entra authentication, and required concurrent/live coverage is missing.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (4)

sdk/postgresql/azure-postgresql-auth/tests/test_sqlalchemy_async.py:156

  • The linked issue requires concurrent connection-creation coverage and a live test for this asyncpg factory, but these tests invoke the captured creator only once with mocks. The existing concurrent/live cases below still exercise enable_entra_authentication_async, so they cannot catch failures in SQLAlchemy's async_creator adaptation or the asyncpg path. Add concurrent unit coverage and a live test using create_asyncpg_engine with the async credential fixture.
    async def test_creates_asyncpg_connection_with_entra_credentials(
        self, mock_connect, mock_get_conninfo, mock_create_engine
    ):

sdk/postgresql/azure-postgresql-auth/azure_postgresql_auth/sqlalchemy/init.py:26

  • Exporting the new public helper makes this module docstring's requirements and function inventory incomplete: it still lists only the two event-hook helpers and says the SQLAlchemy extra installs sqlalchemy>=2.0.0. Document the asyncpg extra and create_asyncpg_engine here as well.
from .asyncpg import create_asyncpg_engine

sdk/postgresql/azure-postgresql-auth/pyproject.toml:40

  • async_creator was added in SQLAlchemy 2.0.16, so the declared >=2.0.0 range allows versions where this factory fails when async_creator is forwarded. Raise the minimum to 2.0.16.
    "sqlalchemy[asyncio]>=2.0.0",

sdk/postgresql/azure-postgresql-auth/README.md:216

  • This new example calls text(...) without importing it, so running the documented snippet raises NameError. Add the SQLAlchemy import to keep the example executable.
from azure.identity.aio import DefaultAzureCredential
from azure_postgresql_auth.sqlalchemy import create_asyncpg_engine
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +75 to +79
connection_kwargs = {
**connect_args,
"user": connect_args.get("user", entra_conninfo["user"]),
"password": connect_args.get("password", entra_conninfo["password"]),
}
Copilot AI review requested due to automatic review settings August 4, 2026 08:27

Copilot AI 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.

🟡 Not ready to approve

The dependency floor, token precedence, documentation example, and required test coverage need correction.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

sdk/postgresql/azure-postgresql-auth/tests/test_sqlalchemy_async.py:148

  • The linked issue explicitly requires concurrent connection-creation unit coverage and a live test using an asynchronous Azure Identity credential. These added tests invoke only one mocked creator at a time, while the existing live class below still exercises enable_entra_authentication_async with the synchronous credential fixture. Add a concurrent creator/pool test and a live create_asyncpg_engine test using async_credential.
class TestSqlalchemyAsyncpgEntraAuthentication:
    """Tests for asyncpg SQLAlchemy Entra authentication integration."""

sdk/postgresql/azure-postgresql-auth/azure_postgresql_auth/sqlalchemy/asyncpg.py:79

  • A password supplied in the URL or connect_args currently overrides the freshly acquired Entra token. That makes every pool connection keep using a static or expired password even though a new token was retrieved, contradicting this factory's authentication contract and causing reconnections to fail. Always use the newly acquired token as the password; an explicit user override can remain if that is intentional.
        connection_kwargs = {
            **connect_args,
            "user": connect_args.get("user", entra_conninfo["user"]),
            "password": connect_args.get("password", entra_conninfo["password"]),
        }

sdk/postgresql/azure-postgresql-auth/README.md:216

  • This example calls text(...) below but never imports it, so the documented copy/paste example raises NameError. Include the SQLAlchemy import in this code block.
from azure.identity.aio import DefaultAzureCredential
from azure_postgresql_auth.sqlalchemy import create_asyncpg_engine
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

]
sqlalchemy = [
"sqlalchemy>=2.0.0",
"sqlalchemy[asyncio]>=2.0.0",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community Contribution Community members are working on the issue customer-reported Issues that are reported by GitHub users external to the Azure organization. PostgreSQL Auth

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add non-blocking asyncpg support to SQLAlchemy Entra authentication

2 participants