Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better warnings #838

Merged
merged 8 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions traitlets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Traitlets Python configuration system"""
from warnings import warn

from . import traitlets
from ._version import __version__, version_info
from .traitlets import *
from .utils.bunch import Bunch
from .utils.decorators import signature_has_traits
from .utils.importstring import import_item
from .utils.warnings import warn

__all__ = [
"traitlets",
Expand Down
11 changes: 8 additions & 3 deletions traitlets/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@


import logging
import warnings
from copy import deepcopy
from textwrap import dedent

Expand All @@ -20,6 +19,7 @@
observe_compat,
validate,
)
from traitlets.utils import warnings
from traitlets.utils.text import indent, wrap_paragraphs

from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key
Expand Down Expand Up @@ -184,7 +184,10 @@ def _load_config(self, cfg, section_names=None, traits=None):
if isinstance(self, LoggingConfigurable):
warn = self.log.warning
else:
warn = lambda msg: warnings.warn(msg, stacklevel=9) # noqa[E371]

def warn(msg):
return warnings.warn(msg, UserWarning, stacklevel=9)

matches = get_close_matches(name, traits)
msg = "Config option `{option}` not recognized by `{klass}`.".format(
option=name, klass=self.__class__.__name__
Expand Down Expand Up @@ -452,7 +455,9 @@ def _validate_log(self, proposal):
# warn about unsupported type, but be lenient to allow for duck typing
warnings.warn(
f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter,"
f" got {proposal.value}."
f" got {proposal.value}.",
UserWarning,
stacklevel=2,
)
return proposal.value

Expand Down
3 changes: 1 addition & 2 deletions traitlets/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
import re
import sys
import typing as t
import warnings

from traitlets.traitlets import Any, Container, Dict, HasTraits, List, Undefined

from ..utils import cast_unicode, filefind
from ..utils import cast_unicode, filefind, warnings

# -----------------------------------------------------------------------------
# Exceptions
Expand Down
2 changes: 1 addition & 1 deletion traitlets/config/tests/test_configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
List,
Set,
Unicode,
_deprecations_shown,
validate,
)
from traitlets.utils.warnings import _deprecations_shown

from ...tests._warnings import expected_warnings

Expand Down
73 changes: 14 additions & 59 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@
import types
import typing as t
from ast import literal_eval
from warnings import warn, warn_explicit

from .utils.bunch import Bunch
from .utils.descriptions import add_article, class_of, describe, repr_type
from .utils.getargspec import getargspec
from .utils.importstring import import_item
from .utils.sentinel import Sentinel
from .utils.warnings import deprecated_method, should_warn, warn

SequenceTypes = (list, tuple, set, frozenset)

Expand Down Expand Up @@ -161,61 +161,11 @@ class TraitError(Exception):
# Utilities
# -----------------------------------------------------------------------------

_name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")


def isidentifier(s):
return s.isidentifier()


_deprecations_shown = set()


def _should_warn(key):
"""Add our own checks for too many deprecation warnings.

Limit to once per package.
"""
env_flag = os.environ.get("TRAITLETS_ALL_DEPRECATIONS")
if env_flag and env_flag != "0":
return True

if key not in _deprecations_shown:
_deprecations_shown.add(key)
return True
else:
return False


def _deprecated_method(method, cls, method_name, msg):
"""Show deprecation warning about a magic method definition.

Uses warn_explicit to bind warning to method definition instead of triggering code,
which isn't relevant.
"""
warn_msg = "{classname}.{method_name} is deprecated in traitlets 4.1: {msg}".format(
classname=cls.__name__, method_name=method_name, msg=msg
)

for parent in inspect.getmro(cls):
if method_name in parent.__dict__:
cls = parent
break
# limit deprecation messages to once per package
package_name = cls.__module__.split(".", 1)[0]
key = (package_name, msg)
if not _should_warn(key):
return
try:
fname = inspect.getsourcefile(method) or "<unknown>"
lineno = inspect.getsourcelines(method)[1] or 0
except (OSError, TypeError) as e:
# Failed to inspect for some reason
warn(warn_msg + ("\n(inspection failed) %s" % e), DeprecationWarning)
else:
warn_explicit(warn_msg, DeprecationWarning, fname, lineno)


def _safe_literal_eval(s):
"""Safely evaluate an expression

