Repository: streamlit/streamlit

Issue:
StreamlitAPIException : set_page_config() can only be called once per app, and must be called as the first Streamlit command in your script.
I am facing this issue when trying to set page config in a multi-page app. However, I have an idea of setting the default layout to wide which will solve my issue but how to do so?



Discussion:
I was facing a similar issue, 
Below solved the issue is my case:

in your `main.py` file, add the page config to the state object.

```python
import streamlit as st

from SessionState import _get_state


state = _get_state()

state.page_config = st.set_page_config(
    page_title="BPJV SI Database Manager test",
    layout="wide",
    initial_sidebar_state="expanded",
)

# rest of the code

state.sync()
```
for state object see [this](https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92) gist 
i am facing this issue when use streamlit official state (0.84) for multi page
Hey @afblaze94 and @muhammadAgfian96 ! Thanks for reaching out to us. I am not sure I understand the problem. Are you asking to be able to set different configs based on the page?
I have the same issue. Actually, it is because my callbacks are executed at the beginning of the script and so, st_page_config is no longer the first Streamlit instruction. As described, st_page_config must be the very first st instruction but it's not always possible when callbacks contain st codes.
Totally understand. I have put this in front of our Product team to take a look.

Resolving patch:
diff --git a/e2e/scripts/st_set_page_config.py b/e2e/scripts/st_set_page_config.py
--- a/e2e/scripts/st_set_page_config.py
+++ b/e2e/scripts/st_set_page_config.py
@@ -22,3 +22,41 @@
 )
 st.sidebar.button("Sidebar!")
 st.markdown("Main!")
+
+
+def show_balloons():
+    st.balloons()
+
+
+st.button("Balloons", on_click=show_balloons)
+
+
+def double_set_page_config():
+    st.set_page_config(
+        page_title="Change 1",
+        page_icon=":shark:",
+        layout="wide",
+        initial_sidebar_state="collapsed",
+    )
+
+    st.set_page_config(
+        page_title="Change 2",
+        page_icon=":shark:",
+        layout="wide",
+        initial_sidebar_state="collapsed",
+    )
+
+
+st.button("Double Set Page Config", on_click=double_set_page_config)
+
+
+def single_set_page_config():
+    st.set_page_config(
+        page_title="Change 3",
+        page_icon=":shark:",
+        layout="wide",
+        initial_sidebar_state="collapsed",
+    )
+
+
+st.button("Single Set Page Config", on_click=single_set_page_config)
diff --git a/lib/streamlit/report_thread.py b/lib/streamlit/report_thread.py
--- a/lib/streamlit/report_thread.py
+++ b/lib/streamlit/report_thread.py
@@ -73,6 +73,7 @@ def __init__(
         self.uploaded_file_mgr = uploaded_file_mgr
         # set_page_config is allowed at most once, as the very first st.command
         self._set_page_config_allowed = True
+        self._has_script_started = False
         # Stack of DGs used for the with block. The current one is at the end.
         self.dg_stack: List["streamlit.delta_generator.DeltaGenerator"] = []
 
@@ -86,6 +87,10 @@ def reset(self, query_string: str = "") -> None:
         self.query_string = query_string
         # Permit set_page_config when the ReportContext is reused on a rerun
         self._set_page_config_allowed = True
+        self._has_script_started = False
+
+    def on_script_start(self) -> None:
+        self._has_script_started = True
 
     def enqueue(self, msg: ForwardMsg) -> None:
         if msg.HasField("page_config_changed") and not self._set_page_config_allowed:
@@ -96,7 +101,12 @@ def enqueue(self, msg: ForwardMsg) -> None:
                 + "(https://docs.streamlit.io/en/stable/api.html#streamlit.set_page_config)."
             )
 
-        if msg.HasField("delta") or msg.HasField("page_config_changed"):
+        # We want to disallow set_page config if one of the following occurs:
+        # - set_page_config was called on this message
+        # - The script has already started and a different st call occurs (a delta)
+        if msg.HasField("page_config_changed") or (
+            msg.HasField("delta") and self._has_script_started
+        ):
             self._set_page_config_allowed = False
 
         self._enqueue(msg)
diff --git a/lib/streamlit/script_runner.py b/lib/streamlit/script_runner.py
--- a/lib/streamlit/script_runner.py
+++ b/lib/streamlit/script_runner.py
@@ -346,6 +346,7 @@ def _run_script(self, rerun_data):
 
                     self._session_state.call_callbacks()
 
+                ctx.on_script_start()
                 exec(code, module.__dict__)
 
         except RerunException as e:


Test patch:
diff --git a/lib/tests/streamlit/report_context_test.py b/lib/tests/streamlit/report_context_test.py
--- a/lib/tests/streamlit/report_context_test.py
+++ b/lib/tests/streamlit/report_context_test.py
@@ -42,7 +42,8 @@ def test_set_page_config_immutable(self):
             ctx.enqueue(msg)
 
     def test_set_page_config_first(self):
-        """st.set_page_config must be called before other st commands"""
+        """st.set_page_config must be called before other st commands
+        when the script has been marked as started"""
 
         fake_enqueue = lambda msg: None
         ctx = ReportContext(
@@ -53,6 +54,8 @@ def test_set_page_config_first(self):
             UploadedFileManager(),
         )
 
+        ctx.on_script_start()
+
         markdown_msg = ForwardMsg()
         markdown_msg.delta.new_element.markdown.body = "foo"
 
@@ -63,6 +66,29 @@ def test_set_page_config_first(self):
         with self.assertRaises(StreamlitAPIException):
             ctx.enqueue(msg)
 
+    def test_disallow_set_page_config_twice(self):
+        """st.set_page_config cannot be called twice"""
+
+        fake_enqueue = lambda msg: None
+        ctx = ReportContext(
+            "TestSessionID",
+            fake_enqueue,
+            "",
+            SessionState(),
+            UploadedFileManager(),
+        )
+
+        ctx.on_script_start()
+
+        msg = ForwardMsg()
+        msg.page_config_changed.title = "foo"
+        ctx.enqueue(msg)
+
+        with self.assertRaises(StreamlitAPIException):
+            same_msg = ForwardMsg()
+            same_msg.page_config_changed.title = "bar"
+            ctx.enqueue(same_msg)
+
     def test_set_page_config_reset(self):
         """st.set_page_config should be allowed after a rerun"""
 
@@ -75,12 +101,15 @@ def test_set_page_config_reset(self):
             UploadedFileManager(),
         )
 
+        ctx.on_script_start()
+
         msg = ForwardMsg()
         msg.page_config_changed.title = "foo"
 
         ctx.enqueue(msg)
         ctx.reset()
         try:
+            ctx.on_script_start()
             ctx.enqueue(msg)
         except StreamlitAPIException:
             self.fail("set_page_config should have succeeded after reset!")