Repository: bokeh/bokeh

Issue:
syntax error in util/deprecation.py
line 24:
    message += " " + extra.trim()
results in error: AttributeError: 'str' object has no attribute 'trim'

it should be instead:
    message += " " + extra.strip()

that fixes the problem.  I needed that change to get the happiness demo to run

Helmut Strey



Discussion:
Ping @mattpap


Resolving patch:
diff --git a/bokeh/util/deprecation.py b/bokeh/util/deprecation.py
--- a/bokeh/util/deprecation.py
+++ b/bokeh/util/deprecation.py
@@ -17,11 +17,14 @@ def deprecated(since_or_msg, old=None, new=None, extra=None):
         if old is None or new is None:
             raise ValueError("deprecated entity and a replacement are required")
 
+        if len(since_or_msg) != 3 or not all(isinstance(x, int) and x >=0 for x in since_or_msg):
+            raise ValueError("invalid version tuple: %r" % (since_or_msg,))
+
         since = "%d.%d.%d" % since_or_msg
         message = "%(old)s was deprecated in Bokeh %(since)s and will be removed, use %(new)s instead."
         message = message % dict(old=old, since=since, new=new)
         if extra is not None:
-            message += " " + extra.trim()
+            message += " " + extra.strip()
     elif isinstance(since_or_msg, six.string_types):
         if not (old is None and new is None and extra is None):
             raise ValueError("deprecated(message) signature doesn't allow extra arguments")


Test patch:
diff --git a/bokeh/util/tests/test_deprecation.py b/bokeh/util/tests/test_deprecation.py
new file mode 100644
--- /dev/null
+++ b/bokeh/util/tests/test_deprecation.py
@@ -0,0 +1,61 @@
+import pytest
+from mock import patch
+import bokeh.util.deprecation as dep
+
+def foo(): pass
+
+def test_bad_arg_type():
+    for x in [10, True, foo, [], (), {}]:
+        with pytest.raises(ValueError):
+            dep.deprecated(x)
+
+@patch('warnings.warn')
+def test_message(mock_warn):
+    dep.deprecated('test')
+    assert mock_warn.called
+    assert mock_warn.call_args[0] == ("test", dep.BokehDeprecationWarning)
+    assert mock_warn.call_args[1] == {'stacklevel': 2}
+
+def test_message_no_extra_args():
+    with pytest.raises(ValueError):
+        dep.deprecated('test', 'foo')
+    with pytest.raises(ValueError):
+        dep.deprecated('test', old='foo')
+    with pytest.raises(ValueError):
+        dep.deprecated('test', new='foo')
+    with pytest.raises(ValueError):
+        dep.deprecated('test', extra='foo')
+
+def test_since_missing_extra_args():
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2,3))
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2,3), old="foo")
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2,3), new="foo")
+
+def test_since_bad_tuple():
+    with pytest.raises(ValueError):
+        dep.deprecated((1,), old="foo", new="bar")
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2), old="foo", new="bar")
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2,3,4), old="foo", new="bar")
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2,-4), old="foo", new="bar")
+    with pytest.raises(ValueError):
+        dep.deprecated((1,2,"3"), old="foo", new="bar")
+
+@patch('warnings.warn')
+def test_since(mock_warn):
+    dep.deprecated((1,2,3), old="foo", new="bar")
+    assert mock_warn.called
+    assert mock_warn.call_args[0] == ("foo was deprecated in Bokeh 1.2.3 and will be removed, use bar instead.", dep.BokehDeprecationWarning)
+    assert mock_warn.call_args[1] == {'stacklevel': 2}
+
+@patch('warnings.warn')
+def test_since_with_extra(mock_warn):
+    dep.deprecated((1,2,3), old="foo", new="bar", extra="baz")
+    assert mock_warn.called
+    assert mock_warn.call_args[0] == ("foo was deprecated in Bokeh 1.2.3 and will be removed, use bar instead. baz", dep.BokehDeprecationWarning)
+    assert mock_warn.call_args[1] == {'stacklevel': 2}