Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
72457dc
Simple PG workflow working
aaron-congo Feb 6, 2026
6cd61c7
Cleanup
aaron-congo Feb 6, 2026
bc607f6
Fix failover2 wrong writer host
aaron-congo Feb 9, 2026
5daa970
Add mysql-connector SQLAlchemy ORM
jonathanl-bq Mar 25, 2026
5ee014a
Revert connection string in sqlalchemy orm unit test
jonathanl-bq Mar 25, 2026
4871ac7
Add __init__.py for sqlalchemy integration tests
jonathanl-bq Mar 26, 2026
068e2e9
Fix RdsUtils not being found
jonathanl-bq Mar 26, 2026
f81b1db
Translate basic django test to sqlalchemy
jonathanl-bq Apr 2, 2026
a1ebf4c
Add basic CRUD test for sqlalchemy ORM mysql tests
jonathanl-bq Apr 4, 2026
f081a63
Add remaining basic MySQL SQLAlchemy ORM tests
jonathanl-bq Apr 7, 2026
1b8d401
Remove temporary changes to get tests to run locally
jonathanl-bq Apr 7, 2026
4429afd
Add license headers and remove unused import
jonathanl-bq Apr 8, 2026
4f6500c
Try fixing mypy errors in tests
jonathanl-bq Apr 9, 2026
9bd824f
Try to fix mypy Base class error
jonathanl-bq Apr 13, 2026
ee53f8c
fix: test failures due to using legacy sqlalchemy api (#1226)
karenc-bq Apr 13, 2026
9f484bb
Add WIP sqlalchemy plugins tests
jonathanl-bq Apr 13, 2026
c7ab03f
Simple PG workflow working
aaron-congo Feb 6, 2026
6e16513
Cleanup
aaron-congo Feb 6, 2026
af9e19f
Fix failover2 wrong writer host
aaron-congo Feb 9, 2026
800b3a7
Add mysql-connector SQLAlchemy ORM
jonathanl-bq Mar 25, 2026
0033f90
Revert connection string in sqlalchemy orm unit test
jonathanl-bq Mar 25, 2026
65a7b38
Add __init__.py for sqlalchemy integration tests
jonathanl-bq Mar 26, 2026
6e055b3
Fix RdsUtils not being found
jonathanl-bq Mar 26, 2026
49bbeea
Translate basic django test to sqlalchemy
jonathanl-bq Apr 2, 2026
92659f3
Add basic CRUD test for sqlalchemy ORM mysql tests
jonathanl-bq Apr 4, 2026
80fb9d0
Add remaining basic MySQL SQLAlchemy ORM tests
jonathanl-bq Apr 7, 2026
9047907
Remove temporary changes to get tests to run locally
jonathanl-bq Apr 7, 2026
0e08938
Add license headers and remove unused import
jonathanl-bq Apr 8, 2026
d4eb5bd
Try fixing mypy errors in tests
jonathanl-bq Apr 9, 2026
b344ef5
Try to fix mypy Base class error
jonathanl-bq Apr 13, 2026
919284a
fix: test failures due to using legacy sqlalchemy api (#1226)
karenc-bq Apr 13, 2026
0ab9c66
Fix multiple class definition errors
jonathanl-bq Apr 14, 2026
a64d748
Override initialize for mysql_orm_dialect.py
jonathanl-bq Apr 21, 2026
fa1f875
Fix most of the sqlalchemy ORM plugin tests
jonathanl-bq May 4, 2026
c981d2d
test: add clean up between tests (#1232)
karenc-bq May 7, 2026
5c21749
Fix issue with plugins being shadowed by sqlalchemy create_engine
jonathanl-bq May 8, 2026
d5b2b82
Remove wrapper_plugins from opts after processing it
jonathanl-bq May 11, 2026
79f990f
Merge branch 'sqlalchemy-orm-mysql' into sqlalchemy-orm-plugins
jonathanl-bq May 12, 2026
f955a9c
Try to fix mypy issues
jonathanl-bq May 13, 2026
c6958bc
Try fixing one mypy error
jonathanl-bq May 13, 2026
1480c48
Fix syntax error
jonathanl-bq May 14, 2026
0f9859d
Fix retrieved variable types
jonathanl-bq May 14, 2026
c75e9f7
Fix mypy error about row
jonathanl-bq May 14, 2026
771b4b0
Try to fix mypy errors in mysql_orm_dialect.py
jonathanl-bq May 14, 2026
7941fe0
Try to fix LSP violation errors
jonathanl-bq May 14, 2026
159b6c3
Try to fix mypy error for missing errno field
jonathanl-bq May 14, 2026
7e2bb1c
Fix last mypy error
jonathanl-bq May 14, 2026
edd4d02
Use err variable's errno property
jonathanl-bq May 14, 2026
9f80c4e
Check errno property on correct type
jonathanl-bq May 14, 2026
f0d7d91
Address flake8 errors
jonathanl-bq May 14, 2026
cfebe5b
Add annotations import
jonathanl-bq May 14, 2026
b45cd27
Run isort
jonathanl-bq May 14, 2026
88dfb8e
Run isort on tests
jonathanl-bq May 14, 2026
c999602
Move int cast for connect_timeout to fix unit tests
jonathanl-bq May 15, 2026
9d2f642
Remove breakpoint call
jonathanl-bq May 15, 2026
560e67c
Set supports_statement_cache flag
jonathanl-bq May 19, 2026
15abdd8
Add SqlAlchemySupport.md
jonathanl-bq May 20, 2026
bc38b78
Merge branch 'sqlalchemy-orm-plugins' into sql-alchemy-orm-docs
jonathanl-bq May 22, 2026
b47aa72
Add SQLAlchemyFailover example
jonathanl-bq May 22, 2026
a3f9c3e
Fix mypy issue with DeclarativeBase
jonathanl-bq May 22, 2026
f186bb3
Check specific DBAPIErrors when failover happens
jonathanl-bq May 22, 2026
6becc21
Merge branch 'sqlalchemy-orm-plugins' into sql-alchemy-orm-docs
jonathanl-bq May 22, 2026
865cd48
Fix example code to catch DBAPIErrors
jonathanl-bq May 22, 2026
0e96a54
Update SqlAlchemySupport.md to add failover example
jonathanl-bq May 22, 2026
3e6e164
Fix isort error
jonathanl-bq May 22, 2026
8fc88f4
Merge branch 'sqlalchemy-orm-plugins' into sql-alchemy-orm-docs
jonathanl-bq May 22, 2026
830eb63
Run isort and change else ifs to elifs
jonathanl-bq May 22, 2026
8e33fd1
Fix flake8 errors and update connection URL
jonathanl-bq May 22, 2026
7efffd2
Merge branch 'main' into sql-alchemy-orm-docs
jonathanl-bq May 22, 2026
e6c24e6
Update example to fix issue with filter not showing results
jonathanl-bq May 25, 2026
694a38d
Explicitly state that we do not support read/write plugin with SQLAlc…
jonathanl-bq May 25, 2026
3bf7e31
Remove pg_orm_dialect so we can add it separately when we properly te…
jonathanl-bq May 25, 2026
192aa13
Remove prepare_connect_info
jonathanl-bq May 26, 2026
5398a2b
Default to failover_v2 and remove prepare_connect_info
jonathanl-bq May 26, 2026
e97777c
Remove PG ORM dialect for now until we can test it later
jonathanl-bq May 26, 2026
dd2f122
Fix flake8 issues
jonathanl-bq May 26, 2026
a23d953
Merge branch 'sqlalchemy-orm-plugins' into sql-alchemy-orm-docs
jonathanl-bq May 26, 2026
2e50221
Merge branch 'main' into sql-alchemy-orm-docs
jonathanl-bq May 26, 2026
f36f719
Update example to use failover_v2 plugin
jonathanl-bq May 26, 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
222 changes: 222 additions & 0 deletions docs/examples/MySQLSQLAlchemyFailover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# 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.

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm import DeclarativeBase, sessionmaker

from aws_advanced_python_wrapper import release_resources
from aws_advanced_python_wrapper.errors import (
FailoverFailedError, FailoverSuccessError,
TransactionResolutionUnknownError)

"""
SQLAlchemy ORM Failover Example with AWS Advanced Python Wrapper

This example demonstrates how to handle failover events when using SQLAlchemy ORM
with the AWS Advanced Python Wrapper.

"""


class Base(DeclarativeBase):
pass


class BankAccount(Base):
"""Example model for demonstrating failover handling."""
__tablename__ = 'bank_test'

id = Column(Integer, primary_key=True)
name = Column(String(50))
account_balance = Column(Integer)

def __str__(self) -> str:
return f"{self.name}: ${self.account_balance}"


def execute_query_with_failover_handling(query_func):
"""
Execute a SQLAlchemy ORM query with failover error handling.

Args:
query_func: A callable that executes the desired query

Returns:
The result of the query function
"""
try:
return query_func()

except DBAPIError as dbapi_err:
e = dbapi_err.orig
if isinstance(e, FailoverSuccessError):
# Query execution failed and AWS Advanced Python Wrapper successfully failed over to an available instance.
# https://github.com/aws/aws-advanced-python-wrapper/blob/main/docs/using-the-python-driver/using-plugins/UsingTheFailoverPlugin.md#failoversuccesserror

# The connection has been re-established. Retry the query.
print("Failover successful! Retrying query...")

# Retry the query
return query_func()

elif isinstance(e, FailoverFailedError):
# Failover failed. The application should open a new connection,
# check the results of the failed transaction and re-run it if needed.
# https://github.com/aws/aws-advanced-python-wrapper/blob/main/docs/using-the-python-driver/using-plugins/UsingTheFailoverPlugin.md#failoverfailederror
print(f"Failover failed: {e}")
print("Application should open a new connection and retry the transaction.")
raise e

elif isinstance(e, TransactionResolutionUnknownError):
# The transaction state is unknown. The application should check the status
# of the failed transaction and restart it if needed.
# https://github.com/aws/aws-advanced-python-wrapper/blob/main/docs/using-the-python-driver/using-plugins/UsingTheFailoverPlugin.md#transactionresolutionunknownerror
print(f"Transaction resolution unknown: {e}")
print("Application should check transaction status and retry if needed.")
raise e


def create_table(engine):
"""Create the database table with failover handling."""
def _create():
Base.metadata.create_all(engine)
print("Table created successfully")

execute_query_with_failover_handling(_create)


def drop_table(engine):
"""Drop the database table with failover handling."""
def _drop():
Base.metadata.drop_all(engine)
print("Table dropped successfully")

execute_query_with_failover_handling(_drop)


def insert_records(session):
"""Insert records with failover handling."""
print("\n--- Inserting Records ---")

def _insert1():
account = BankAccount(name='Jane Doe', account_balance=200)
session.add(account)
session.commit() # Explicit commit required
print(f"Inserted: {account}")
return account

def _insert2():
account = BankAccount(name='John Smith', account_balance=200)
session.add(account)
session.commit()
print(f"Inserted: {account}")
return account

execute_query_with_failover_handling(_insert1)
execute_query_with_failover_handling(_insert2)


def query_records(session):
"""Query records with failover handling."""
print("\n--- Querying Records ---")

def _query():
accounts = session.query(BankAccount).all()
for account in accounts:
print(f" {account}")
return accounts

return execute_query_with_failover_handling(_query)


def update_record(session):
"""Update a record with failover handling."""
print("\n--- Updating Record ---")

def _update():
account = session.query(BankAccount).filter(BankAccount.name == "Jane Doe").first()
if account:
account.account_balance = 300
session.commit()
print(f"Updated: {account}")
return account

return execute_query_with_failover_handling(_update)


def filter_records(session):
"""Filter records with failover handling."""
print("\n--- Filtering Records ---")

def _filter():
accounts = session.query(BankAccount).filter(BankAccount.account_balance >= 250).all()
print(f"Found {len(accounts)} accounts with balance >= $250:")
for account in accounts:
print(f" {account}")
return accounts

return execute_query_with_failover_handling(_filter)


if __name__ == "__main__":
try:
print("SQLAlchemy ORM Failover Example with AWS Advanced Python Wrapper")
print("=" * 60)

engine = create_engine(
'mysql+aws_wrapper_mysqlconnector://admin:pwd@'
'database.cluster-xyz.us-east-1.rds.amazonaws.com:3306/mysql?'
'wrapper_plugins=failover_v2'
)

# Create table
create_table(engine)

Session = sessionmaker(bind=engine)
with Session() as session:
# Insert records
insert_records(session)

# Query records
query_records(session)

# Update a record
update_record(session)

# Query again to see the update
query_records(session)

# Filter records
filter_records(session)

session.close()

# Cleanup
print("\n--- Cleanup ---")
drop_table(engine)

print("\n" + "=" * 60)
print("Example completed successfully!")

engine.dispose()

except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

finally:
# Clean up AWS Advanced Python Wrapper resources
release_resources()
66 changes: 66 additions & 0 deletions docs/using-the-python-wrapper/SqlAlchemySupport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SQLAlchemy ORM Support

> [!IMPORTANT]
> SQLAlchemy ORM support is currently only available for **MySQL databases**.

The AWS Advanced Python Wrapper provides a custom SQLAlchemy database backend that enables SQLAlchemy applications to leverage AWS and Aurora functionalities such as failover handling and IAM authentication.

## Prerequisites

- SQLAlchemy 2.0.0+

## Basic Configuration

To use the AWS Advanced Python Wrapper with SQLAlchemy, call the `create_engine` function with your database URL with the configuration settings appended:

```python
from sqlalchemy import create_engine

create_engine("mysql+aws_wrapper_mysqlconnector://your_username:your_password@your-cluster-endpoint.cluster-xyz.us-east-1.rds.amazonaws.com:your_port/your_database_name?connect_timeout=10&wrapper_plugins=aurora_connection_tracker%2Cfailover_v2")
```

See [the SQLALchemy official documentation](https://docs.sqlalchemy.org/en/20/core/engines.html) for more information on engine configuration.

### Supported Engines

| Driver | Database Dialect |
|-------------------|------------------|
| `aws_wrapper_mysqlconnector` | `mysql` |
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.

Curious on this "database dialect" - is that from our wrapper or sql alchemy? if it's our wrapper should we not support aurora-mysql and the other mysql database dialects too?


## Using Plugins with SQLAlchemy

The AWS Advanced Python Wrapper supports a variety of plugins that enhance your SQLAlchemy application with features like failover handling, IAM authentication, and more. Most plugins can be enabled simply by adding them to the `wrapper_plugins` parameter in your database URL.
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.

Suggested change
The AWS Advanced Python Wrapper supports a variety of plugins that enhance your SQLAlchemy application with features like failover handling, IAM authentication, and more. Most plugins can be enabled simply by adding them to the `wrapper_plugins` parameter in your database URL.
The AWS Advanced Python Wrapper supports a variety of plugins that enhance your SQLAlchemy application with features like failover handling, IAM authentication, and more. Most plugins can be enabled simply by adding them to the `wrapper_plugins` parameter in your database URL.
> [!NOTE]
> SQLAlchemy reserves the `plugins` connection parameter for its own engine, pool, and dialect event listeners.
> When using SQLAlchemy, enable AWS Advanced Python Wrapper plugins (such as the Aurora Initial Connection Strategy plugin) via `wrapper_plugins`. Otherwise, use `plugins` as usual.


For a complete list of available plugins, see the [List of Available Plugins](./UsingThePythonWrapper.md#list-of-available-plugins) in the main driver documentation.


### Failover Plugin

The Failover Plugin provides automatic failover handling for Aurora clusters. When a database instance becomes unavailable, the plugin automatically connects to a healthy instance in the cluster.
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.

Can we add a link to the failover plugin docs here


#### Handling Failover Events

During a failover event, the driver will throw a `DBAPIError` exception after successfully connecting to a new instance. Your application should catch this exception, check which type of failover error occurred, and retry the failed query:

```python
from aws_advanced_python_wrapper.errors import FailoverSuccessError

def execute_query_with_failover_handling(query_func):
try:
return query_func()
except DBAPIError as dbapi_err:
err = dbapi_err.orig
if isinstance(err, FailoverSuccessError):
# Failover successful, retry the query
return query_func()
```

For a complete example, see [MySQLSQLAlchemyFailover.py](../examples/MySQLSQLAlchemyFailover.py).

For more information about the Failover Plugin, see the [Failover Plugin documentation](./using-plugins/UsingTheFailoverPlugin.md).

### Read/Write Splitting

The Read/Write Splitting Plugin is not supported for SQLAlchemy, since session binds already implement this functionality.

See the [official SQLAlchemy documentation on the Session API](https://docs.sqlalchemy.org/en/20/orm/session_api.html) for more information on session binds.
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.

I would love to see this maybe structured a different way or with some additional plugins that ARE supported. You say that there are many other plugins and then only list one so I would suggest either

  1. add to line 34 that failover is the most relevant and then keep the section as is
  2. list iam as another plugin that is helpful after failover

I would also like rw splitting to be under a header of something like "not supported" It's confusing that it is positioned the same way as failover which is supported. Adding a not supported section would also give you space to mention that lack of pg support again

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.

We could consider reworking this into a plugin compatibility section, and create a table indicating which plugins are supported and which are not (rw splitting and srw)

4 changes: 2 additions & 2 deletions docs/using-the-python-wrapper/UsingThePythonWrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Plugins are loaded and managed through the Connection Plugin Manager and may be

| Parameter | Value | Required | Description | Default Value |
|----------------------------------|-------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| `plugins` | `str` | No | Comma separated list of connection plugin codes. <br><br>Example: `failover,efm` | `auroraConnectionTracker,failover,efm` |
| `plugins` (`wrapper_plugins` if using SQLAlchemy) | `str` | No | Comma separated list of connection plugin codes. <br><br>Example: `failover,efm` | `auroraConnectionTracker,failover,efm` |
| `auto_sort_wrapper_plugin_order` | `str` | No | Certain plugins require a specific plugin chain ordering to function correctly. When enabled, this property automatically sorts the requested plugins into the correct order. | `True` |

### List of Available Plugins
Expand Down Expand Up @@ -143,4 +143,4 @@ args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
```
```