Skip to content

Commit

Permalink
Added explicit stacklevel arguments to all warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Mar 22, 2023
1 parent 76fcd70 commit a37c482
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 11 deletions.
2 changes: 2 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-v

**UNRELEASED**

- Improved warnings by ensuring that they target user code and not Typeguard internal
code
- Fixed ``warn_on_error()`` not showing where the type violation actually occurred
- Fixed local assignment to ``*args`` or ``**kwargs`` being type checked incorrectly
- Fixed ``TypeError`` on ``check_type(..., None)``
Expand Down
13 changes: 9 additions & 4 deletions src/typeguard/_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from ._config import ForwardRefPolicy, global_config
from ._exceptions import TypeCheckError, TypeHintWarning
from ._memo import CallMemo, TypeCheckMemo
from ._utils import evaluate_forwardref, get_type_name, qualified_name
from ._utils import evaluate_forwardref, get_stacklevel, get_type_name, qualified_name

if sys.version_info >= (3, 11):
from typing import (
Expand Down Expand Up @@ -572,7 +572,8 @@ def check_protocol(
warnings.warn(
f"Typeguard cannot check the {origin_type.__qualname__} protocol because "
f"it is a non-runtime protocol. If you would like to type check this "
f"protocol, please use @typing.runtime_checkable"
f"protocol, please use @typing.runtime_checkable",
stacklevel=get_stacklevel(),
)


Expand Down Expand Up @@ -637,6 +638,7 @@ def check_type_internal(value: Any, annotation: Any, memo: TypeCheckMemo) -> Non
warnings.warn(
f"Cannot resolve forward reference {annotation.__forward_arg__!r}",
TypeHintWarning,
stacklevel=get_stacklevel(),
)

return
Expand Down Expand Up @@ -770,12 +772,15 @@ def load_plugins() -> None:
plugin = ep.load()
except Exception as exc:
warnings.warn(
f"Failed to load plugin {ep.name!r}: " f"{qualified_name(exc)}: {exc}"
f"Failed to load plugin {ep.name!r}: " f"{qualified_name(exc)}: {exc}",
stacklevel=2,
)
continue

if not callable(plugin):
warnings.warn(f"Plugin {ep} returned a non-callable object: {plugin!r}")
warnings.warn(
f"Plugin {ep} returned a non-callable object: {plugin!r}", stacklevel=2
)
continue

checker_lookup_functions.insert(0, plugin)
3 changes: 2 additions & 1 deletion src/typeguard/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ._config import global_config
from ._exceptions import InstrumentationWarning
from ._transformer import TypeguardTransformer
from ._utils import function_name, is_method_of
from ._utils import function_name, get_stacklevel, is_method_of

if TYPE_CHECKING:
from typeshed.stdlib.types import _Cell
Expand Down Expand Up @@ -177,6 +177,7 @@ def typechecked(target: T_CallableOrType | None = None) -> Any:
warn(
f"{retval} -- not typechecking {function_name(target)}",
InstrumentationWarning,
stacklevel=get_stacklevel(),
)
return target

Expand Down
4 changes: 2 additions & 2 deletions src/typeguard/_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ._config import global_config
from ._exceptions import TypeCheckError, TypeCheckWarning
from ._memo import CallMemo, TypeCheckMemo
from ._utils import qualified_name
from ._utils import get_stacklevel, qualified_name

if sys.version_info >= (3, 11):
from typing import Never
Expand Down Expand Up @@ -308,7 +308,7 @@ def warn_on_error(exc: TypeCheckError, memo: TypeCheckMemo) -> None:
:attr:`TypeCheckConfiguration.typecheck_fail_callback`.
"""
warnings.warn(TypeCheckWarning(str(exc)), stacklevel=3)
warnings.warn(TypeCheckWarning(str(exc)), stacklevel=get_stacklevel())


@contextmanager
Expand Down
1 change: 1 addition & 0 deletions src/typeguard/_pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def pytest_configure(config: Config) -> None:
f"typeguard cannot check these packages because they are already "
f"imported: {', '.join(already_imported_packages)}",
InstrumentationWarning,
stacklevel=1,
)

install_import_hook(packages=packages)
Expand Down
15 changes: 13 additions & 2 deletions src/typeguard/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import inspect
import sys
from importlib import import_module
from types import CodeType, FunctionType
from typing import TYPE_CHECKING, Any, Callable, ForwardRef, Union
from inspect import currentframe
from types import CodeType, FrameType, FunctionType
from typing import TYPE_CHECKING, Any, Callable, ForwardRef, Union, cast
from weakref import WeakValueDictionary

if TYPE_CHECKING:
Expand Down Expand Up @@ -137,3 +138,13 @@ def is_method_of(obj: object, cls: type) -> bool:
and obj.__module__ == cls.__module__
and obj.__qualname__.startswith(cls.__qualname__ + ".")
)


def get_stacklevel() -> int:
level = 1
frame = cast(FrameType, currentframe()).f_back
while frame and frame.f_globals.get("__name__", "").startswith("typeguard."):
level += 1
frame = frame.f_back

return level
8 changes: 6 additions & 2 deletions tests/test_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,14 +877,18 @@ def meth(self) -> None:

with pytest.warns(
UserWarning, match=r"Typeguard cannot check the StaticProtocol protocol.*"
):
) as warning:
check_type(Foo(), StaticProtocol)

assert warning.list[0].filename == __file__

with pytest.warns(
UserWarning, match=r"Typeguard cannot check the StaticProtocol protocol.*"
):
) as warning:
check_type(Foo, Type[StaticProtocol])

assert warning.list[0].filename == __file__

def test_fail_non_method_members(self):
class Foo:
val = 1
Expand Down

0 comments on commit a37c482

Please sign in to comment.