Skip to content
Open
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
2 changes: 1 addition & 1 deletion .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.8", "3.11" ]
python-version: [ "3.8", "3.11", "3.12", "3.13" ]
engine-version: [ "lts", "latest"]
environment: ["mysql", "pg"]

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.8", "3.11" ]
python-version: [ "3.8", "3.11", "3.12", "3.13" ]
environment: [ "mysql", "pg" ]

runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
poetry-version: ["1.8.2"]

steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ For all other questions, please use [GitHub discussions](https://github.com/awsl

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.8 - 3.11 (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.8 - 3.13 (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
2 changes: 1 addition & 1 deletion docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Before using the AWS Advanced Python Driver, you must install:

- Python 3.8 - 3.11 (inclusive).
- Python 3.8 - 3.13 (inclusive).
- The AWS Advanced Python Driver.
- Your choice of underlying Python driver.
- To use the wrapper with Aurora with PostgreSQL compatibility, install [Psycopg](https://github.com/psycopg/psycopg).
Expand Down
2 changes: 1 addition & 1 deletion docs/development-guide/DevelopmentGuide.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Development Guide

### Setup
Make sure you have Python 3.8 - 3.11 (inclusive) installed, along with your choice of underlying Python driver (see [minimum requirements](../GettingStarted.md#minimum-requirements)).
Make sure you have Python 3.8 - 3.13 (inclusive) installed, along with your choice of underlying Python driver (see [minimum requirements](../GettingStarted.md#minimum-requirements)).

Clone the AWS Advanced Python Driver repository:

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]

[tool.poetry.dependencies]
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/container/utils/target_python_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
class TargetPythonVersion(Enum):
PYTHON_3_11 = "PYTHON_3_11"
PYTHON_3_8 = "PYTHON_3_8"
PYTHON_3_12 = "PYTHON_3_12"
PYTHON_3_13 = "PYTHON_3_13"
76 changes: 76 additions & 0 deletions tests/integration/host/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ tasks.register<Test>("test-python-3.11-mysql") {
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-38", "true")
systemProperty("exclude-python-312", "true")
systemProperty("exclude-python-313", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
Expand All @@ -83,6 +85,8 @@ tasks.register<Test>("test-python-3.8-mysql") {
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-311", "true")
systemProperty("exclude-python-312", "true")
systemProperty("exclude-python-313", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
Expand All @@ -97,6 +101,8 @@ tasks.register<Test>("test-python-3.11-pg") {
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-38", "true")
systemProperty("exclude-python-312", "true")
systemProperty("exclude-python-313", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
Expand All @@ -113,6 +119,76 @@ tasks.register<Test>("test-python-3.8-pg") {
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-311", "true")
systemProperty("exclude-python-312", "true")
systemProperty("exclude-python-313", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
systemProperty("exclude-mysql-driver", "true")
systemProperty("exclude-mysql-engine", "true")
systemProperty("exclude-mariadb-driver", "true")
systemProperty("exclude-mariadb-engine", "true")
}
}

tasks.register<Test>("test-python-3.12-mysql") {
group = "verification"
filter.includeTestsMatching("integration.host.TestRunner.runTests")
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-38", "true")
systemProperty("exclude-python-311", "true")
systemProperty("exclude-python-313", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
systemProperty("exclude-pg-driver", "true")
systemProperty("exclude-pg-engine", "true")
}
}

tasks.register<Test>("test-python-3.12-pg") {
group = "verification"
filter.includeTestsMatching("integration.host.TestRunner.runTests")
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-38", "true")
systemProperty("exclude-python-311", "true")
systemProperty("exclude-python-313", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
systemProperty("exclude-mysql-driver", "true")
systemProperty("exclude-mysql-engine", "true")
systemProperty("exclude-mariadb-driver", "true")
systemProperty("exclude-mariadb-engine", "true")
}
}

tasks.register<Test>("test-python-3.13-mysql") {
group = "verification"
filter.includeTestsMatching("integration.host.TestRunner.runTests")
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-38", "true")
systemProperty("exclude-python-311", "true")
systemProperty("exclude-python-312", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
systemProperty("exclude-pg-driver", "true")
systemProperty("exclude-pg-engine", "true")
}
}

tasks.register<Test>("test-python-3.13-pg") {
group = "verification"
filter.includeTestsMatching("integration.host.TestRunner.runTests")
doFirst {
systemProperty("exclude-performance", "true")
systemProperty("exclude-python-38", "true")
systemProperty("exclude-python-311", "true")
systemProperty("exclude-python-312", "true")
systemProperty("exclude-multi-az-cluster", "true")
systemProperty("exclude-multi-az-instance", "true")
systemProperty("exclude-bg", "true")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@

public enum TargetPythonVersion {
PYTHON_3_11,
PYTHON_3_8
PYTHON_3_8,
PYTHON_3_12,
PYTHON_3_13
}
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,10 @@ private static String getContainerBaseImageName(TestEnvironmentRequest request)
return "python:3.8.18";
case PYTHON_3_11:
return "python:3.11.5";
case PYTHON_3_12:
return "python:3.12";
case PYTHON_3_13:
return "python:3.13";
default:
throw new NotImplementedException(request.getTargetPythonVersion().toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public class TestEnvironmentConfiguration {
Boolean.parseBoolean(System.getProperty("exclude-python-38", "false"));
public boolean excludePython311 =
Boolean.parseBoolean(System.getProperty("exclude-python-311", "false"));
public boolean excludePython312 =
Boolean.parseBoolean(System.getProperty("exclude-python-312", "false"));
public boolean excludePython313 =
Boolean.parseBoolean(System.getProperty("exclude-python-313", "false"));

public String testFilter = System.getenv("FILTER");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContex
if (targetPythonVersion == TargetPythonVersion.PYTHON_3_11 && config.excludePython311) {
continue;
}
if (targetPythonVersion == TargetPythonVersion.PYTHON_3_12 && config.excludePython312) {
continue;
}
if (targetPythonVersion == TargetPythonVersion.PYTHON_3_13 && config.excludePython313) {
continue;
}

for (boolean withBlueGreenFeature : Arrays.asList(true, false)) {
if (!withBlueGreenFeature) {
Expand Down
28 changes: 26 additions & 2 deletions tests/unit/test_connection_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,31 @@ def default_provider_mock(mocker):

@pytest.fixture
def set_provider_mock(mocker):
return mocker.MagicMock()
# In Python 3.12+, isinstance checks with Protocols are stricter
# Create a real instance that implements the protocol, then wrap release_resources
# with a mock to allow method call tracking
class MockConnectionProviderWithRelease:
"""Mock connection provider that implements CanReleaseResources protocol."""
def accepts_host_info(self, host_info, props):
return True

def accepts_strategy(self, role, strategy):
return True

def get_host_info_by_strategy(self, hosts, role, strategy, props):
return hosts[0] if hosts else None

def connect(self, target_func, driver_dialect, database_dialect, host_info, props):
return None

def release_resources(self):
pass

# Create a real instance
provider = MockConnectionProviderWithRelease()
# Replace release_resources with a MagicMock so we can assert it was called
provider.release_resources = mocker.MagicMock()
return provider


@pytest.fixture
Expand Down Expand Up @@ -177,4 +201,4 @@ def test_release_resources(connection_mock, default_provider_mock, set_provider_
ConnectionProviderManager.set_connection_provider(set_provider_mock)
ConnectionProviderManager.release_resources()

connection_provider_manager._conn_provider.release_resources.assert_called_once()
set_provider_mock.release_resources.assert_called_once()
8 changes: 6 additions & 2 deletions tests/unit/test_limitless_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
import psycopg
import pytest

from aws_advanced_python_wrapper.database_dialect import (DatabaseDialect,
from aws_advanced_python_wrapper.database_dialect import (AuroraPgDialect,
DatabaseDialect,
MysqlDatabaseDialect)
from aws_advanced_python_wrapper.errors import (AwsWrapperError,
UnsupportedOperationError)
Expand All @@ -36,6 +37,8 @@ def mock_plugin_service(mocker, mock_driver_dialect, mock_conn, host_info):
service_mock = mocker.MagicMock()
service_mock.current_connection = mock_conn
service_mock.current_host_info = host_info
# Use a real AuroraPgDialect to pass isinstance checks in Python 3.12+
service_mock.database_dialect = AuroraPgDialect()

type(service_mock).driver_dialect = mocker.PropertyMock(return_value=mock_driver_dialect)
return service_mock
Expand Down Expand Up @@ -125,7 +128,8 @@ def test_connect_supported_dialect_after_refresh(
mocker, plugin, host_info, props, mock_conn, mock_plugin_service, mock_limitless_router_service, mock_driver_dialect
):
unsupported_dialect: DatabaseDialect = MysqlDatabaseDialect()
type(mock_plugin_service).database_dialect = PropertyMock(side_effect=[unsupported_dialect, mock_driver_dialect])
supported_dialect: DatabaseDialect = AuroraPgDialect()
type(mock_plugin_service).database_dialect = PropertyMock(side_effect=[unsupported_dialect, supported_dialect])

def replace_context_connection(invocation):
context = invocation._connection_plugin._context
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/test_multi_az_rds_host_list_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import psycopg
import pytest

from aws_advanced_python_wrapper.database_dialect import AuroraPgDialect
from aws_advanced_python_wrapper.errors import (AwsWrapperError,
QueryTimeoutError)
from aws_advanced_python_wrapper.host_list_provider import (
Expand Down Expand Up @@ -59,7 +60,10 @@ def mock_cursor(mocker):

@pytest.fixture
def mock_provider_service(mocker):
return mocker.MagicMock()
service_mock = mocker.MagicMock()
# Use a real AuroraPgDialect to pass isinstance checks in Python 3.12+
service_mock.database_dialect = AuroraPgDialect()
return service_mock


@pytest.fixture
Expand Down
13 changes: 11 additions & 2 deletions tests/unit/test_mysql_driver_dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@

import psycopg
import pytest
from mysql.connector import CMySQLConnection
from mysql.connector.cursor_cext import CMySQLCursor

from aws_advanced_python_wrapper.errors import AwsWrapperError
from aws_advanced_python_wrapper.hostinfo import HostInfo
from aws_advanced_python_wrapper.mysql_driver_dialect import MySQLDriverDialect
from aws_advanced_python_wrapper.utils.properties import (Properties,
WrapperProperties)

try:
from mysql.connector import CMySQLConnection
from mysql.connector.cursor_cext import CMySQLCursor
except ImportError:
# CMySQLConnection not available (e.g., Python 3.13 with mysql-connector-python 9.0.0)
# Skip all tests in this module if C extension is not available
pytest.skip(
"CMySQLConnection not available (C extension not supported on this Python version)",
allow_module_level=True
)


@pytest.fixture
def dialect():
Expand Down
16 changes: 14 additions & 2 deletions tests/unit/test_pg_driver_dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import psycopg
import pytest
from mysql.connector import CMySQLConnection
from sqlalchemy import PoolProxiedConnection

from aws_advanced_python_wrapper.errors import AwsWrapperError
Expand All @@ -23,6 +22,14 @@
from aws_advanced_python_wrapper.utils.properties import (Properties,
WrapperProperties)

try:
from mysql.connector import CMySQLConnection
HAS_C_MYSQL_CONNECTION = True
except ImportError:
# CMySQLConnection not available (e.g., Python 3.13 with mysql-connector-python 9.0.0)
HAS_C_MYSQL_CONNECTION = False
CMySQLConnection = None


@pytest.fixture
def mock_conn(mocker):
Expand All @@ -40,7 +47,12 @@ def mock_pool_conn(mocker, mock_conn):

@pytest.fixture
def mock_invalid_conn(mocker):
return mocker.MagicMock(spec=CMySQLConnection)
if HAS_C_MYSQL_CONNECTION:
return mocker.MagicMock(spec=CMySQLConnection)
else:
# Use a different invalid connection type when CMySQLConnection is not available
# This simulates an invalid connection type for error testing
return mocker.MagicMock(spec=object)


@pytest.fixture
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/test_rds_host_list_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import psycopg
import pytest

from aws_advanced_python_wrapper.database_dialect import AuroraPgDialect
from aws_advanced_python_wrapper.errors import (AwsWrapperError,
QueryTimeoutError)
from aws_advanced_python_wrapper.host_list_provider import RdsHostListProvider
Expand Down Expand Up @@ -58,7 +59,10 @@ def mock_cursor(mocker):

@pytest.fixture
def mock_provider_service(mocker):
return mocker.MagicMock()
service_mock = mocker.MagicMock()
# Use a real AuroraPgDialect to pass isinstance checks in Python 3.12+
service_mock.database_dialect = AuroraPgDialect()
return service_mock


@pytest.fixture
Expand Down
Loading