Repository: streamlit/streamlit
Pull request: Adding experimental_rerun
**Issue:**

Fixes https://github.com/streamlit/streamlit/issues/653

**Description:** 

Added `st.experimental_rerun` and `st.experimental_get_session_id`. Given that rerun needed access to the server I chose to make the relevant test be within the e2e testing suite. This e2e test undergoes a few reruns within a thread, and then a couple more just within the main streamlit instance.

Problem statement:
Add the ability to programatically re-run the streamlit script
### Problem

I am building a small labeling too where I read in an example, the user will select labels from a list of check boxes and click submit in order to save the labels, when they submit a field in the data for it being labeled is added so in the next read it skips that example. The problem is that the submit button will cause a rerun where the labels are collected and saved but it doesn't cause another re-run that will cause the data to be reread (this will move the app on to the next question)

### Solution

I think a solution would be a `st.rerun()` function that you can call to reexecute the script from the top. In my cause this would be called after my save and trigger a re-read. Basically it would do the same thing I am currently using a second `Next` button for but remove the need for user interaction.

Hints from issue discussion:
Hey @blester125 

We've been thinking a lot about different ways to solve issues like yours recently. We have a few really nice and clean solutions in mind but they will take a little time to implement.

In the meantime, if I understand your app correctly, I think what you need can be implemented by persisting some data between reruns of your script, via a feature we call _Session State_. 

