Repository: pallets/click

Issue:
ipython notebook support fo colored output
hi, currently click strips colors when used in code invoked by ipython notebooks

after a short chat on the ipython notebook channel i learned that a commonly suggested workaround is 

`click.utils.should_strip_ansi = lambda *k, **kw: False` and that a reasonably reliable way to detect such a stream is `stream.__class__.__module__.startswith('ipykernel.')`

i propose setting up isatty to check this in the exception case (aka no tty attribute as the jupyter streams dont have one

additional help will be needed from someone with a windows system, as to me it is not clear whether its required to alter the stream wrapping that is used on win32




Discussion:

Resolving patch:
diff --git a/click/_compat.py b/click/_compat.py
--- a/click/_compat.py
+++ b/click/_compat.py
@@ -570,11 +570,23 @@ def strip_ansi(value):
     return _ansi_re.sub('', value)
 
 
+def _is_jupyter_kernel_output(stream):
+    if WIN:
+        # TODO: Couldn't test on Windows, should't try to support until
+        # someone tests the details wrt colorama.
+        return
+
+    while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
+        stream = stream._stream
+
+    return stream.__class__.__module__.startswith("ipykernel.")
+
+
 def should_strip_ansi(stream=None, color=None):
     if color is None:
         if stream is None:
             stream = sys.stdin
-        return not isatty(stream)
+        return not isatty(stream) and not _is_jupyter_kernel_output(stream)
     return not color
 
 


Test patch:
diff --git a/tests/test_compat.py b/tests/test_compat.py
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,21 +1,21 @@
 import click
 import pytest
+from click._compat import should_strip_ansi, WIN
 
 
-if click.__version__ >= '3.0':
-    def test_legacy_callbacks(runner):
-        def legacy_callback(ctx, value):
-            return value.upper()
+def test_legacy_callbacks(runner):
+    def legacy_callback(ctx, value):
+        return value.upper()
 
-        @click.command()
-        @click.option('--foo', callback=legacy_callback)
-        def cli(foo):
-            click.echo(foo)
+    @click.command()
+    @click.option('--foo', callback=legacy_callback)
+    def cli(foo):
+        click.echo(foo)
 
-        with pytest.warns(Warning, match='Invoked legacy parameter callback'):
-            result = runner.invoke(cli, ['--foo', 'wat'])
-            assert result.exit_code == 0
-            assert 'WAT' in result.output
+    with pytest.warns(Warning, match='Invoked legacy parameter callback'):
+        result = runner.invoke(cli, ['--foo', 'wat'])
+        assert result.exit_code == 0
+        assert 'WAT' in result.output
 
 
 def test_bash_func_name():
@@ -23,3 +23,13 @@ def test_bash_func_name():
     script = get_completion_script('foo-bar baz_blah', '_COMPLETE_VAR', 'bash').strip()
     assert script.startswith('_foo_barbaz_blah_completion()')
     assert '_COMPLETE_VAR=complete $1' in script
+
+
+@pytest.mark.xfail(WIN, reason="Jupyter not tested/supported on Windows")
+def test_is_jupyter_kernel_output():
+    class JupyterKernelFakeStream(object):
+        pass
+
+    # implementation detail, aka cheapskate test
+    JupyterKernelFakeStream.__module__ = "ipykernel.faked"
+    assert not should_strip_ansi(stream=JupyterKernelFakeStream())