-
Notifications
You must be signed in to change notification settings - Fork 18
Add SQLAlchemy ORM plugin tests #1235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jonathanl-bq
wants to merge
41
commits into
sqlalchemy-orm-mysql
Choose a base branch
from
sqlalchemy-orm-plugins
base: sqlalchemy-orm-mysql
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+786
−22
Open
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
72457dc
Simple PG workflow working
aaron-congo 6cd61c7
Cleanup
aaron-congo bc607f6
Fix failover2 wrong writer host
aaron-congo 5daa970
Add mysql-connector SQLAlchemy ORM
jonathanl-bq 5ee014a
Revert connection string in sqlalchemy orm unit test
jonathanl-bq 4871ac7
Add __init__.py for sqlalchemy integration tests
jonathanl-bq 068e2e9
Fix RdsUtils not being found
jonathanl-bq f81b1db
Translate basic django test to sqlalchemy
jonathanl-bq a1ebf4c
Add basic CRUD test for sqlalchemy ORM mysql tests
jonathanl-bq f081a63
Add remaining basic MySQL SQLAlchemy ORM tests
jonathanl-bq 1b8d401
Remove temporary changes to get tests to run locally
jonathanl-bq 4429afd
Add license headers and remove unused import
jonathanl-bq 4f6500c
Try fixing mypy errors in tests
jonathanl-bq 9bd824f
Try to fix mypy Base class error
jonathanl-bq ee53f8c
fix: test failures due to using legacy sqlalchemy api (#1226)
karenc-bq 9f484bb
Add WIP sqlalchemy plugins tests
jonathanl-bq 0ab9c66
Fix multiple class definition errors
jonathanl-bq a64d748
Override initialize for mysql_orm_dialect.py
jonathanl-bq fa1f875
Fix most of the sqlalchemy ORM plugin tests
jonathanl-bq c981d2d
test: add clean up between tests (#1232)
karenc-bq 5c21749
Fix issue with plugins being shadowed by sqlalchemy create_engine
jonathanl-bq d5b2b82
Remove wrapper_plugins from opts after processing it
jonathanl-bq 79f990f
Merge branch 'sqlalchemy-orm-mysql' into sqlalchemy-orm-plugins
jonathanl-bq f955a9c
Try to fix mypy issues
jonathanl-bq c6958bc
Try fixing one mypy error
jonathanl-bq 1480c48
Fix syntax error
jonathanl-bq 0f9859d
Fix retrieved variable types
jonathanl-bq c75e9f7
Fix mypy error about row
jonathanl-bq 771b4b0
Try to fix mypy errors in mysql_orm_dialect.py
jonathanl-bq 7941fe0
Try to fix LSP violation errors
jonathanl-bq 159b6c3
Try to fix mypy error for missing errno field
jonathanl-bq 7e2bb1c
Fix last mypy error
jonathanl-bq edd4d02
Use err variable's errno property
jonathanl-bq 9f80c4e
Check errno property on correct type
jonathanl-bq f0d7d91
Address flake8 errors
jonathanl-bq cfebe5b
Add annotations import
jonathanl-bq b45cd27
Run isort
jonathanl-bq 88dfb8e
Run isort on tests
jonathanl-bq c999602
Move int cast for connect_timeout to fix unit tests
jonathanl-bq 9d2f642
Remove breakpoint call
jonathanl-bq 560e67c
Set supports_statement_cache flag
jonathanl-bq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,31 @@ | |
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # aws_advanced_python_wrapper/sqlalchemy/sqlalchemy_mysqlconnector_dialect.py | ||
| from __future__ import annotations | ||
|
|
||
| from typing import TYPE_CHECKING, Optional | ||
|
|
||
| import mysql.connector | ||
| from mysql.connector import CMySQLConnection | ||
| from mysql.connector.errors import Error | ||
| from sqlalchemy.dialects.mysql.mysqlconnector import \ | ||
| MySQLDialect_mysqlconnector | ||
| from sqlalchemy.engine import default | ||
|
|
||
| from aws_advanced_python_wrapper import AwsWrapperConnection | ||
| from aws_advanced_python_wrapper.errors import AwsWrapperError | ||
| from aws_advanced_python_wrapper.utils.properties import (Properties, | ||
| PropertiesUtils) | ||
|
|
||
| if TYPE_CHECKING: | ||
| from sqlalchemy import Connection | ||
|
|
||
| from aws_advanced_python_wrapper.hostinfo import HostInfo | ||
|
|
||
|
|
||
| class SqlAlchemyOrmMysqlDialect(MySQLDialect_mysqlconnector): | ||
| supports_statement_cache = True | ||
|
|
||
| """ | ||
| SQLAlchemy dialect for AWS Advanced Python Wrapper with mysqlconnector. Extends the SQLAlchemy MySQL mysqlconnector dialect. | ||
| This dialect is not related to the DriverDialect or DatabaseDialect classes used by our driver. Instead, it is used | ||
|
|
@@ -27,3 +46,154 @@ class SqlAlchemyOrmMysqlDialect(MySQLDialect_mysqlconnector): | |
|
|
||
| name = 'mysql' | ||
| driver = 'aws_wrapper_mysqlconnector' | ||
|
|
||
| @classmethod | ||
| def import_dbapi(cls): | ||
| """ | ||
| Return the DB-API 2.0 module. | ||
| SQLAlchemy calls this to get the driver module. | ||
| """ | ||
| import aws_advanced_python_wrapper | ||
| return aws_advanced_python_wrapper | ||
|
|
||
| def create_connect_args(self, url): | ||
| """ | ||
| Transform SQLAlchemy URL into connection arguments. | ||
| Must include the 'target' parameter for our wrapper driver. | ||
| """ | ||
| # Extract standard connection parameters | ||
| opts = url.translate_connect_args(username='user') | ||
|
|
||
| # Add query string parameters | ||
| opts.update(url.query) | ||
|
|
||
| # Add the required 'target' parameter for our wrapper | ||
| if 'target' not in opts: | ||
| opts['target'] = mysql.connector.Connect | ||
| if 'wrapper_plugins' not in opts: | ||
| opts['plugins'] = "aurora_connection_tracker,failover" | ||
| else: | ||
| opts['plugins'] = opts['wrapper_plugins'] | ||
| opts.pop('wrapper_plugins', None) | ||
| if 'connect_timeout' in opts: | ||
| opts['connect_timeout'] = int(opts['connect_timeout']) | ||
|
|
||
| # Return empty args list and kwargs dict | ||
| return [], opts | ||
|
|
||
| def _detect_charset(self, connection: Connection) -> str: | ||
| if isinstance(connection, CMySQLConnection): | ||
| return connection.charset | ||
| else: | ||
| raise Exception("Could not detect charset because connection was not a CMySQLConnection.") | ||
|
|
||
| def _extract_error_code(self, exception: BaseException) -> int: | ||
| if isinstance(exception, AwsWrapperError): | ||
| err = exception.driver_error | ||
| if err and isinstance(err, Error): | ||
| return err.errno | ||
| else: | ||
| raise Exception("Could not extract error code because driver_error was not a BaseException.") | ||
| else: | ||
| raise Exception("Could not extract error code because exception was not an AwsWrapperError.") | ||
|
|
||
| def initialize(self, connection): | ||
| """ | ||
| Override initialization to handle type introspection. | ||
| The parent class tries to use TypeInfo.fetch() which requires | ||
| a native SQLAlchemy connection, not AwsWrapperConnection. | ||
| """ | ||
|
|
||
| # Unwrap SQLAlchemy's connection object | ||
| wrapper_conn, wrapper_parent = self._get_wrapper_connection_and_parent(connection) | ||
|
|
||
| # this is driver-based, does not need server version info | ||
| # and is fairly critical for even basic SQL operations | ||
| self._connection_charset: Optional[str] = self._detect_charset( | ||
| wrapper_conn.target_connection | ||
| ) | ||
|
|
||
| # call super().initialize() because we need to have | ||
| # server_version_info set up. in 1.4 under python 2 only this does the | ||
| # "check unicode returns" thing, which is the one area that some | ||
| # SQL gets compiled within initialize() currently | ||
| default.DefaultDialect.initialize(self, connection) | ||
|
|
||
| self._detect_sql_mode(connection) | ||
| self._detect_ansiquotes(connection) # depends on sql mode | ||
| self._detect_casing(connection) | ||
| if self._server_ansiquotes: | ||
| # if ansiquotes == True, build a new IdentifierPreparer | ||
| # with the new setting | ||
| self.identifier_preparer = self.preparer( | ||
| self, server_ansiquotes=self._server_ansiquotes | ||
| ) | ||
|
|
||
| self.supports_sequences = ( | ||
| self.is_mariadb and self.server_version_info >= (10, 3) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why are we checking mariadb here? |
||
| ) | ||
|
|
||
| self.supports_for_update_of = ( | ||
| self._is_mysql and self.server_version_info >= (8,) | ||
| ) | ||
|
|
||
| self.use_mysql_for_share = ( | ||
| self._is_mysql and self.server_version_info >= (8, 0, 1) | ||
| ) | ||
|
|
||
| self._needs_correct_for_88718_96365 = ( | ||
| not self.is_mariadb and self.server_version_info >= (8,) | ||
| ) | ||
|
|
||
| self.delete_returning = ( | ||
| self.is_mariadb and self.server_version_info >= (10, 0, 5) | ||
| ) | ||
|
|
||
| self.insert_returning = ( | ||
| self.is_mariadb and self.server_version_info >= (10, 5) | ||
| ) | ||
|
|
||
| self._requires_alias_for_on_duplicate_key = ( | ||
| self._is_mysql and self.server_version_info >= (8, 0, 20) | ||
| ) | ||
|
|
||
| self._warn_for_known_db_issues() | ||
|
|
||
| def _get_wrapper_connection_and_parent(self, connection): | ||
| """ | ||
| Traverse the connection chain to find AwsWrapperConnection and its parent connection. | ||
|
|
||
| Args: | ||
| connection: SQLAlchemy Connection object | ||
|
|
||
| Returns: | ||
| AwsWrapperConnection instance or None, parent connection of AwsWrapperConnection or None | ||
| """ | ||
| # Start with the DBAPI connection | ||
| parent = connection | ||
| child = connection.connection | ||
|
|
||
| # Traverse up to 5 levels deep (reasonable limit) | ||
| for _ in range(5): | ||
| if isinstance(child, AwsWrapperConnection): | ||
| return child, parent | ||
|
|
||
| # Try to go deeper if there's a .connection attribute | ||
| if hasattr(child, 'connection'): | ||
| parent = child | ||
| child = child.connection | ||
| else: | ||
| break | ||
|
|
||
| return None | ||
|
|
||
| def prepare_connect_info(self, host_info: HostInfo, props: Properties) -> Properties: | ||
| prop_copy: Properties = Properties(props.copy()) | ||
|
|
||
| prop_copy["host"] = host_info.host | ||
|
|
||
| if host_info.is_port_specified(): | ||
| prop_copy["port"] = str(host_info.port) | ||
|
|
||
| PropertiesUtils.remove_wrapper_props(prop_copy) | ||
| return prop_copy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also need to check for other timeouts here