Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 65 additions & 0 deletions .github/scripts/next_rc_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# 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.

"""Print the next release-candidate number for a release version.

Usage: python next_rc_number.py 3.1.0

The counter is read back from TestPyPI rather than kept as CI-side state, so it
starts at 1 for a release version that has no candidates yet and increments on
every re-cut. This uses the standard simple-index JSON API.

Caveat: the index lists only the versions currently published, while the upload
API keeps every filename ever used permanently reserved. Deleting a candidate
release therefore frees its number here but not on the index, and the next
upload would be rejected. Leave published candidates in place.
"""

import json
import re
import sys
import urllib.error
import urllib.request

INDEX_URL = "https://test.pypi.org/simple/aws-advanced-python-wrapper/"
ACCEPT_HEADER = "application/vnd.pypi.simple.v1+json"
REQUEST_TIMEOUT_SEC = 30


def next_rc_number(release_version: str, uploaded_versions: list[str]) -> int:
candidate_pattern = re.compile(re.escape(release_version) + r"rc(\d+)$")
matches = [candidate_pattern.match(version) for version in uploaded_versions]
used_numbers = [int(match.group(1)) for match in matches if match is not None]
return max(used_numbers) + 1 if used_numbers else 1


def fetch_uploaded_versions() -> list[str]:
request = urllib.request.Request(INDEX_URL, headers={"Accept": ACCEPT_HEADER})
try:
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SEC) as response:
return json.load(response).get("versions", [])
except urllib.error.HTTPError as error:
if error.code != 404:
raise
return [] # The project has never been uploaded to TestPyPI.


def main() -> None:
if len(sys.argv) != 2:
raise SystemExit(f"usage: {sys.argv[0]} <release-version>")
print(next_rc_number(sys.argv[1], fetch_uploaded_versions()))


