Repository: streamlit/streamlit

Issue:
On rerun, faded elements are becoming opaque one by one. Instead they become opaque all at once.
Steps to repro:
1. Run `examples/reference.py`
2. When done, rerun it.

**Expected:** on rerun, all elements fade out and then become opaque one by one even before the run is done.
**Actual:** on rerun, all elements fade out and only become opaque when the entire run is done.

I believe this bug was introduced with the Sidebar code.


Discussion:

Resolving patch:
diff --git a/lib/streamlit/DeltaGenerator.py b/lib/streamlit/DeltaGenerator.py
--- a/lib/streamlit/DeltaGenerator.py
+++ b/lib/streamlit/DeltaGenerator.py
@@ -45,15 +45,17 @@
 MAX_DELTA_BYTES = 14 * 1024 * 1024  # 14MB
 
 
-def _wraps_with_cleaned_sig(wrapped):
-    """Simplify the function signature by removing "self" and "element".
+def _wraps_with_cleaned_sig(wrapped, num_args_to_remove):
+    """Simplify the function signature by removing arguments from it.
 
-    Removes "self" and "element" from function signature, since signatures are
-    visible in our user-facing docs and these elements make no sense to the
-    user.
+    Removes the first N arguments from function signature (where N is
+    num_args_to_remove). This is useful since function signatures are visible
+    in our user-facing docs, and many methods in DeltaGenerator have arguments
+    that users have no access to.
     """
-    # By passing (None, None), we're removing (self, element) from *args
-    fake_wrapped = functools.partial(wrapped, None, None)
+    # By passing (None, ...), we're removing (arg1, ...) from *args
+    args_to_remove = (None,) * num_args_to_remove
+    fake_wrapped = functools.partial(wrapped, *args_to_remove)
     fake_wrapped.__doc__ = wrapped.__doc__
 
     # These fields are used by wraps(), but in Python 2 partial() does not
@@ -64,35 +66,12 @@ def _wraps_with_cleaned_sig(wrapped):
     return functools.wraps(fake_wrapped)
 
 
-def _clean_up_sig(method):
-    """Cleanup function signature.
+def _remove_self_from_sig(method):
+    """Remove the `self` argument from `method`'s signature."""
 
