Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3e7342f
build: add aiomysql/aiohttp/greenlet deps and the aiomysql SQLAlchemy…
AhmadMasry Jul 3, 2026
f4209b0
feat: async (asyncio) wrapper layer under aws_advanced_python_wrapper…
AhmadMasry Jul 3, 2026
27749c4
test: async unit/integration coverage and test-harness plumbing
AhmadMasry Jul 3, 2026
2ab4d40
docs: async usage documentation and changelog entry
AhmadMasry Jul 3, 2026
1827c0f
chore: add Python 3.14 to integration CI matrices; fix stale plugin c…
AhmadMasry Jul 3, 2026
100764e
fix: align async topology providers with sync data-plane contracts
AhmadMasry Jul 3, 2026
20574ef
refactor: remove reader-role recheck from async RWS (sync parity)
AhmadMasry Jul 3, 2026
b150e78
fix: port host-list notification and old-connection lifecycle to asyn…
AhmadMasry Jul 3, 2026
f89e1c2
fix: STRICT_READER role verification and writer allowlist in async fa…
AhmadMasry Jul 3, 2026
7e94e0a
fix: align async plugin weights and pipeline subscription with sync
AhmadMasry Jul 3, 2026
7bfab55
fix: RWS sync-parity guards; drop SA-pool heuristic and reader re-probe
AhmadMasry Jul 3, 2026
0294f4e
feat: port sync Limitless connect state machine to async
AhmadMasry Jul 3, 2026
687c35f
feat: port sync Blue/Green provider and monitor machinery to async
AhmadMasry Jul 3, 2026
660b879
fix: sync-parity auth flows for async IAM, Secrets Manager, federated…
AhmadMasry Jul 3, 2026
461ee9f
fix: fastest-response, host-list, and plugin-service sync-parity gaps
AhmadMasry Jul 3, 2026
414c0ba
feat: port sync EFM v2 semantics to the async host monitoring plugin
AhmadMasry Jul 3, 2026
51d3d72
feat: telemetry spans in async plugin dispatch (sync parity)
AhmadMasry Jul 3, 2026
7f3d4d4
refactor: route async failover and RWS error messages through message…
AhmadMasry Jul 3, 2026
836e960
fix: psycopg dbname mapping, isolation-level transfer, bounded async …
AhmadMasry Jul 3, 2026
acdde8d
refactor: complete the async message-key i18n sweep
AhmadMasry Jul 3, 2026
e0f0e31
feat: enforce socket_timeout on async operations at the terminal plugin
AhmadMasry Jul 3, 2026
38e49e5
fix: wrapper/core sync-parity gaps from the final review
AhmadMasry Jul 3, 2026
711747b
fix: plugin-level sync-parity gaps from the final review
AhmadMasry Jul 3, 2026
d87e345
test: fix mypy narrowing in blue/green monitor tests
AhmadMasry Jul 3, 2026
77369a5
feat: connect-time failover in the async failover plugin
AhmadMasry Jul 3, 2026
159ac52
fix: align async limitless login handling and Blue/Green log keys wit…
AhmadMasry Jul 4, 2026
55163c9
fix(aio): converge writer failover on the new writer via a surviving …
AhmadMasry Jul 4, 2026
33d6ccf
fix(aio): gate the socket-timeout bound on network-bound methods (syn…
AhmadMasry Jul 4, 2026
c4e522c
fix(aio): cache auth credentials in the shared StorageService (sync p…
AhmadMasry Jul 4, 2026
3827fb0
chore: regenerate poetry.lock with Poetry 1.8.2; keep container poetr…
AhmadMasry Jul 4, 2026
f332907
fix(aio): drop the candidate when the reader-failover role probe fail…
AhmadMasry Jul 4, 2026
405c213
docs(aio): fix stale enable_connect_failover comment (connect-time fa…
AhmadMasry Jul 4, 2026
20df8f2
feat(aio): monitor-owned writer discovery for failover (sync v2 parity)
AhmadMasry Jul 4, 2026
746217d
fix(aio): async hardening -- cross-loop cancel, 3.10 semaphore leak, …
AhmadMasry Jul 4, 2026
13e195d
fix(aio): validate Okta authn HTTP status before JSON-parsing; docume…
AhmadMasry Jul 4, 2026
ffe0071
fix: use inspect.iscoroutinefunction (asyncio variant deprecated on 3…
AhmadMasry Jul 4, 2026
6e32d7a
fix(aio): topology-monitor probes must force_connect (bypass pooled p…
AhmadMasry Jul 4, 2026
e404d09
chore(aio): log the swallowed exception in AsyncDialectUtils.get_inst…
AhmadMasry Jul 4, 2026
83a3bb8
fix(aio): await writer-change invalidation inline (was fire-and-forge…
AhmadMasry Jul 4, 2026
656a3b2
fix(aio): set host_id on Aurora topology rows (identify_connection al…
AhmadMasry Jul 4, 2026
70d3acb
fix(aio): force-refresh topology in the tracker's failover handler (d…
AhmadMasry Jul 4, 2026
efc978c
fix(aio): topology-independent departed-host invalidation on failover
AhmadMasry Jul 4, 2026
518a052
fix(aio): publish role-corrected topology while the verified writer s…
AhmadMasry Jul 4, 2026
ad5ee28
test(aio): wait for the demoted writer to finish rebooting before rec…
AhmadMasry Jul 4, 2026
6670522
fix(aio): srw must PRESERVE its cached endpoint connections on switch
AhmadMasry Jul 4, 2026
2c1ae59
fix(aio): pin the tracker's writer from a live role probe, not lagged…
AhmadMasry Jul 5, 2026
64317cb
test(aio): probe the endpoint LIVE before post-failover reconnects
AhmadMasry Jul 5, 2026
921d4e5
fix(aio): release_resources must CLOSE the current connection, not ab…
AhmadMasry Jul 5, 2026
0db806d
chore(aio): log the tracker failover-handler decision inputs
AhmadMasry Jul 5, 2026
f74a7ed
fix(aio): departed-host guard must not veto on a first-observation pin
AhmadMasry Jul 5, 2026
044288a
fix(mysql): classify pymysql InterfaceError(0, 'Not connected') as ne…
AhmadMasry Jul 5, 2026
93f5ad8
fix(aio): dialect-independent failover trigger for aiomysql 'Not conn…
AhmadMasry Jul 5, 2026
8c1c4c2
fix(aio+mysql): match aiomysql's single-string 'Not connected' shape too
AhmadMasry Jul 5, 2026
f568f8f
refactor(aio): upgrade the MySQL database dialect inside the terminal…
AhmadMasry Jul 6, 2026
2b700a7
docs: reference PR #1257 in the changelog async entry
AhmadMasry Jul 6, 2026
b27ab83
chore: relock after rebasing onto main's soupsieve bump
AhmadMasry Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ "3.11", "3.12", "3.13" ]
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
environment: [ "mysql", "pg" ]

steps:
Expand Down Expand Up @@ -71,7 +71,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ "3.11", "3.12", "3.13" ]
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
environment: [ "mysql", "pg" ]

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration_tests_codebuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ "3.11", "3.12", "3.13" ]
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
environment: [ "mysql", "pg" ]

runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
### :magic_wand: Added
* Python 3.14 support. ([PR #1252](https://github.com/aws/aws-advanced-python-wrapper/pull/1252))
* Async (asyncio) counterpart of the wrapper (`aws_advanced_python_wrapper.aio`), targeting sync parity for the shipped plugins: failover v2, read/write splitting, EFM v2, IAM auth, AWS Secrets Manager, federated + Okta auth, Aurora connection tracker, cluster topology monitor, custom endpoint, stale DNS, Aurora initial connection strategy, simple read/write splitting, developer plugin, blue/green deployment, limitless, fastest-response strategy. Backed by psycopg async and aiomysql, with async SQLAlchemy support via `create_async_engine` (`postgresql+aws_wrapper_psycopg://` serves both sync and async; MySQL async uses `mysql+aws_wrapper_aiomysql://`). Includes `AsyncConnectionProvider`/`AsyncPooledConnectionProvider`, `AsyncSessionStateService`, an async IdP factory registry, and `release_resources_async()` for background-task teardown. ([PR #1257](https://github.com/aws/aws-advanced-python-wrapper/pull/1257))

### :bug: Fixed
* Cross-thread use-after-free (SIGSEGV) when an offloaded query times out (e.g. during failover) and the connection is later closed or reused while the query is still running: on timeout the driver dialects now shut down the connection socket and wait for the worker to unwind before propagating, and leak rather than close a connection whose worker cannot be drained. ([PR #1252](https://github.com/aws/aws-advanced-python-wrapper/pull/1252))
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ The following table lists the connection properties used with the AWS Advanced P
Technical documentation regarding the functionality of the AWS Advanced Python Wrapper will be maintained in this GitHub repository. Since the AWS Advanced Python Wrapper requires an underlying Python driver, please refer to the individual driver's documentation for driver-specific information.
To find all the documentation and concrete examples on how to use the AWS Advanced Python Wrapper, please refer to the [AWS Advanced Python Wrapper Documentation](./docs/README.md) page.

For SQLAlchemy integration (both PostgreSQL and MySQL), see [SQLAlchemy Support](./docs/using-the-python-wrapper/SqlAlchemySupport.md).

### Known Limitations

#### Amazon RDS Blue/Green Deployments
Expand Down Expand Up @@ -214,7 +216,7 @@ For all other questions, please use [GitHub discussions](https://github.com/aws/

1. Set up your environment by following the directions in the [Development Guide](./docs/development-guide/DevelopmentGuide.md).
2. To contribute, first make a fork of this project.
3. Make any changes on your fork. Make sure you are aware of the requirements for the project (e.g. do not require Python 3.7 if we are supporting Python 3.10 - 3.13 (inclusive)).
3. Make any changes on your fork. Make sure you are aware of the requirements for the project (e.g. do not require Python 3.7 if we are supporting Python 3.10 - 3.14 (inclusive)).
4. Create a pull request from your fork.
5. Pull requests need to be approved and merged by maintainers into the main branch. <br />

Expand Down
45 changes: 45 additions & 0 deletions aws_advanced_python_wrapper/aio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Async counterparts of the sync wrapper classes.

Structure:
- ``aio.wrapper`` -- ``AsyncAwsWrapperConnection`` (async connection facade)
- ``aio.plugin`` -- ``AsyncPlugin`` / ``AsyncConnectionProvider`` ABCs
- ``aio.driver_dialect`` -- the sole driver-specific interface
(``AsyncDriverDialect`` ABC + per-driver concrete dialects)
- ``aio.pooled_connection_provider`` -- ``AsyncPooledConnectionProvider``
(per-host, per-user async pool with sliding expiration)

Typical usage (once SP-2 lands the implementation)::

from aws_advanced_python_wrapper.aio import AsyncAwsWrapperConnection

conn = await AsyncAwsWrapperConnection.connect(...)

See the F3-B master spec for the overall design.
"""

from aws_advanced_python_wrapper.aio.cleanup import (register_shutdown_hook,
release_resources_async)
from aws_advanced_python_wrapper.aio.pooled_connection_provider import \
AsyncPooledConnectionProvider
from aws_advanced_python_wrapper.aio.wrapper import AsyncAwsWrapperConnection

__all__ = [
"AsyncAwsWrapperConnection",
"AsyncPooledConnectionProvider",
"release_resources_async",
"register_shutdown_hook",
]
110 changes: 110 additions & 0 deletions aws_advanced_python_wrapper/aio/_rws_failover_eviction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Shared failover-eviction logic for the async read/write-splitting plugins.

Async parity with the sync ``AbstractReadWriteSplittingPlugin`` (PR #1117 /
#1229): when a ``FailoverError`` is raised while a command executes, the cached
reader/writer connections must be evicted -- otherwise an internal-pool
connection that failed over mid-query is returned to the pool stale, and a
later checkout hands back a connection bound to a now-demoted instance.

Both async RWS variants (:class:`AsyncReadWriteSplittingPlugin` and
:class:`AsyncSimpleReadWriteSplittingPlugin`) mix this in. It expects the host
class to define ``_plugin_service``, ``_reader_conn`` and ``_writer_conn``;
``_reader_host_info`` / ``_writer_host_info`` are cleared too when present (the
topology variant caches them; the simple variant uses fixed endpoint hosts).
"""

from __future__ import annotations

import asyncio
from typing import Any, Awaitable, Callable

from aws_advanced_python_wrapper.errors import (FailoverError,
FailoverFailedError)
from aws_advanced_python_wrapper.utils.log import Logger

logger = Logger(__name__)


class _AsyncRwsFailoverEvictionMixin:
"""Failover-eviction helpers shared by the async RWS plugins."""

# Declared on the concrete plugin classes; annotated here for type checkers.
_plugin_service: Any
_reader_conn: Any
_writer_conn: Any
# The topology RWS variant caches these; the simple variant uses fixed
# endpoint hosts and never sets them. Declared as Any so each subclass may
# annotate them as Optional[HostInfo] without a variance conflict.
_reader_host_info: Any
_writer_host_info: Any

async def _execute_with_failover_eviction(
self,
method_name: str,
execute_func: Callable[..., Awaitable[Any]]) -> Any:
"""Run ``execute_func`` and evict stale pooled conns on failover.

Sync parity: ``AbstractReadWriteSplittingPlugin.execute``.
``FailoverFailedError`` -> no usable connection, so evict even the
in-use one; any other ``FailoverError`` (success /
transaction-resolution-unknown) means the wrapper reconnected, so evict
only the idle cached connections and keep the in-use one.
"""
try:
return await execute_func()
except FailoverFailedError:
await self._close_connections(close_only_if_idle=False)
raise
except FailoverError:
logger.debug(
"ReadWriteSplittingPlugin.FailoverExceptionWhileExecutingCommand",
method_name)
await self._close_connections(close_only_if_idle=True)
raise

async def _close_connections(self, close_only_if_idle: bool = True) -> None:
await self._close_connection(self._reader_conn, close_only_if_idle)
await self._close_connection(self._writer_conn, close_only_if_idle)

async def _close_connection(
self,
internal_conn: Any,
close_only_if_idle: bool = True) -> None:
if internal_conn is None:
return
current = self._plugin_service.current_connection
driver_dialect = self._plugin_service.driver_dialect
if close_only_if_idle and internal_conn is current:
# In use and still usable (failover reconnected it) -> keep.
return
try:
if not await driver_dialect.is_closed(internal_conn):
result = internal_conn.close()
if asyncio.iscoroutine(result):
await result
except Exception: # noqa: BLE001 - cleanup is best-effort
pass
finally:
# Drop the cache slot so the dead connection is never reused.
if internal_conn is self._writer_conn:
self._writer_conn = None
if getattr(self, "_writer_host_info", None) is not None:
self._writer_host_info = None
if internal_conn is self._reader_conn:
self._reader_conn = None
if getattr(self, "_reader_host_info", None) is not None:
self._reader_host_info = None
68 changes: 68 additions & 0 deletions aws_advanced_python_wrapper/aio/aiomysql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""PEP 249-style async DBAPI module bound to aiomysql.

Async counterpart of :mod:`aws_advanced_python_wrapper.mysql_connector`.
Enables SQLAlchemy's ``create_async_engine`` with the custom dialect
``mysql+aws_wrapper_aiomysql``.

Module-level attributes are populated via :func:`_dbapi.install`;
PEP 562 ``__getattr__`` forwards missing attrs to the real
:mod:`aiomysql` module so SA's MySQL-async dialect introspection keeps
working.
"""

from __future__ import annotations

import sys
from typing import Any

import aiomysql as _aiomysql

from aws_advanced_python_wrapper import _dbapi
from aws_advanced_python_wrapper.aio.wrapper import AsyncAwsWrapperConnection


async def connect(conninfo: str = "", **kwargs: Any) -> AsyncAwsWrapperConnection:
"""PEP 249 async ``connect``, target-driver-bound to aiomysql.

Equivalent to::

await AsyncAwsWrapperConnection.connect(
aiomysql.connect, conninfo, **kwargs)
"""
return await AsyncAwsWrapperConnection.connect(
_aiomysql.connect, conninfo, **kwargs)


def __getattr__(name: str) -> Any:
"""Forward missing attributes to the underlying :mod:`aiomysql` module.

PEP 562 module-level ``__getattr__``. Fires only for names NOT
populated by :func:`_dbapi.install`, so PEP 249 exports take
precedence. SA's MySQL-aiomysql dialect probes the DBAPI module for
aiomysql-specific state (cursor class, error types, etc.);
forwarding lets it see the real driver.
"""
try:
return getattr(_aiomysql, name)
except AttributeError:
raise AttributeError(
f"module 'aws_advanced_python_wrapper.aio.aiomysql' has no "
f"attribute {name!r}"
) from None


_dbapi.install(sys.modules[__name__].__dict__, connect=connect)
Loading