Expand Down Expand Up @@ -578,7 +528,7 @@ def __init__(
mod = f.f_globals.get("__name__") or ""
pkg = mod.split(".", 1)[0]
key = ("metadata-tag", pkg, *sorted(kwargs))
if _should_warn(key):
if should_warn(key):
warn(
"metadata {} was set from the constructor. "
"With traitlets 4.1, metadata should be set using the .tag() method, "
Expand Down Expand Up @@ -751,7 +701,7 @@ def _cross_validate(self, obj, value):
elif hasattr(obj, "_%s_validate" % self.name):
meth_name = "_%s_validate" % self.name
cross_validate = getattr(obj, meth_name)
_deprecated_method(
deprecated_method(
cross_validate,
obj.__class__,
meth_name,
Expand Down Expand Up @@ -1139,6 +1089,7 @@ def compatible_observer(self, change_or_name, old=Undefined, new=Undefined):
"A parent of %s._%s_changed has adopted the new (traitlets 4.1) @observe(change) API"
% (clsname, change_or_name),
DeprecationWarning,
stacklevel=2,
)
change = Bunch(
type="change",
Expand Down Expand Up @@ -1544,7 +1495,7 @@ def _notify_observers(self, event):
if event['type'] == "change" and hasattr(self, magic_name):
class_value = getattr(self.__class__, magic_name)
if not isinstance(class_value, ObserveHandler):
_deprecated_method(
deprecated_method(
class_value,
self.__class__,
magic_name,
Expand Down Expand Up @@ -1724,7 +1675,7 @@ def _register_validator(self, handler, names):
if hasattr(self, magic_name):
class_value = getattr(self.__class__, magic_name)
if not isinstance(class_value, ValidateHandler):
_deprecated_method(
deprecated_method(
class_value,
self.__class__,
magic_name,
Expand Down Expand Up @@ -2511,7 +2462,8 @@ def from_string(self, s):
warn(
"Supporting extra quotes around Bytes is deprecated in traitlets 5.0. "
"Use {!r} instead of {!r}.".format(s, old_s),
FutureWarning,
DeprecationWarning,
stacklevel=2,
)
break
return s.encode("utf8")
Expand Down Expand Up @@ -2562,7 +2514,8 @@ def from_string(self, s):
"You can use {!r} instead of {!r} if you require traitlets >=5.".format(
s, old_s
),
FutureWarning,
DeprecationWarning,
stacklevel=2,
)
return s

Expand Down Expand Up @@ -2946,7 +2899,8 @@ def from_string_list(self, s_list):
"You can pass `--{0} item` ... multiple times to add items to a list.".format(
clsname + self.name, r
),
FutureWarning,
DeprecationWarning,
stacklevel=2,
)
return self.klass(literal_eval(r)) # type:ignore[operator]
sig = inspect.signature(self.item_from_string)
Expand Down Expand Up @@ -3454,7 +3408,8 @@ def from_string_list(self, s_list):
self.name,
s_list[0],
),
FutureWarning,
DeprecationWarning,
stacklevel=2,
)

return literal_eval(s_list[0])
Expand Down
63 changes: 63 additions & 0 deletions traitlets/utils/warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import inspect
import os
import warnings


def warn(msg, category, *, stacklevel, source=None):
"""Like warnings.warn(), but category and stacklevel are required.

You pretty much never want the default stacklevel of 1, so this helps
encourage setting it explicitly."""
return warnings.warn(msg, category=category, stacklevel=stacklevel, source=source)


def deprecated_method(method, cls, method_name, msg):
"""Show deprecation warning about a magic method definition.

Uses warn_explicit to bind warning to method definition instead of triggering code,
which isn't relevant.
"""
warn_msg = "{classname}.{method_name} is deprecated in traitlets 4.1: {msg}".format(
classname=cls.__name__, method_name=method_name, msg=msg
)

for parent in inspect.getmro(cls):
if method_name in parent.__dict__:
cls = parent
break
# limit deprecation messages to once per package
package_name = cls.__module__.split(".", 1)[0]
key = (package_name, msg)
if not should_warn(key):
return
try:
fname = inspect.getsourcefile(method) or "<unknown>"
lineno = inspect.getsourcelines(method)[1] or 0
except (OSError, TypeError) as e:
# Failed to inspect for some reason
warn(
warn_msg + ("\n(inspection failed) %s" % e),
DeprecationWarning,
stacklevel=2,
)
else:
warnings.warn_explicit(warn_msg, DeprecationWarning, fname, lineno)


_deprecations_shown = set()


def should_warn(key):
"""Add our own checks for too many deprecation warnings.

Limit to once per package.
"""
env_flag = os.environ.get("TRAITLETS_ALL_DEPRECATIONS")
if env_flag and env_flag != "0":
return True

if key not in _deprecations_shown:
_deprecations_shown.add(key)
return True
else:
return False