summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/testing/assertions.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-09-04 12:12:46 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-09-28 13:27:54 -0400
commit6ce0d644db60ce6ea89eb15a76e078c4fa1a9066 (patch)
treef800bc5e1c308eae078e732c834d8eca501461cb /lib/sqlalchemy/testing/assertions.py
parent52e8545b2df312898d46f6a5b119675e8d0aa956 (diff)
downloadsqlalchemy-6ce0d644db60ce6ea89eb15a76e078c4fa1a9066.tar.gz
warn or deprecate for auto-aliasing in joins
An extra layer of warning messages has been added to the functionality of :meth:`_orm.Query.join` and the ORM version of :meth:`_sql.Select.join`, where a few places where "automatic aliasing" continues to occur will now be called out as a pattern to avoid, mostly specific to the area of joined table inheritance where classes that share common base tables are being joined together without using explicit aliases. One case emits a legacy warning for a pattern that's not recommended, the other case is fully deprecated. The automatic aliasing within ORM join() which occurs for overlapping mapped tables does not work consistently with all APIs such as ``contains_eager()``, and rather than continue to try to make these use cases work everywhere, replacing with a more user-explicit pattern is clearer, less prone to bugs and simplifies SQLAlchemy's internals further. The warnings include links to the errors.rst page where each pattern is demonstrated along with the recommended pattern to fix. * Improved the exception message generated when configuring a mapping with joined table inheritance where the two tables either have no foreign key relationships set up, or where they have multiple foreign key relationships set up. The message is now ORM specific and includes context that the :paramref:`_orm.Mapper.inherit_condition` parameter may be needed particularly for the ambiguous foreign keys case. * Add explicit support in the _expect_warnings() assertion for nested _expect_warnings calls * generalize the NoCache fixture, which we also need to catch warnings during compilation consistently * generalize the __str__() method for the HasCode mixin so all warnings and errors include the code link in their string Fixes: #6974 Change-Id: I84ed79ba2112c39eaab7973b6d6f46de7fa80842
Diffstat (limited to 'lib/sqlalchemy/testing/assertions.py')
-rw-r--r--lib/sqlalchemy/testing/assertions.py105
1 files changed, 64 insertions, 41 deletions
diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py
index 0cf0cbc7a..986dbb5e9 100644
--- a/lib/sqlalchemy/testing/assertions.py
+++ b/lib/sqlalchemy/testing/assertions.py
@@ -133,6 +133,11 @@ def uses_deprecated(*messages):
return decorate
+_FILTERS = None
+_SEEN = None
+_EXC_CLS = None
+
+
@contextlib.contextmanager
def _expect_warnings(
exc_cls,
@@ -143,58 +148,76 @@ def _expect_warnings(
raise_on_any_unexpected=False,
):
+ global _FILTERS, _SEEN, _EXC_CLS
+
if regex:
filters = [re.compile(msg, re.I | re.S) for msg in messages]
else:
- filters = messages
-
- seen = set(filters)
+ filters = list(messages)
+
+ if _FILTERS is not None:
+ # nested call; update _FILTERS and _SEEN, return. outer
+ # block will assert our messages
+ assert _SEEN is not None
+ assert _EXC_CLS is not None
+ _FILTERS.extend(filters)
+ _SEEN.update(filters)
+ _EXC_CLS += (exc_cls,)
+ yield
+ else:
+ seen = _SEEN = set(filters)
+ _FILTERS = filters
+ _EXC_CLS = (exc_cls,)
- if raise_on_any_unexpected:
+ if raise_on_any_unexpected:
- def real_warn(msg, *arg, **kw):
- raise AssertionError("Got unexpected warning: %r" % msg)
+ def real_warn(msg, *arg, **kw):
+ raise AssertionError("Got unexpected warning: %r" % msg)
- else:
- real_warn = warnings.warn
-
- def our_warn(msg, *arg, **kw):
- if isinstance(msg, exc_cls):
- exception = type(msg)
- msg = str(msg)
- elif arg:
- exception = arg[0]
else:
- exception = None
+ real_warn = warnings.warn
- if not exception or not issubclass(exception, exc_cls):
- return real_warn(msg, *arg, **kw)
+ def our_warn(msg, *arg, **kw):
- if not filters and not raise_on_any_unexpected:
- return
+ if isinstance(msg, _EXC_CLS):
+ exception = type(msg)
+ msg = str(msg)
+ elif arg:
+ exception = arg[0]
+ else:
+ exception = None
- for filter_ in filters:
- if (regex and filter_.match(msg)) or (
- not regex and filter_ == msg
- ):
- seen.discard(filter_)
- break
- else:
- real_warn(msg, *arg, **kw)
-
- with mock.patch("warnings.warn", our_warn), mock.patch(
- "sqlalchemy.util.SQLALCHEMY_WARN_20", True
- ), mock.patch(
- "sqlalchemy.util.deprecations.SQLALCHEMY_WARN_20", True
- ), mock.patch(
- "sqlalchemy.engine.row.LegacyRow._default_key_style", 2
- ):
- yield
+ if not exception or not issubclass(exception, _EXC_CLS):
+ return real_warn(msg, *arg, **kw)
- if assert_ and (not py2konly or not compat.py3k):
- assert not seen, "Warnings were not seen: %s" % ", ".join(
- "%r" % (s.pattern if regex else s) for s in seen
- )
+ if not filters and not raise_on_any_unexpected:
+ return
+
+ for filter_ in filters:
+ if (regex and filter_.match(msg)) or (
+ not regex and filter_ == msg
+ ):
+ seen.discard(filter_)
+ break
+ else:
+ real_warn(msg, *arg, **kw)
+
+ with mock.patch("warnings.warn", our_warn), mock.patch(
+ "sqlalchemy.util.SQLALCHEMY_WARN_20", True
+ ), mock.patch(
+ "sqlalchemy.util.deprecations.SQLALCHEMY_WARN_20", True
+ ), mock.patch(
+ "sqlalchemy.engine.row.LegacyRow._default_key_style", 2
+ ):
+ try:
+ yield
+ finally:
+ _SEEN = _FILTERS = _EXC_CLS = None
+
+ if assert_ and (not py2konly or not compat.py3k):
+ assert not seen, "Warnings were not seen: %s" % ", ".join(
+ "%r" % (s.pattern if regex else s) for s in seen
+ )
def global_cleanup_assertions():