How to implement recursive functions within streamlit scripts?

I want to build a spell bee app on Streamlit.
It will play a word when a button is clicked, wait for the word to be spelled in a text box, and then play another word and repeat the process as long as the answers are correct.
I wanted to have a recursive function that will call itself if the answer is correct, but due to Streamlit’s behaviour of rerunning the entire script when a button is pressed, I am having troubles implementing the functionality.

Here is the python script which works perfectly as a CLI app:

import json
import random
import os
from pprint import PrettyPrinter
from PyDictionary import PyDictionary
from pathlib import Path

from gtts import gTTS
from playsound import playsound

pp = PrettyPrinter(compact=True, sort_dicts=True, width=100)
dictionary = PyDictionary()


def play(word: str, slow: bool = False) -> None:
    """Pronounces `word`

    Args:
        word (str): Word to be pronounced
        slow (bool, optional): Pronounces `word` slowly. Defaults to False.
    """

    t1 = gTTS(word, slow=slow)
    if os.path.exists("audio.mp3"):
        os.remove("audio.mp3")
    t1.save("audio.mp3")
    return playsound("audio.mp3")


# --------- RECURSIVE FUNCTION ---------
def recurse(words: list, score: int = 0) -> None:
    """Recursive scoring function.

    Args:
        words (list): List of words to choose from
        score (int, optional): Intermediate score. Defaults to 0.

    """

    word = random.choice(words)
    words.remove(word)
    play(word)

    answer = (
        input(
            'Enter spelling (type "define" to get the definition, or "repeat" to repeat the pronounciation slowly): '
        )
        .lower()
        .strip()
    )

    while answer in ["define", "repeat"]:

        if answer == "define":
            if meaning := dictionary.meaning(word, disable_errors=True):
                pp.pprint(meaning)
            else:
                print("Definition not available")
        else:
            play(word, slow=True)
        answer = (
            input('Enter spelling (type "repeat" to repeat slowly): ').lower().strip()
        )

    if answer == word:
        score += 1
        print(f"Correct! Score: {score}")
        recurse(words, score)
    else:
        print(f'Wrong answer. The correct answer is "{word}"\nFinal score: {score}')


# ---------- RUN ---------

with open("words.txt", "r") as f:
    words = json.load(f)

recurse(words)

Below is a barebones implementation of the above in Streamlit:

import json
import random
import os
from pprint import PrettyPrinter
from pathlib import Path
import sys

from gtts import gTTS
from playsound import playsound

pp = PrettyPrinter(compact=True, sort_dicts=True, width=100)

import streamlit as st

# --- PLAY WORD ----
def play(word: str, slow: bool = False) -> None:
    """Pronounces `word`

    Args:
        word (str): Word to be pronounced
        slow (bool, optional): Pronounces `word` slowly. Defaults to False.
    """

    t1 = gTTS(word, slow=slow)
    if os.path.exists("audio.mp3"):
        os.remove("audio.mp3")
    t1.save("audio.mp3")
    return playsound("audio.mp3")


# --------- RECURSIVE FUNCTION ---------
def recurse(words: list, score: int = 0) -> None:
    """Recursive scoring function.

    Args:
        words (list): List of words to choose from
        score (int, optional): Intermediate score. Defaults to 0.
    """

    word = random.choice(words)
    words.remove(word)
    play(word)
    st.write(word)
    if answer := st.text_input("Enter spelling: ").lower().strip():
        if answer == word:
            score += 1
            st.success(f"Correct! Score: {score}")
            recurse(words, score)
        else:
            st.error(
                f'Wrong answer. The correct answer is "{word}"\nFinal score: {score}'
            )
            sys.exit()


# ---------- RUN ---------
import contextlib

with open("words.txt", "r") as f:
    words = json.load(f)

with contextlib.suppress(SystemExit):
    if st.button("Hear word"):
        recurse(words)

As soon as text is entered in the text box, the whole app resets.

Can this even be implemented in Streamlit? I tried using session state to save words already displayed to have an idea of the current state, but no luck.

Thanks in advance!

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.