if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions .github/workflows/draft_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,41 @@ jobs:
echo "$RELEASE_DETAILS" > RELEASE_DETAILS.md
- name: 'Create a Package'
run: poetry build
# The tag, the GitHub draft release, and the eventual PyPI release all keep
# the plain version. Only the TestPyPI upload carries an rc suffix, so a tag
# can be re-cut without colliding with an immutable TestPyPI filename.
# release.yml builds from the tag independently, so PyPI is unaffected.
- name: 'Build A Release Candidate For TestPyPI'
run: |
set -euo pipefail
# rc1 for the first candidate of a release version, then rc2, rc3, ...
# The counter is read back from TestPyPI so no state is carried between
# runs and a re-cut can never collide with an immutable filename.
RC_VERSION="${RELEASE_VERSION}rc$(python .github/scripts/next_rc_number.py "$RELEASE_VERSION")"
mv dist dist-release
poetry version "$RC_VERSION"
poetry build
mv dist dist-rc
mv dist-release dist
poetry version "$RELEASE_VERSION"
echo "Release candidate: pin testers to \`aws_advanced_python_wrapper==$RC_VERSION\`" >> $GITHUB_STEP_SUMMARY
- name: 'Upload to TestPyPI'
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
repository-url: https://test.pypi.org/legacy/
packages-dir: dist-rc
- name: 'Create a Draft Release'
if: always()
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
with:
draft: true
# Allow a tag to be re-cut: update the existing draft and replace its
# artifacts instead of failing because the release already exists.
allowUpdates: true
replacesArtifacts: true
# Never overwrite an already-published release: only a draft or
# prerelease may be updated in place.
updateOnlyUnreleased: true
name: "AWS Advanced Python Wrapper - v${{ env.RELEASE_VERSION }}"
bodyFile: RELEASE_DETAILS.md
artifacts: ./dist/*
Expand Down
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,36 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/#semantic-versioning-200).

## [Unreleased]
## [3.1.0] - 2026-08-24
### :magic_wand: Added
* Python 3.14 support. ([PR #1252](https://github.com/aws/aws-advanced-python-wrapper/pull/1252))
* Aurora Global Database support. Adds the `gdb_failover` plugin, which is aware of a home region and selects a failover target using the `failover_home_region`, `active_home_failover_mode` and `inactive_home_failover_mode` parameters, and the `gdb_rw` plugin, which restricts read/write splitting to the home region and can defer writes to Global Write Forwarding. Global writer endpoints are recognized, and cross-region topology is discovered through the `global-aurora-pg` / `global-aurora-mysql` dialects using `global_cluster_instance_host_patterns`. See [Aurora Global Databases](./docs/using-the-python-wrapper/GlobalDatabases.md). ([PR #1243](https://github.com/aws/aws-advanced-python-wrapper/pull/1243), [PR #1246](https://github.com/aws/aws-advanced-python-wrapper/pull/1246))
* `gdb_accessible_regions`, restricting host selection to the AWS regions an application can reach, and `monitoring_connection_priority` / `gdb_monitoring_connection_priority`, controlling which host role or region the topology monitor uses for its background connection. Both are currently supported in the synchronous driver only. ([PR #1266](https://github.com/aws/aws-advanced-python-wrapper/pull/1266))
* 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))
* Schema-qualified every function, operator, cast and catalog reference in the PostgreSQL queries the driver issues internally, so a session `search_path` cannot redirect them. This covers the queries issued by the asynchronous Aurora host list provider as well. ([PR #1262](https://github.com/aws/aws-advanced-python-wrapper/pull/1262), [PR #1270](https://github.com/aws/aws-advanced-python-wrapper/pull/1270))
* Connection properties whose name indicates a credential are now masked when connection properties are logged. Previously only `password` was masked, so properties such as `idp_password` and the `monitoring-` and `blue-green-monitoring-` prefixed passwords were logged in clear text. The Secrets Manager properties remain readable, since they identify a secret rather than hold one. ([PR #1270](https://github.com/aws/aws-advanced-python-wrapper/pull/1270))
* `opentelemetry-api` is now declared as a runtime dependency rather than a development one. ([PR #1261](https://github.com/aws/aws-advanced-python-wrapper/pull/1261))
* `boto3-stubs` and `types_aws_xray_sdk` are no longer declared as runtime dependencies. Both are type-stub distributions with no runtime effect, and declaring them meant their version ranges constrained applications that pin those packages themselves. ([PR #1274](https://github.com/aws/aws-advanced-python-wrapper/pull/1274))
* `create_engine("postgresql+aws_wrapper_psycopg://")` now resolves. The PostgreSQL SQLAlchemy dialect entry point registered by 3.0.0 pointed at a module that was not present in the distribution, so constructing the engine raised `ModuleNotFoundError` before any connection was attempted. ([Issue #1260](https://github.com/aws/aws-advanced-python-wrapper/issues/1260), [Issue #1273](https://github.com/aws/aws-advanced-python-wrapper/issues/1273), [PR #1252](https://github.com/aws/aws-advanced-python-wrapper/pull/1252))
* Query timeouts are now threaded through the failover paths, and pooled connections are invalidated in the Aurora connection tracker. ([PR #1255](https://github.com/aws/aws-advanced-python-wrapper/pull/1255))
* `pool_pre_ping` is now supported in the SQLAlchemy ORM MySQL dialect. ([PR #1245](https://github.com/aws/aws-advanced-python-wrapper/pull/1245))
* Issues found while aligning the synchronous and asynchronous implementations ([PR #1256](https://github.com/aws/aws-advanced-python-wrapper/pull/1256)):
* The Aurora Initial Connection Strategy Plugin computed its retry deadline from `open_connection_retry_interval_ms` rather than `open_connection_retry_timeout_ms`, so it gave up retrying much earlier than configured.
* The Limitless Plugin discarded the result of its login-exception check, so an authentication failure while fetching transaction routers was never recognized as one.
* The Limitless Plugin did not read the connection from its routing context before using it.
* Resetting the session state transfer handler assigned to a name that did not exist, leaving a previously registered handler installed.
* A message key was raised in place of its resolved text when the current host could not be determined.
* Added two missing Blue/Green deployment log messages.
* Documentation corrections. ([PR #1244](https://github.com/aws/aws-advanced-python-wrapper/pull/1244))
* Corrected documented examples that could not run as written: the plugin codes `failover2` and `efm2` (the registered codes are `failover_v2` and `host_monitoring_v2`), `dialect` in place of the `wrapper_dialect` parameter, and a MySQL Global Database chain containing `host_monitoring_v2`, which cannot be loaded on `mysql-connector-python`. Also documents the event loop requirement for asyncio on Windows, and notes that `gdb_accessible_regions` is currently supported in the synchronous driver only. ([PR #1270](https://github.com/aws/aws-advanced-python-wrapper/pull/1270))

### :cloud: Changed
* The SQLAlchemy dialects moved to `aws_advanced_python_wrapper.sqlalchemy_dialects` and register as drivers under SQLAlchemy's existing dialects: `postgresql+aws_wrapper_psycopg://`, `mysql+aws_wrapper_mysqlconnector://` and `mysql+aws_wrapper_aiomysql://`. The URL driver names are unchanged, so URL-based and `creator=` configurations continue to work without modification. `aws_advanced_python_wrapper.sqlalchemy.mysql_orm_dialect.SqlAlchemyOrmMysqlDialect` remains importable as a deprecated alias for `sqlalchemy_dialects.mysql.AwsWrapperMySQLConnectorDialect` and will be removed in the next major version. Note that the replacement class does not inject a default `aurora_connection_tracker,failover_v2` plugin chain when `wrapper_plugins` is absent; the wrapper's own default chain applies instead, which for `mysql-connector-python` is `initial_connection,aurora_connection_tracker,failover_v2`. See [SQLAlchemy Support](./docs/using-the-python-wrapper/SqlAlchemySupport.md). ([PR #1274](https://github.com/aws/aws-advanced-python-wrapper/pull/1274))
* Reworked the Aurora initial connection strategy plugin. ([PR #1253](https://github.com/aws/aws-advanced-python-wrapper/pull/1253), [PR #1264](https://github.com/aws/aws-advanced-python-wrapper/pull/1264))
* Updated the Community and Aurora database versions the test suite runs against. ([PR #1248](https://github.com/aws/aws-advanced-python-wrapper/pull/1248))

## [3.0.0] - 2026-06-02

Expand Down Expand Up @@ -135,6 +158,7 @@ The Amazon Web Services (AWS) Advanced Python Wrapper allows an application to t
* Support for PostgreSQL
* Support for MySQL

[3.1.0]: https://github.com/aws/aws-advanced-python-wrapper/compare/3.0.0...3.1.0
[3.0.0]: https://github.com/aws/aws-advanced-python-wrapper/compare/2.1.0...3.0.0
[2.1.0]: https://github.com/aws/aws-advanced-python-wrapper/compare/2.0.0...2.1.0
[2.0.0]: https://github.com/aws/aws-advanced-python-wrapper/compare/1.4.0...2.0.0
Expand Down
3 changes: 2 additions & 1 deletion Maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
| January 14, 2026 | [Release 2.0.0](https://github.com/aws/aws-advanced-python-wrapper/releases/tag/2.0.0) |
| February 11, 2026 | [Release 2.1.0](https://github.com/aws/aws-advanced-python-wrapper/releases/tag/2.1.0) |
| June 2, 2026 | [Release 3.0.0](https://github.com/aws/aws-advanced-python-wrapper/releases/tag/3.0.0) |
| August 24, 2026 | [Release 3.1.0](https://github.com/aws/aws-advanced-python-wrapper/releases/tag/3.1.0) |

`aws-advanced-python-wrapper` [follows semver](https://semver.org/#semantic-versioning-200) which means we will only
release breaking changes in major versions. Generally speaking patches will be released to fix existing problems without
Expand Down Expand Up @@ -65,4 +66,4 @@ from the updated source after the PRs are merged.
|---------------|----------------------|-------------|------------------|--------------------------|------------------------|
| 1 | 1.4.0 | Maintenance | May 16, 2024 | January 14, 2026 | January 14, 2027 |
| 2 | 2.1.0 | Maintenance | January 14, 2026 | June 2, 2026 | June 2, 2027 |
| 3 | 3.0.0 | Current | June 2, 2026 | N/A | N/A |
| 3 | 3.1.0 | Current | June 2, 2026 | N/A | N/A |
2 changes: 1 addition & 1 deletion aws_advanced_python_wrapper/driver_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@

class DriverInfo:
DRIVER_NAME = "aws_advanced_python_wrapper"
DRIVER_VERSION = "3.0.0"
DRIVER_VERSION = "3.1.0"
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "2.1.0"
__version__ = "3.1.0"
Loading