Repository: bokeh/bokeh

Issue:
Customize warning formatter
I'm trying out the imminent bokeh release with the dask dashboard.  I get hundreds of lines like the following:

```python
/home/mrocklin/Software/anaconda/lib/python3.6/site-packages/bokeh/models/sources.py:91: BokehUserWarning: ColumnD)
  "Current lengths: %s" % ", ".join(sorted(str((k, len(v))) for k, v in data.items())), BokehUserWarning))
```

Clearly I'm doing something wrong in my code, and it's great to know about it.  However, two things would make this nicer:

1.  Getting some sort of information about the cause of the failure.  It looks like an informative error message was attempted, but rather than getting a nice result I get the code instead.
2.  These are filling up my terminal at the rate that I update my plots.  It might be nice to only warn once or twice.


Discussion:
@mrocklin are you saying there is not an accompanying line that *does* have the formatted message? 


That is correct.  I just get those two lines repeated.
@mrocklin I'm at a bit of a loss, I would expect this:
```
In [1]: from bokeh.models import ColumnDataSource

In [2]: source = ColumnDataSource(data=dict(a=[1], b=[1,2]))
/Users/bryan/work/bokeh/bokeh/models/sources.py:91: BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('a', 1), ('b', 2)
  "Current lengths: %s" % ", ".join(sorted(str((k, len(v))) for k, v in data.items())), BokehUserWarning))
```

As you can see this is identical to yours, except your output has truncated the line that has the actual information in it. The "extra" line with the code is alway how I've seen `warnings.warn` work, I also think its annoying but am not aware of any way to turn it off. Is there something that could be truncating your log lines on your end?
Re: the "code" the only solution seems to be to monkey patch the `warnings` module, ~~~which I would not be in favor of~~~

https://stackoverflow.com/questions/2187269/python-print-only-the-message-on-warnings

EDIT: Evidently monkeypatching `showarnings` is officially endorsed in the python docs:

https://docs.python.org/3/library/warnings.html#warnings.showwarning
@mrocklin I am definitely in favor of making the output prettier (i.e. not include the line of code for the warning itself, which is pretty useless). I suspect the truncation is somehow a problem on your end, though. 

I am -0.5 on rate limiting the warning. Ragged column lengths is a serious usage error, and has been a support burden for us for years. The warning is meant to be annoying, very annoying. 
OK, yes, I've verified that something on my end was squashing that bit of the warning message.  It's somewhat odd that only part of the message came through, but given that I'm doing weird things this probably isn't worth diving into further.  

On the plus side I've now resolved the underlying issue on my end that was creating the warnings, so that's a plus :)
I've changed the task to reflect the part that seems reasonable (a better formatter for warnings)

It also might just do to up the `stacklevel` parameter in that particular warning.
I would like to add this particular warning is a warning for users' convenience, so that a lot of broken code didn't just suddenly stop working. This will be changed to an exception at some point (pre-1.0 I presume).

Resolving patch:
diff --git a/bokeh/__init__.py b/bokeh/__init__.py
--- a/bokeh/__init__.py
+++ b/bokeh/__init__.py
@@ -60,10 +60,21 @@ def license():
 from .util import logconfig
 del logconfig
 
-# Configure warnings to always show, despite Python's active efforts to hide them from users.
+# Configure warnings to always show nice mssages, despite Python's active
+# efforts to hide them from users.
 import warnings
 from .util.warnings import BokehDeprecationWarning, BokehUserWarning
 warnings.simplefilter('always', BokehDeprecationWarning)
 warnings.simplefilter('always', BokehUserWarning)
+
+original_formatwarning = warnings.formatwarning
+def _formatwarning(message, category, filename, lineno, line=None):
+    from .util.warnings import BokehDeprecationWarning, BokehUserWarning
+    if category not in (BokehDeprecationWarning, BokehUserWarning):
+        return original_formatwarning(message, category, filename, lineno, line)
+    return "%s: %s\n" % (category.__name__, message)
+warnings.formatwarning = _formatwarning
+
+del _formatwarning
 del BokehDeprecationWarning, BokehUserWarning
 del warnings


Test patch:
diff --git a/bokeh/tests/test___init__.py b/bokeh/tests/test___init__.py
--- a/bokeh/tests/test___init__.py
+++ b/bokeh/tests/test___init__.py
@@ -20,12 +20,14 @@
 # Standard library imports
 from os.path import dirname, join
 from shutil import copy
+import warnings
 
 # External imports
 from six import string_types
 
 # Bokeh imports
 from bokeh.util.testing import verify_all
+from bokeh.util.warnings import BokehDeprecationWarning, BokehUserWarning
 
 # Module under test
 import bokeh as b
@@ -95,3 +97,17 @@ def test_license(capsys):
     b.license()
     out, err = capsys.readouterr()
     assert out == _LICENSE
+
+class TestWarnings(object):
+    @pytest.mark.parametrize('cat', (BokehDeprecationWarning, BokehUserWarning))
+    def test_bokeh_custom(self, cat):
+        r = warnings.formatwarning("message", cat, "line", "lineno")
+        assert r == "%s: %s\n" %(cat.__name__, "message")
+
+    def test_general_default(self):
+        r = warnings.formatwarning("message", RuntimeWarning, "line", "lineno")
+        assert r == "line:lineno: RuntimeWarning: message\n"
+
+    def test_filters(self):
+        assert ('always', None, BokehUserWarning, None, 0) in warnings.filters
+        assert ('always', None, BokehDeprecationWarning, None, 0) in warnings.filters