Repository: plotly/dash

Issue:
[Feature Request] Remove dependency on LGPL licensed `ansi2html`
**Is your feature request related to a problem? Please describe.**

Dash currently depends on [`ansi2html`](https://pypi.org/project/ansi2html/)

https://github.com/plotly/dash/blob/7527383050b6fe59a11833bbeac6f1a741436084/requires-install.txt#L13

`ansi2html` is licensed under LGPL:

- https://github.com/pycontribs/ansi2html/blob/main/LICENSE

This is not a truly permissive license, such as MIT, BSD, or Apache, which can cause headaches when distributing some private code that depends on Dash (and transitively depends on `ansi2html`).

LGPL makes it challenging for including Dash inside of a binary due to the restriction that users need to be able to have the ability to swap out the LGPL library for another of their choosing, something infeasible/very tricky for a binary distribution.

For example, this affects users of [Pyinstaller](https://www.pyinstaller.org/), [Nuitka](https://nuitka.net/), [PyOxdizer](https://pyoxidizer.readthedocs.io/en/stable/), etc. that want to package their Python code and dependencies into a stand-alone binary.

**Describe the solution you'd like**

Dash only depends on fully permissive licensed packages (such as MIT, BSD, Apache).

**Related Historical Work**

For a long time `chardet` was an LGPL package depended on by popular packages, such as `requests`:

- https://github.com/chardet/chardet/issues/36

The community has migrated to a permissively licensed package [`charset_normalizer`](https://github.com/Ousret/charset_normalizer).

One of the motivations was to ensure that the full dependency tree was permissively licensed.


Discussion:
Thanks for create this issue, @johnthagen. We'll make sure to take a deeper look into this after the holidays. 
cc @alexcjohnson 
Thanks @johnthagen! `ansi2html` is used here, to give nicely-formatted tracebacks in Jupyter:

https://github.com/plotly/dash/blob/7527383050b6fe59a11833bbeac6f1a741436084/dash/_jupyter.py#L474-L476

@T4rk1n unless you can think of a simple alternative, I'd be fine with dropping color entirely. Presumably we can get a plain ASCII traceback and display it in a `<pre>` tag or something?

Resolving patch:
diff --git a/dash/_jupyter.py b/dash/_jupyter.py
--- a/dash/_jupyter.py
+++ b/dash/_jupyter.py
@@ -5,7 +5,6 @@
 import os
 import queue
 import uuid
-import re
 import sys
 import threading
 import time
@@ -22,7 +21,6 @@
     from IPython.core.display import HTML
     from IPython.core.ultratb import FormattedTB
     from retrying import retry
-    from ansi2html import Ansi2HTMLConverter
     from ipykernel.comm import Comm
     import nest_asyncio
 
@@ -450,13 +448,12 @@ def _wrap_errors(error):
             original_formatargvalues = inspect.formatargvalues
             inspect.formatargvalues = _custom_formatargvalues
             try:
-                # Use IPython traceback formatting to build colored ANSI traceback
-                # string
+                # Use IPython traceback formatting to build the traceback string.
                 ostream = io.StringIO()
                 ipytb = FormattedTB(
                     tb_offset=skip,
                     mode="Verbose",
-                    color_scheme="Linux",
+                    color_scheme="NoColor",
                     include_vars=True,
                     ostream=ostream,
                 )
@@ -465,27 +462,12 @@ def _wrap_errors(error):
                 # Restore formatargvalues
                 inspect.formatargvalues = original_formatargvalues
 
-            # Print colored ANSI representation if requested
-            ansi_stacktrace = ostream.getvalue()
+            stacktrace = ostream.getvalue()
 
             if self.inline_exceptions:
-                print(ansi_stacktrace)
+                print(stacktrace)
 
-            # Use ansi2html to convert the colored ANSI string to HTML
-            conv = Ansi2HTMLConverter(scheme="ansi2html", dark_bg=False)
-            html_str = conv.convert(ansi_stacktrace)
-
-            # Set width to fit 75-character wide stack trace and font to a size the
-            # won't require a horizontal scroll bar
-            html_str = html_str.replace(
-                "<html>", '<html style="width: 75ch; font-size: 0.86em">'
-            )
-
-            # Remove explicit background color so Dash dev-tools can set background
-            # color
-            html_str = re.sub("background-color:[^;]+;", "", html_str)
-
-            return html_str, 500
+            return stacktrace, 500
 
     @property
     def active(self):


Test patch: