From 3183efc76064153218d3a0f75fb96804ae41c85f Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Thu, 26 Jan 2023 12:18:33 +0100 Subject: [PATCH] QA: Add EOL/deprecation warning about support for SQLAlchemy 1.3 --- src/crate/client/sqlalchemy/__init__.py | 16 +++++++++- src/crate/client/sqlalchemy/tests/__init__.py | 2 ++ .../client/sqlalchemy/tests/warnings_test.py | 30 +++++++++++++++++++ src/crate/testing/util.py | 20 +++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/crate/client/sqlalchemy/tests/warnings_test.py create mode 100644 src/crate/testing/util.py diff --git a/src/crate/client/sqlalchemy/__init__.py b/src/crate/client/sqlalchemy/__init__.py index 52864719..ce18faec 100644 --- a/src/crate/client/sqlalchemy/__init__.py +++ b/src/crate/client/sqlalchemy/__init__.py @@ -23,10 +23,24 @@ from .dialect import CrateDialect from .sa_version import SA_1_4, SA_VERSION -# SQLAlchemy 1.3 does not have the `exec_driver_sql` method. + if SA_VERSION < SA_1_4: + import textwrap + import warnings + + # SQLAlchemy 1.3 is effectively EOL. + SA13_DEPRECATION_WARNING = textwrap.dedent(""" + WARNING: SQLAlchemy 1.3 is effectively EOL. + With SQLAlchemy 2 around the corner, this will be even more immanent. + Future versions of the CrateDB SQLAlchemy dialect will drop support for SQLAlchemy 1.3. + It is recommended that you transition to using SQLAlchemy 1.4 or 2.0. + """.lstrip("\n")) + warnings.warn(message=SA13_DEPRECATION_WARNING, category=DeprecationWarning) + + # SQLAlchemy 1.3 does not have the `exec_driver_sql` method, so add it. monkeypatch_add_exec_driver_sql() + __all__ = [ CrateDialect, ] diff --git a/src/crate/client/sqlalchemy/tests/__init__.py b/src/crate/client/sqlalchemy/tests/__init__.py index 61a2669b..00abe4bf 100644 --- a/src/crate/client/sqlalchemy/tests/__init__.py +++ b/src/crate/client/sqlalchemy/tests/__init__.py @@ -21,6 +21,7 @@ from .array_test import SqlAlchemyArrayTypeTest from .dialect_test import SqlAlchemyDialectTest from .function_test import SqlAlchemyFunctionTest +from .warnings_test import SqlAlchemyWarningsTest def test_suite(): @@ -38,4 +39,5 @@ def test_suite(): tests.addTest(makeSuite(SqlAlchemyDialectTest)) tests.addTest(makeSuite(SqlAlchemyFunctionTest)) tests.addTest(makeSuite(SqlAlchemyArrayTypeTest)) + tests.addTest(makeSuite(SqlAlchemyWarningsTest)) return tests diff --git a/src/crate/client/sqlalchemy/tests/warnings_test.py b/src/crate/client/sqlalchemy/tests/warnings_test.py new file mode 100644 index 00000000..c6fff3f0 --- /dev/null +++ b/src/crate/client/sqlalchemy/tests/warnings_test.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +import sys +import warnings +from unittest import TestCase + +from crate.testing.util import ExtraAssertions + + +class SqlAlchemyWarningsTest(TestCase, ExtraAssertions): + + def test_sa13_deprecation_warning(self): + """ + Verify that a `DeprecationWarning` is issued when running SQLAlchemy 1.3. + + https://docs.python.org/3/library/warnings.html#testing-warnings + """ + with warnings.catch_warnings(record=True) as w: + + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Trigger a warning by importing the SQLAlchemy dialect module. + # Because it already has been loaded, unload it beforehand. + del sys.modules["crate.client.sqlalchemy"] + import crate.client.sqlalchemy # noqa: F401 + + # Verify details of the SA13 EOL/deprecation warning. + self.assertEqual(len(w), 1) + self.assertIsSubclass(w[-1].category, DeprecationWarning) + self.assertIn("SQLAlchemy 1.3 is effectively EOL.", str(w[-1].message)) diff --git a/src/crate/testing/util.py b/src/crate/testing/util.py new file mode 100644 index 00000000..3e9885d6 --- /dev/null +++ b/src/crate/testing/util.py @@ -0,0 +1,20 @@ +class ExtraAssertions: + """ + Additional assert methods for unittest. + + - https://github.com/python/cpython/issues/71339 + - https://bugs.python.org/issue14819 + - https://bugs.python.org/file43047/extra_assertions.patch + """ + + def assertIsSubclass(self, cls, superclass, msg=None): + try: + r = issubclass(cls, superclass) + except TypeError: + if not isinstance(cls, type): + self.fail(self._formatMessage(msg, + '%r is not a class' % (cls,))) + raise + if not r: + self.fail(self._formatMessage(msg, + '%r is not a subclass of %r' % (cls, superclass)))