{"id":1383,"date":"2026-03-21T16:51:26","date_gmt":"2026-03-21T15:51:26","guid":{"rendered":"https:\/\/programmier-workshops.de\/blog\/?page_id=1383"},"modified":"2026-03-21T16:55:13","modified_gmt":"2026-03-21T15:55:13","slug":"streamlit","status":"publish","type":"page","link":"https:\/\/programmier-workshops.de\/blog\/streamlit\/","title":{"rendered":"Streamlit &#8211; Building Reliable Streamlit Applications"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#1_The_Script_Executes_Top-to-Bottom_on_Every_Interaction\" >1. The Script Executes Top-to-Bottom on Every Interaction<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#2_Cache_Any_Expensive_Operation\" >2. Cache Any Expensive Operation<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Cache_Types\" >Cache Types<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#3_Externalize_Secrets\" >3. Externalize Secrets<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Required_setup\" >Required setup<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#4_Treat_Missing_Data_as_the_Default_Case\" >4. Treat Missing Data as the Default Case<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Apply_consistently\" >Apply consistently<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#5_Use_Forms_to_Control_Execution_Timing\" >5. Use Forms to Control Execution Timing<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#6_Assign_Explicit_Keys_to_Dynamic_Widgets\" >6. Assign Explicit Keys to Dynamic Widgets<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Two_access_patterns\" >Two access patterns<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#7_Provide_Feedback_for_Long-Running_Operations\" >7. Provide Feedback for Long-Running Operations<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Streaming_responses_LLMs\" >Streaming responses (LLMs)<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#8_Use_Fragments_to_Isolate_Reruns\" >8. Use Fragments to Isolate Reruns<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#9_Enforce_Project_Structure_Early\" >9. Enforce Project Structure Early<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Key_constraints\" >Key constraints<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#10_Prefer_Built-in_Components_Over_Custom_Implementations\" >10. Prefer Built-in Components Over Custom Implementations<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Selection_hierarchy\" >Selection hierarchy<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/programmier-workshops.de\/blog\/streamlit\/#Summary\" >Summary<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"1_The_Script_Executes_Top-to-Bottom_on_Every_Interaction\"><\/span>1. The Script Executes Top-to-Bottom on Every Interaction<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Streamlit does not update incrementally. Any interaction\u2014button, slider, input\u2014causes a full script execution.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c State resets every time\ncounter = 0\nif st.button(\"Add\"):\n    counter += 1\nst.write(counter)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Persist state explicitly\nif \"counter\" not in st.session_state:\n    st.session_state.counter = 0\n\nif st.button(\"Add\"):\n    st.session_state.counter += 1\n\nst.write(st.session_state.counter)\n<\/pre>\n\n\n\n<p><strong>Implication:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Variables are ephemeral unless persisted<\/li>\n\n\n\n<li>UI actions are not incremental events<\/li>\n\n\n\n<li>The script is effectively stateless unless you define state<\/li>\n<\/ul>\n\n\n\n<p><strong>Mechanism:<\/strong> <code>st.session_state<\/code><\/p>\n\n\n\n<p>Use it for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Counters<\/li>\n\n\n\n<li>Multi-step flows<\/li>\n\n\n\n<li>Chat history<\/li>\n\n\n\n<li>User selections<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"2_Cache_Any_Expensive_Operation\"><\/span>2. Cache Any Expensive Operation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Given the full rerun model, any non-trivial computation will repeat unless explicitly cached.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c Re-executes every time\ndata = pd.read_csv(\"large_file.csv\")\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Cached execution\n@st.cache_data\ndef load_data():\n    return pd.read_csv(\"large_file.csv\")\n\ndata = load_data()\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Cache_Types\"><\/span>Cache Types<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>@st.cache_data<\/code> \u2192 returns copies (DataFrames, API responses)<\/li>\n\n\n\n<li><code>@st.cache_resource<\/code> \u2192 returns shared objects (models, DB connections)<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@st.cache_data(ttl=300)\ndef fetch_data():\n    return api_call()\n<\/pre>\n\n\n\n<p><strong>Effect:<\/strong> avoids redundant computation across reruns.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"3_Externalize_Secrets\"><\/span>3. Externalize Secrets<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Hardcoding credentials introduces immediate security risk.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c Unsafe\napi_key = \"sk-xxxx\"\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Correct approach\napi_key = st.secrets[\"openai\"][\"api_key\"]\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Required_setup\"><\/span>Required setup<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>.streamlit\/secrets.toml<\/code><\/li>\n\n\n\n<li>Add to <code>.gitignore<\/code><\/li>\n\n\n\n<li>Use platform secret management in deployment<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"4_Treat_Missing_Data_as_the_Default_Case\"><\/span>4. Treat Missing Data as the Default Case<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Streamlit apps frequently start in an empty state. Code must handle that condition explicitly.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">uploaded = st.file_uploader(\"Upload\")\n\nif uploaded is not None:\n    df = pd.read_csv(uploaded)\n    st.dataframe(df)\nelse:\n    st.info(\"Upload a file to proceed\")\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Apply_consistently\"><\/span>Apply consistently<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>File inputs<\/li>\n\n\n\n<li>API responses<\/li>\n\n\n\n<li>Filtered datasets<\/li>\n\n\n\n<li>Dynamic option lists<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">options = [x for x in items if x.startswith(\"A\")]\n\nif options:\n    st.selectbox(\"Choose\", options)\nelse:\n    st.warning(\"No results available\")\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"5_Use_Forms_to_Control_Execution_Timing\"><\/span>5. Use Forms to Control Execution Timing<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Without forms, each input triggers a rerun independently.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c Fragmented execution\nname = st.text_input(\"Name\")\nemail = st.text_input(\"Email\")\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Controlled submission\nwith st.form(\"user_form\"):\n    name = st.text_input(\"Name\")\n    email = st.text_input(\"Email\")\n    submitted = st.form_submit_button(\"Submit\")\n\nif submitted:\n    if name and email:\n        process(name, email)\n    else:\n        st.error(\"Missing input\")\n<\/pre>\n\n\n\n<p><strong>Use when:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Multiple dependent inputs<\/li>\n\n\n\n<li>Data entry workflows<\/li>\n\n\n\n<li>Validation is required before processing<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"6_Assign_Explicit_Keys_to_Dynamic_Widgets\"><\/span>6. Assign Explicit Keys to Dynamic Widgets<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Streamlit identifies widgets by structure. In loops, this leads to collisions.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c Ambiguous widget identity\nfor item in items:\n    st.checkbox(item)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Unique identifiers\nfor i, item in enumerate(items):\n    st.checkbox(item, key=f\"checkbox_{i}\")\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Two_access_patterns\"><\/span>Two access patterns<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">value = st.slider(\"Temperature\", 0, 100, 50)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">st.slider(\"Humidity\", 0, 100, 50, key=\"humidity\")\nst.write(st.session_state.humidity)\n<\/pre>\n\n\n\n<p><strong>Rule:<\/strong> Any dynamically generated widget requires a deterministic key.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"7_Provide_Feedback_for_Long-Running_Operations\"><\/span>7. Provide Feedback for Long-Running Operations<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Unresponsive UI is interpreted as failure.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c No feedback\nresult = slow_call()\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Spinner\nwith st.spinner(\"Processing...\"):\n    result = slow_call()\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Multi-step status\nwith st.status(\"Running...\", expanded=True) as status:\n    st.write(\"Loading data...\")\n    data = load()\n\n    st.write(\"Processing...\")\n    output = compute(data)\n\n    status.update(label=\"Done\", state=\"complete\")\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Streaming_responses_LLMs\"><\/span>Streaming responses (LLMs)<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">st.write_stream(stream)\n<\/pre>\n\n\n\n<p><strong>Objective:<\/strong> maintain user trust during latency.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"8_Use_Fragments_to_Isolate_Reruns\"><\/span>8. Use Fragments to Isolate Reruns<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Full reruns can be inefficient when only part of the UI changes.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@st.fragment\ndef counter():\n    if st.button(\"Increment\"):\n        st.session_state.count = st.session_state.get(\"count\", 0) + 1\n    st.write(st.session_state.get(\"count\", 0))\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">plot_heavy_chart(data)\ncounter()\n<\/pre>\n\n\n\n<p><strong>Effect:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Expensive components remain static<\/li>\n\n\n\n<li>Interactive sections update independently<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"9_Enforce_Project_Structure_Early\"><\/span>9. Enforce Project Structure Early<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Unstructured scripts become unmanageable quickly.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">my_app\/\n\u251c\u2500\u2500 app.py\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 .streamlit\/\n\u2502   \u251c\u2500\u2500 config.toml\n\u2502   \u2514\u2500\u2500 secrets.toml\n\u251c\u2500\u2500 pages\/\n\u251c\u2500\u2500 utils\/\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Key_constraints\"><\/span>Key constraints<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Pin dependencies (<code>pandas==2.1.0<\/code>)<\/li>\n\n\n\n<li>Use relative paths<\/li>\n\n\n\n<li>Separate logic from UI<\/li>\n<\/ul>\n\n\n\n<p><strong>Result:<\/strong> reproducible deployments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"10_Prefer_Built-in_Components_Over_Custom_Implementations\"><\/span>10. Prefer Built-in Components Over Custom Implementations<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Streamlit provides native solutions for most UI needs.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Requirement<\/th><th>Built-in Feature<\/th><\/tr><\/thead><tbody><tr><td>Loading feedback<\/td><td><code>st.spinner()<\/code><\/td><\/tr><tr><td>KPIs<\/td><td><code>st.metric()<\/code><\/td><\/tr><tr><td>Layout<\/td><td><code>st.columns()<\/code>, <code>st.tabs()<\/code><\/td><\/tr><tr><td>Progress<\/td><td><code>st.status()<\/code><\/td><\/tr><tr><td>Chat UI<\/td><td><code>st.chat_message()<\/code><\/td><\/tr><tr><td>Streaming<\/td><td><code>st.write_stream()<\/code><\/td><\/tr><tr><td>Partial updates<\/td><td><code>@st.fragment<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c Manual formatting\nst.write(f\"Revenue: ${revenue}\")\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u2705 Native component\nst.metric(\"Revenue\", f\"${revenue}\", f\"{delta:+.1%}\")\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Selection_hierarchy\"><\/span>Selection hierarchy<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Built-in<\/li>\n\n\n\n<li>Official extensions<\/li>\n\n\n\n<li>Third-party components<\/li>\n\n\n\n<li>Custom implementation<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Summary\"><\/span>Summary<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Streamlit\u2019s behavior is deterministic once its execution model is understood:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Full script reruns drive everything<\/li>\n\n\n\n<li>State must be explicitly persisted<\/li>\n\n\n\n<li>Performance depends on caching<\/li>\n\n\n\n<li>UI stability depends on controlled execution<\/li>\n<\/ul>\n\n\n\n<p>These rules eliminate the majority of non-obvious failures and enable predictable application behavior.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. The Script Executes Top-to-Bottom on Every Interaction Streamlit does not update incrementally. Any interaction\u2014button, slider, input\u2014causes a full script execution. Implication: Mechanism: st.session_state Use it for: 2. Cache Any Expensive Operation Given the full rerun model, any non-trivial computation will repeat unless explicitly cached.&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1383","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/pages\/1383","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/comments?post=1383"}],"version-history":[{"count":2,"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/pages\/1383\/revisions"}],"predecessor-version":[{"id":1395,"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/pages\/1383\/revisions\/1395"}],"wp:attachment":[{"href":"https:\/\/programmier-workshops.de\/blog\/wp-json\/wp\/v2\/media?parent=1383"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}