-    This passes 'None' into the `element` argument of the wrapped function.
-
-    The reason this function exists is to allow us to use
-    `@_wraps_with_cleaned_sig` in functions like `st.dataframe`, which do not
-    take an element as input.
-
-    Contrast this with the `_with_element()` function, below, which creates an
-    actual Element proto, passes it into the function, and takes care of
-    enqueueing the element later.
-
-    So if you have some function
-        @_clean_up_sig
-        def some_function(self, unused_element_argument, stuff):
-    then the wrapped version of `some_function` can be called like this by
-    the user:
-        dg.some_function(stuff)
-
-    and its signature (introspected in st.help or IPython's `?` magic command)
-    will correctly reflect the above.  Meanwhile, when the user calls the
-    function as above, the wrapped function will be called this way:
-        dg.some_function(None, stuff)
-    """
-
-    @_wraps_with_cleaned_sig(method)
+    @_wraps_with_cleaned_sig(method, 1)  # Remove self from sig.
     def wrapped_method(self, *args, **kwargs):
-        return method(self, None, *args, **kwargs)
+        return method(self, *args, **kwargs)
 
     return wrapped_method
 
@@ -118,18 +97,18 @@ def _with_element(method):
 
     """
 
-    @_wraps_with_cleaned_sig(method)
-    def wrapped_method(self, *args, **kwargs):
+    @_wraps_with_cleaned_sig(method, 2)  # Remove self and element from sig.
+    def wrapped_method(dg, *args, **kwargs):
         def marshall_element(element):
-            return method(self, element, *args, **kwargs)
+            return method(dg, element, *args, **kwargs)
 
-        return self._enqueue_new_element_delta(marshall_element)
+        return dg._enqueue_new_element_delta(marshall_element)
 
     return wrapped_method
 
 
-def _widget(f):
-    @_wraps_with_cleaned_sig(f)
+def _widget(method):
+    @_wraps_with_cleaned_sig(method, 3)  # Remove self, element, ui_value.
     @_with_element
     def wrapper(dg, element, *args, **kwargs):
         # All of this label-parsing code only exists so we can throw a pretty
@@ -142,7 +121,7 @@ def wrapper(dg, element, *args, **kwargs):
             label = args[0]
             args = args[1:]
         else:
-            raise TypeError("%s must have a label" % f.__name__)
+            raise TypeError("%s must have a label" % method.__name__)
 
         ctx = get_report_ctx()
         # The widget ID is the widget type (i.e. the name "foo" of the
@@ -150,13 +129,13 @@ def wrapper(dg, element, *args, **kwargs):
         # This allows widgets of different types to have the same label,
         # and solves a bug where changing the widget type but keeping
         # the label could break things.
-        widget_id = "%s-%s" % (f.__name__, label)
+        widget_id = "%s-%s" % (method.__name__, label)
 
-        el = getattr(element, f.__name__)
+        el = getattr(element, method.__name__)
         el.id = widget_id
 
         ui_value = ctx.widgets.get_widget_value(widget_id) if ctx else None
-        return f(dg, element, ui_value, label, *args, **kwargs)
+        return method(dg, element, ui_value, label, *args, **kwargs)
 
     return wrapper
 
@@ -654,8 +633,8 @@ def _text_exception(self, element, exception_type, message, stack_trace):
         element.exception.message = message
         element.exception.stack_trace.extend(stack_trace)
 
-    @_clean_up_sig
-    def dataframe(self, _, data=None, width=None, height=None):
+    @_remove_self_from_sig
+    def dataframe(self, data=None, width=None, height=None):
         """Display a dataframe as an interactive table.
 
         Parameters
@@ -690,9 +669,6 @@ def dataframe(self, _, data=None, width=None, height=None):
 
         >>> st.dataframe(df, 200, 100)
 
-        .. output::
-           Same as before but width and height are constrained as specified
-
         You can also pass a Pandas Styler object to change the style of
         the rendered DataFrame:
 


Test patch:
diff --git a/lib/tests/streamlit/delta_generator_test.py b/lib/tests/streamlit/delta_generator_test.py
--- a/lib/tests/streamlit/delta_generator_test.py
+++ b/lib/tests/streamlit/delta_generator_test.py
@@ -36,7 +36,7 @@
 from streamlit.proto.BlockPath_pb2 import BlockPath
 from streamlit.DeltaGenerator import (
     _wraps_with_cleaned_sig,
-    _clean_up_sig,
+    _remove_self_from_sig,
     _with_element,
 )
 from tests import testutil
@@ -87,14 +87,9 @@ def fake_text(self, element, body):
         element.text.body = str(body)
         element.text.format = Text.PLAIN
 
-    def fake_dataframe(self, element, arg0, data=None):
-        """Fake dataframe.
-
-        In the real dataframe, element is set to _ but in reality the
-        decorator passes None in to what would be the element, so I want to
-        verify that None is indeed getting passed in.
-        """
-        return (element, arg0, data)
+    def fake_dataframe(self, arg0, data=None):
+        """Fake dataframe."""
+        return (arg0, data)
 
     def fake_text_raise_exception(self, element, body):
         """Fake text that raises exception."""
@@ -148,7 +143,8 @@ def test_sidebar_nonexistent_method(self):
                          " objects. Did you mean `st.write()`?")
 
     def test_wraps_with_cleaned_sig(self):
-        wrapped_function = _wraps_with_cleaned_sig(FakeDeltaGenerator.fake_text)
+        wrapped_function = (
+            _wraps_with_cleaned_sig(FakeDeltaGenerator.fake_text, 2))
         wrapped = wrapped_function.keywords.get("wrapped")
 
         # Check meta data.
@@ -164,12 +160,12 @@ def test_wraps_with_cleaned_sig(self):
         sig = signature(wrapped)
         self.assertEqual(str(sig), "(body)")
 
-    def test_clean_up_sig(self):
-        wrapped = _clean_up_sig(FakeDeltaGenerator.fake_dataframe)
+    def test_remove_self_from_sig(self):
+        wrapped = _remove_self_from_sig(FakeDeltaGenerator.fake_dataframe)
 
         # Verify original signature
         sig = signature(FakeDeltaGenerator.fake_dataframe)
-        self.assertEqual(str(sig), "(self, element, arg0, data=None)", str(sig))
+        self.assertEqual(str(sig), "(self, arg0, data=None)", str(sig))
 
         # Check cleaned signature.
         # On python2 it looks like: '(self, *args, **kwargs)'
@@ -180,7 +176,7 @@ def test_clean_up_sig(self):
         # Check cleaned output.
         dg = FakeDeltaGenerator()
         result = wrapped(dg, "foo", data="bar")
-        self.assertEqual(result, (None, "foo", "bar"))
+        self.assertEqual(result, ("foo", "bar"))
 
     def test_with_element(self):
         wrapped = _with_element(FakeDeltaGenerator.fake_text)