Show file and line number in deprecation warnings (#4631)

* Show file and line number in deprecation warnings

* Exclude modules/packages by import

Less bespoke method of considering some packages to be part of the framework
and passed over when finding user code.
This commit is contained in:
Masen Furer 2025-01-15 14:19:11 -08:00 committed by GitHub
parent caf29c3680
commit fbf9524a6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 5 deletions

View File

@ -2,6 +2,11 @@
from __future__ import annotations from __future__ import annotations
import inspect
import shutil
from pathlib import Path
from types import FrameType
from rich.console import Console from rich.console import Console
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from rich.prompt import Prompt from rich.prompt import Prompt
@ -188,6 +193,33 @@ def warn(msg: str, dedupe: bool = False, **kwargs):
print(f"[orange1]Warning: {msg}[/orange1]", **kwargs) print(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
def _get_first_non_framework_frame() -> FrameType | None:
import click
import typer
import typing_extensions
import reflex as rx
# Exclude utility modules that should never be the source of deprecated reflex usage.
exclude_modules = [click, rx, typer, typing_extensions]
exclude_roots = [
p.parent.resolve()
if (p := Path(m.__file__)).name == "__init__.py"
else p.resolve()
for m in exclude_modules
]
# Specifically exclude the reflex cli module.
if reflex_bin := shutil.which(b"reflex"):
exclude_roots.append(Path(reflex_bin.decode()))
frame = inspect.currentframe()
while frame := frame and frame.f_back:
frame_path = Path(inspect.getfile(frame)).resolve()
if not any(frame_path.is_relative_to(root) for root in exclude_roots):
break
return frame
def deprecate( def deprecate(
feature_name: str, feature_name: str,
reason: str, reason: str,
@ -206,15 +238,27 @@ def deprecate(
dedupe: If True, suppress multiple console logs of deprecation message. dedupe: If True, suppress multiple console logs of deprecation message.
kwargs: Keyword arguments to pass to the print function. kwargs: Keyword arguments to pass to the print function.
""" """
if feature_name not in _EMITTED_DEPRECATION_WARNINGS: dedupe_key = feature_name
loc = ""
# See if we can find where the deprecation exists in "user code"
origin_frame = _get_first_non_framework_frame()
if origin_frame is not None:
filename = Path(origin_frame.f_code.co_filename)
if filename.is_relative_to(Path.cwd()):
filename = filename.relative_to(Path.cwd())
loc = f"{filename}:{origin_frame.f_lineno}"
dedupe_key = f"{dedupe_key} {loc}"
if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS:
msg = ( msg = (
f"{feature_name} has been deprecated in version {deprecation_version} {reason.rstrip('.')}. It will be completely " f"{feature_name} has been deprecated in version {deprecation_version} {reason.rstrip('.')}. It will be completely "
f"removed in {removal_version}" f"removed in {removal_version}. ({loc})"
) )
if _LOG_LEVEL <= LogLevel.WARNING: if _LOG_LEVEL <= LogLevel.WARNING:
print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
if dedupe: if dedupe:
_EMITTED_DEPRECATION_WARNINGS.add(feature_name) _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key)
def error(msg: str, dedupe: bool = False, **kwargs): def error(msg: str, dedupe: bool = False, **kwargs):