diff --git a/py/misc/testing/test_warn.py b/py/misc/testing/test_warn.py new file mode 100644 index 000000000..e0b27f048 --- /dev/null +++ b/py/misc/testing/test_warn.py @@ -0,0 +1,53 @@ +import py +from py.__.misc.warn import WarningBus +mypath = py.magic.autopath() + +class TestWarningBus: + def setup_method(self, method): + self.wb = WarningBus() + self.warnings = [] + self.wb.subscribe(self.warnings.append) + + def test_basic(self): + self.wb.warn("hello") + assert len(self.warnings) == 1 + self.wb.unsubscribe(self.warnings.append) + self.wb.warn("this") + assert len(self.warnings) == 1 + w = self.warnings[0] + + def test_location(self): + self.wb.warn("again") + warning = self.warnings[0] + lno = self.test_location.im_func.func_code.co_firstlineno + 1 + assert warning.lineno == lno + assert warning.path == mypath + locstr = "%s:%d: " %(mypath, lno+1,) + assert repr(warning).startswith(locstr) + assert str(warning) == warning.msg + + def test_stacklevel(self): + l = [] + self.wb.subscribe(l.append) + def f(): + self.wb.warn("x", stacklevel=2) + # 5 + # 6 + f() + lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 7 + warning = l[0] + assert warning.lineno == lno + + def test_forwarding_to_warnings_module(self): + self.wb._setforwarding() + py.test.deprecated_call(self.wb.warn, "x") + + def test_apiwarn(self): + self.wb.apiwarn("3.0", "xxx") + warning = self.warnings[0] + assert warning.msg == "xxx (since version 3.0)" + +def test_APIWARN(): + from py.__.misc.warn import APIWARN + wb = APIWARN.im_self + assert wb._forward in wb._eventbus._subscribers diff --git a/py/misc/warn.py b/py/misc/warn.py new file mode 100644 index 000000000..146a295bb --- /dev/null +++ b/py/misc/warn.py @@ -0,0 +1,74 @@ +import py, sys +from py.__.test.event import EventBus + +class Warning(py.std.exceptions.DeprecationWarning): + def __init__(self, msg, path, lineno): + self.msg = msg + self.path = path + self.lineno = lineno + def __repr__(self): + return "%s:%d: %s" %(self.path, self.lineno+1, self.msg) + def __str__(self): + return self.msg + +class WarningBus(object): + def __init__(self): + self._eventbus = EventBus() + + def subscribe(self, callable): + self._eventbus.subscribe(callable) + + def unsubscribe(self, callable): + self._eventbus.unsubscribe(callable) + + def _setforwarding(self): + self._eventbus.subscribe(self._forward) + def _forward(self, warning): + py.std.warnings.warn_explicit(warning, category=Warning, + filename=str(warning.path), + lineno=warning.lineno, + registry=py.std.warnings.__dict__.setdefault( + "__warningsregistry__", {}) + ) + + def apiwarn(self, startversion, msg, stacklevel=1): + # below is mostly COPIED from python2.4/warnings.py's def warn() + # Get context information + msg = "%s (since version %s)" %(msg, startversion) + self.warn(msg, stacklevel=stacklevel+1) + + def warn(self, msg, stacklevel=1): + try: + caller = sys._getframe(stacklevel) + except ValueError: + globals = sys.__dict__ + lineno = 1 + else: + globals = caller.f_globals + lineno = caller.f_lineno + if '__name__' in globals: + module = globals['__name__'] + else: + module = "" + filename = globals.get('__file__') + if filename: + fnl = filename.lower() + if fnl.endswith(".pyc") or fnl.endswith(".pyo"): + filename = filename[:-1] + else: + if module == "__main__": + try: + filename = sys.argv[0] + except AttributeError: + # embedded interpreters don't have sys.argv, see bug #839151 + filename = '__main__' + if not filename: + filename = module + path = py.path.local(filename) + warning = Warning(msg, path, lineno) + self._eventbus.notify(warning) + +# singleton api warner for py lib +apiwarner = WarningBus() +apiwarner._setforwarding() +APIWARN = apiwarner.apiwarn diff --git a/py/path/common.py b/py/path/common.py index b747d6135..931e399e7 100644 --- a/py/path/common.py +++ b/py/path/common.py @@ -5,6 +5,7 @@ module with base functionality for std.path package from __future__ import generators import os, sys import py +from py.__.misc.warn import APIWARN def checktype(pathinstance, kw): names = ('local', 'svnwc', 'svnurl', 'py', ) @@ -21,9 +22,10 @@ class checker: kwargs-specified specification. """ def __init__(self, **kwargs): - py.std.warnings.warn("py.path.checker is deprecated, construct " - "calls to pathobj.check() instead", - DeprecationWarning, stacklevel=2) + APIWARN("0.9.0", + "py.path.checker is deprecated, construct " + "calls to pathobj.check() instead", + ) self.kwargs = kwargs def __call__(self, p): return p.check(**self.kwargs) diff --git a/py/test/collect.py b/py/test/collect.py index 58245dfa3..e0a0fc777 100644 --- a/py/test/collect.py +++ b/py/test/collect.py @@ -19,6 +19,7 @@ The is a schematic example of a tree of collectors and test items:: """ import py +from py.__.misc.warn import APIWARN def configproperty(name): def fget(self): @@ -493,10 +494,16 @@ def getrelpath(curdir, dest): def depwarn(msg): - py.std.warnings.warn(msg, DeprecationWarning) + APIWARN("1.0", msg, stacklevel=2) def warnoldcollect(): - return depwarn("implement collector.collect() instead of collector.run() and collector.join()") + APIWARN("1.0", + "implement collector.collect() instead of " + "collector.run() and collector.join()", + stacklevel=2) def warnoldtestrun(): - return depwarn("implement item.runtest() instead of item.run() and item.execute()") + APIWARN("1.0", + "implement item.runtest() instead of " + "item.run() and item.execute()", + stacklevel=2)