diff options
author | jonathan vanasco <jonathan@2xlp.com> | 2021-11-12 12:45:54 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-01-24 22:03:06 -0500 |
commit | 03212e5aefb3d5c912d25274f447eb9c493f7268 (patch) | |
tree | fdf6b7919dab70e979304f43b6fbde9b8ad6515b /lib/sqlalchemy/util/langhelpers.py | |
parent | 55b243a3f22f267c829f9088fd4e801f0b621a31 (diff) | |
download | sqlalchemy-03212e5aefb3d5c912d25274f447eb9c493f7268.tar.gz |
add context for warnings emitted from configure_mappers(), autoflush()
Improved the notification of warnings that are emitted within the configure
mappers or flush process, which are often invoked as part of a different
operation, to add additional context to the message that indicates one of
these operations as the source of the warning within operations that may
not be obviously related.
Fixes: #7305
Change-Id: I79da7a6a5d4cf67d57615d0ffc2b8d8454011c84
Diffstat (limited to 'lib/sqlalchemy/util/langhelpers.py')
-rw-r--r-- | lib/sqlalchemy/util/langhelpers.py | 47 |
1 files changed, 40 insertions, 7 deletions
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 767148045..01f083bec 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -24,6 +24,7 @@ import sys import textwrap import threading import types +from types import CodeType from typing import Any from typing import Callable from typing import cast @@ -1825,6 +1826,19 @@ def warn_limited(msg: str, args: Sequence[Any]) -> None: _warnings_warn(msg, exc.SAWarning) +_warning_tags: Dict[CodeType, Tuple[str, Type[Warning]]] = {} + + +def tag_method_for_warnings( + message: str, category: Type[Warning] +) -> Callable[[_F], _F]: + def go(fn): + _warning_tags[fn.__code__] = (message, category) + return fn + + return go + + _not_sa_pattern = re.compile(r"^(?:sqlalchemy\.(?!testing)|alembic\.)") @@ -1846,14 +1860,33 @@ def _warnings_warn( # ok, but don't crash stacklevel = 0 else: - # using __name__ here requires that we have __name__ in the - # __globals__ of the decorated string functions we make also. - # we generate this using {"__name__": fn.__module__} - while frame is not None and re.match( - _not_sa_pattern, frame.f_globals.get("__name__", "") - ): + stacklevel_found = warning_tag_found = False + while frame is not None: + # using __name__ here requires that we have __name__ in the + # __globals__ of the decorated string functions we make also. + # we generate this using {"__name__": fn.__module__} + if not stacklevel_found and not re.match( + _not_sa_pattern, frame.f_globals.get("__name__", "") + ): + # stop incrementing stack level if an out-of-SQLA line + # were found. + stacklevel_found = True + + # however, for the warning tag thing, we have to keep + # scanning up the whole traceback + + if frame.f_code in _warning_tags: + warning_tag_found = True + (_prefix, _category) = _warning_tags[frame.f_code] + category = category or _category + message = f"{message} ({_prefix})" + frame = frame.f_back # type: ignore[assignment] - stacklevel += 1 + + if not stacklevel_found: + stacklevel += 1 + elif stacklevel_found and warning_tag_found: + break if category is not None: warnings.warn(message, category, stacklevel=stacklevel + 1) |