~/wiki

Streamlit

Mis à jour le 2026-08-07Confiance : high
streamlitpythondemouidashboardprototypage

Interface web en Python pur. Le meilleur rapport temps/effet pour une démo client interne.

Le modèle d'exécution, à comprendre en premier

Le script entier est réexécuté de haut en bas à chaque interaction. Pas de callbacks, pas de composants. C'est ce qui rend Streamlit simple et ce qui provoque toutes ses surprises.

Deux conséquences : tout état doit vivre dans st.session_state, et tout calcul coûteux doit être mis en cache.

pip install streamlit
streamlit run app.py
  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://192.168.1.24:8501

Afficher

import streamlit as st

st.title("Test d'accessibilité produit")
st.header("Résultats")
st.subheader("Par site")
st.write("Accepte à peu près tout : texte, DataFrame, figure, dict")
st.markdown("**gras**, `code`, [lien](https://example.com)")
st.code("requests.post(url, json=payload)", language="python")
st.json({"findable": True, "clicks": 3})
st.dataframe(df)            # interactif, triable
st.table(df.head())         # statique
st.metric("Clics moyens", 3.2, delta=-0.4)

Saisir

site = st.text_input("Site", value="versace.com")
produit = st.text_area("Produit recherché")
budget = st.slider("Budget d'étapes", 10, 200, 60)
n = st.number_input("Répétitions", min_value=1, max_value=10, value=3)
mode = st.selectbox("Mode", ["fast", "default"])
tags = st.multiselect("Tests", ["navigation", "panier", "checkout"])
strict = st.checkbox("Mode strict")
fichier = st.file_uploader("CSV", type=["csv"])

if st.button("Lancer"):
    ...

Chaque widget renvoie sa valeur courante. Le if st.button(...) n'est vrai que sur le rerun déclenché par le clic.

Mise en page

col1, col2, col3 = st.columns(3)
with col1:
    st.metric("Sessions", 12)

with st.sidebar:
    api_key = st.text_input("Clé API", type="password")

tab1, tab2 = st.tabs(["Résultats", "Logs"])
with tab1:
    st.dataframe(df)

with st.expander("Détails techniques"):
    st.code(trace)

with st.container():
    st.write("bloc regroupé")

État

if "runs" not in st.session_state:
    st.session_state.runs = []

if st.button("Ajouter"):
    st.session_state.runs.append({"site": site})

st.write(f"{len(st.session_state.runs)} exécutions")

Sans session_state, la liste serait recréée vide à chaque interaction.

Cache

@st.cache_data                     # pour des données : DataFrame, JSON, réponses API
def charger(path: str):
    return pd.read_csv(path)

@st.cache_resource                 # pour des objets vivants : client, modèle, connexion
def get_client():
    return SomeClient(api_key=os.environ["API_KEY"])

La distinction compte : cache_data sérialise et renvoie une copie, cache_resource renvoie le même objet partagé entre sessions. Un client HTTP dans cache_data casse.

Retour visuel pendant un traitement long

with st.spinner("Session en cours…"):
    result = run_agent(site, produit)

bar = st.progress(0)
for i, test in enumerate(tests):
    run(test)
    bar.progress((i + 1) / len(tests))

st.success("Terminé")
st.warning("2 tests non concluants")
st.error("Clé API invalide")

with st.status("Navigation…", expanded=True) as s:
    st.write("Ouverture du site")
    st.write("Recherche du produit")
    s.update(label="Terminé", state="complete")

Streaming d'un LLM

def tokens():
    for chunk in client.stream(prompt):
        yield chunk.text

st.write_stream(tokens)

Pour un chat complet, st.chat_message("user" | "assistant") et st.chat_input().

Secrets et configuration

.streamlit/secrets.toml :

API_KEY = "sk-..."
key = st.secrets["API_KEY"]

.streamlit/config.toml pour le thème et le port :

[server]
port = 8501
[theme]
base = "light"

Limites à connaître avant de s'engager

Pas de routing multi-pages fin (juste un dossier pages/), peu de contrôle sur le CSS, et le modèle de rerun devient pénible dès qu'on veut une vraie interactivité. Au-delà de la démo, passer à Next.js avec une API FastAPI derrière.

Déploiement : Streamlit Community Cloud gratuit depuis un repo GitHub, ou un conteneur Docker n'importe où. Pas sur Vercel — Vercel ne fait pas tourner de processus Python long.

See also