The prototype for Session State can be downloaded from [this Gist](https://gist.githubusercontent.com/tvst/036da038ab3e999a64497f42de966a92/raw/36e810364f34b25e270bd856cb25611775e691e3/SessionState.py).

After you download it, you can create a labeling app this way:

```py
import streamlit as st
import SessionState

s = SessionState.get(current_file_index = 0)

# This is a function that loads your file, given the file index.
f = get_file_to_label(s.current_file_index)

# These are your labels.
label1 = st.checkbox("foo")
label2 = st.checkbox("bar")
label2 = st.checkbox("baz")

if st.button("submit"):
  # This is a function that saves labels, given a file index.
  save_labels(s.current_file_index, label1, label2, label3)

  s.current_file_index += 1
```

That said, it's very possible I misunderstood your app and its requirements. So if you _really_ need to have some way to rerun apps you can actually do it today by delving into Streamlit's internals:

```py
from streamlit.ScriptRunner import StopException, RerunException

# do stuff

if st.button():
  # do other stuff
  raise RerunException()  # This causes the app to rerun

# even more stuff
```

You can also use `StopException` to stop the execution at any point.
I'm confused by the behaviour on any button/checkbox click. I want to click on multiple checkboxes  and then hit a submit button.

When I try to implement your first example below I can never click on the submit button because as soon as I change the first checkbox the submit button disappears and returns me to my initial setup.  This is a problem because I want to label multiple items at a time, but I can only ever do one.

Adding in StopExceptions doesn't seem to help either

```
        label1 = st.checkbox("Relevant",False)
        label2 = st.checkbox("Not relevant",False)
        if label1:
            labelled.loc[row,'relevance'] == 1
            raise StopException()
        if label2:
            labelled.loc[row,'relevance'] == 0
            raise StopException()
        if st.button('submit'):
            labelled = tm.addToTrainValidation(labelled.loc[[row]],train,valid,unlabelled)
```
Any suggestions?
Duplicate of #166 I guess
@tvst Thanks for providing a workaround while the permanent functionality is being worked out.
However, by placing the RerunException as indicated, it seems that I get an error:

```
TypeError: __init__() missing 1 required positional argument: 'rerun_data'
Traceback:
  File "c:\users\fabien~1.val\trial1~1\venv~1\lib\site-packages\streamlit\ScriptRunner.py", line 311, in _run_script
    exec(code, module.__dict__)
  File "C:\Users\Fab\trial 1\streamlit_gui.py", line 110, in <module>
    raise RerunException()
```

What data should I provide to this exception?
Thanks
> @tvst Thanks for providing a workaround while the permanent functionality is being worked out.
> However, by placing the RerunException as indicated, it seems that I get an error:
> 
> ```
> TypeError: __init__() missing 1 required positional argument: 'rerun_data'
> Traceback:
>   File "c:\users\fabien~1.val\trial1~1\venv~1\lib\site-packages\streamlit\ScriptRunner.py", line 311, in _run_script
>     exec(code, module.__dict__)
>   File "C:\Users\Fab\trial 1\streamlit_gui.py", line 110, in <module>
>     raise RerunException()
> ```
> 
> What data should I provide to this exception?
> Thanks

Oh, sorry, I just found your [gist](https://gist.github.com/tvst/ef477845ac86962fa4c92ec6a72bb5bd) that do not raise the error. (however, values of gui elements are reseted to default and do not match the UI it seems)
This issue is already open for quite some time. Could someone on the team elaborate on the time scale on which this will be implemented if ever?
Also the above mentioned [gist](https://gist.github.com/tvst/ef477845ac86962fa4c92ec6a72bb5bd) seems to be not working anymore in more recent versions (is working for me under 0.53.0, but not 0.56.0 with an error complaining about `_main_dg` not being a member of `session`. So probably some internal changes broke this, would be nice, if someone could update the gist accordingly.

Thank you for this amazing project anyway, became quite fond of it.
Hi @DavidS3141 , here's the updated gist, https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92

Regarding the triage of this issue, will get back to you shortly!
Sorry, I was not clear. I really need the rerun functionality, as the `SessionState` workaround is not viable for me. Maybe take another look at the gist I linked ([here](https://gist.github.com/tvst/ef477845ac86962fa4c92ec6a72bb5bd) again) to make sure what I mean. Thanks!
Okay, I looked at your linked gist again and understood why you linked it, as it shows how to fix the `_main_dg` issue! I fixed the rerun logic accordingly and the resulting fork can be found [here](https://gist.github.com/DavidS3141/822310a2ed9f600362ac31ee49223f86).
A neater fix seems to be the following:

```python
import streamtlit as st

def rerun():
    raise st.ScriptRunner.RerunException(st.ScriptRequestQueue.RerunData(None))
```

Worked this out from the comment written over at:

```python
# Data attached to RERUN requests
RerunData = namedtuple(
    "RerunData",
    [
        # WidgetStates protobuf to run the script with. If this is None, the
        # widget_state from the most recent run of the script will be used instead.
        "widget_state"
    ],
)
```

Meaning, passing `None` to `RerunData` defaults to no change in state.

Commit message: ignore rerun within integration tests

Patch to review:
diff --git a/e2e/scripts/st_experimental_rerun.py b/e2e/scripts/st_experimental_rerun.py
new file mode 100644
index 000000000000..baf2719c10aa
--- /dev/null
+++ b/e2e/scripts/st_experimental_rerun.py
@@ -0,0 +1,30 @@
+# Copyright 2018-2020 Streamlit Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import streamlit as st
+
+
+@st.cache(allow_output_mutation=True)
+def rerun_record():
+    return [0]
+
+
+count = rerun_record()
+count[0] += 1
+
+if count[0] < 4:
+    st.experimental_rerun()
+
+if count[0] >= 4:
+    st.text("Being able to rerun a session is awesome!")
diff --git a/e2e/specs/st_experimental_rerun.spec.ts b/e2e/specs/st_experimental_rerun.spec.ts
new file mode 100644
index 000000000000..9638d4553199
--- /dev/null
+++ b/e2e/specs/st_experimental_rerun.spec.ts
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright 2018-2020 Streamlit Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/// <reference types="cypress" />
+
+describe("st.experimental_rerun", () => {
+  before(() => {
+    cy.visit("http://localhost:3000/");
+  });
+
+  it("works within threads", () => {
+    cy.get(".element-container .stText").should(
+      "contain",
+      "Being able to rerun a session is awesome!"
+    );
+  });
+});
diff --git a/lib/streamlit/__init__.py b/lib/streamlit/__init__.py
index d60c766f37ed..91d245ac145a 100644
--- a/lib/streamlit/__init__.py
+++ b/lib/streamlit/__init__.py
@@ -76,6 +76,8 @@
 from streamlit.report_thread import add_report_ctx as _add_report_ctx
 from streamlit.report_thread import get_report_ctx as _get_report_ctx
 from streamlit.script_runner import StopException
+from streamlit.script_runner import RerunException as _RerunException
+from streamlit.script_request_queue import RerunData as _RerunData
 from streamlit.errors import StreamlitAPIException
 from streamlit.proto import BlockPath_pb2 as _BlockPath_pb2
 from streamlit.proto import ForwardMsg_pb2 as _ForwardMsg_pb2
@@ -507,3 +509,12 @@ def stop():
 
     """
     raise StopException()
+
+
+def experimental_rerun():
+    """Reruns the streamlit application.
+
+    When run outside of Streamlit this will raise an Exception.
+    """
+
+    raise _RerunException(_RerunData(None))
diff --git a/scripts/run_bare_integration_tests.py b/scripts/run_bare_integration_tests.py
index abb0d95394e2..df267e7515ba 100755
--- a/scripts/run_bare_integration_tests.py
+++ b/scripts/run_bare_integration_tests.py
@@ -36,6 +36,10 @@
 
 EXCLUDED_FILENAMES = set()  # type: Set[str]
 
+# st_experimental_rerun.py calls st.experimental_rerun which raises a
+# RerunException when called within plain Python.
+EXCLUDED_FILENAMES.add("st_experimental_rerun.py")
+
 # Since there is not DISPLAY set (and since Streamlit is not actually running
 # and fixing Matplotlib in these tests), we set the MPL backend to something
 # that doesn't require a display.


Code review comments:
diff_hunk: @@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright 2018-2020 Streamlit Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/// <reference types="cypress" />
+
+describe("st.experimental_rerun", () => {
+  before(() => {
+    cy.visit("http://localhost:3000/");
+  });
+
+  it("works within threads", () => {
path: e2e/specs/st_experimental_rerun.spec.ts
text: @user1:
I think this test is misnamed now, no?

@author:
Yup :). Thanks.

diff_hunk: @@ -507,3 +509,12 @@ def stop():
 
     """
     raise StopException()
+
+
+def experimental_rerun():
+    """Reruns the streamlit application.
+
+    When run outside of Streamlit this will raise an Exception.
+    """
path: lib/streamlit/__init__.py
text: @user1:
Can we change this to:

```
    """Rerun the script immediately.

    When `st.experimental_rerun` is called, the script is halted - no more
    statements will be run, and the script will be queued to re-run from the
    top.

    If this function is called outside of Streamlit, it will raise an
    Exception.
    """
```

@author:
Can do. Thanks :).