how to make a calculator 10

I have a solid calculator from chapters 1–8 and a Flask website from chapter 9. Now I want to make it beautiful and ridiculously easy to use by turning it into a modern web app with Streamlit.

Streamlit lets me build professional-looking web apps with almost no extra code.


preview

These are the tests I have by the end of the chapter

 1import unittest
 2
 3import streamlit as st
 4
 5
 6class TestCalculatorStreamlit(unittest.TestCase):
 7
 8    def test_streamlit_can_be_imported(self):
 9        """Make sure Streamlit is installed and importable"""
10        self.assertIsNotNone(st)
11
12    def test_streamlit_calculator_app(self):
13        """The main function in our Streamlit app exists and is callable"""
14        from src.streamlit_app import main
15        self.assertTrue(callable(main))
16
17    def test_calculator_error_messages_still_work(self):
18        """Our original calculator error messages are still working"""
19        import src.calculator
20
21        self.assertEqual(
22            src.calculator.divide(10, 0),
23            'brmph?! I cannot divide by 0. Try again...'
24        )
25
26        self.assertEqual(
27            src.calculator.add(None, 5),
28            'brmph?! Numbers only. Try again...'
29        )
30
31        self.assertEqual(
32            src.calculator.multiply('hello', 3),
33            'brmph?! Numbers only. Try again...'
34        )
35
36
37# Exceptions seen
38# (none new - we reuse everything from chapters 1-8)

open the project

  • I change directory to the calculator folder

    cd calculator
    
  • I install Streamlit

    uv add streamlit
    
  • I use pytest-watcher to run the tests

    uv run pytest-watcher . --now
    
  • I create a new test file for the Streamlit version

    touch tests/test_calculator_streamlit.py
    

test_streamlit_installed

RED: make it fail

I open tests/test_calculator_streamlit.py and write

1import unittest
2import streamlit as st
3
4class TestCalculatorStreamlit(unittest.TestCase):
5
6    def test_streamlit_can_be_imported(self):
7        self.assertIsNotNone(st)

the terminal shows ModuleNotFoundError


GREEN: make it pass

I already ran uv add streamlit, so the test passes.


test_streamlit_calculator_app

RED: make it fail

I add a test for the app itself

 8    def test_streamlit_can_be_imported(self):
 9        ...
10
11    def test_streamlit_calculator_app(self):
12        from src.streamlit_app import main
13        self.assertTrue(callable(main))

the terminal shows ModuleNotFoundError


GREEN: make it pass

  • I create the file

    touch src/streamlit_app.py
    
  • I open it and add the basic app

    1import streamlit as st
    2import src.calculator as calc
    3
    4def main():
    5    st.title("🧮 My TDD Calculator")
    6    st.write("Built step-by-step with Test-Driven Development!")
    7
    8if __name__ == "__main__":
    9    main()
    

The test passes.


REFACTOR: make it better

Now I build the full interactive calculator.

I update the test to also check core behavior (we already know it works from earlier chapters, but it feels good to see it here).

I expand src/streamlit_app.py:

 6  def main():
 7      st.title("🧮 My TDD Calculator")
 8      st.write("Built step-by-step with Test-Driven Development!")
 9
10      col1, col2, col3 = st.columns([3, 1, 3])
11
12      with col1:
13          first = st.number_input("First number", value=0.0, step=0.1)
14
15      with col2:
16          operation = st.selectbox(
17              "Operation",
18              ["+", "-", "×", "÷"]
19          )
20
21      with col3:
22          second = st.number_input("Second number", value=0.0, step=0.1)
23
24      if st.button("Calculate", type="primary"):
25          try:
26              ops = {
27                  "+": calc.add,
28                  "-": calc.subtract,
29                  "×": calc.multiply,
30                  "÷": calc.divide
31              }
32              result = ops[operation](first, second)
33              st.success(f"**Result:** {result}")
34
35          except ZeroDivisionError:
36              st.error("brmph?! I cannot divide by 0. Try again...")
37          except Exception:
38              st.error("brmph?! Numbers only. Try again...")
39
40      # Bonus: show history
41      if "history" not in st.session_state:
42          st.session_state.history = []
43
44      if st.button("Clear history"):
45          st.session_state.history = []
46
47      if st.session_state.history:
48          st.subheader("Recent calculations")
49          for item in reversed(st.session_state.history[-5:]):
50              st.write(item)
51
52  if __name__ == "__main__":
53      main()

The app now looks and works beautifully.


test_error_messages_in_streamlit

RED: make it fail

I add

20    def test_error_messages_in_streamlit(self):
21        self.assertEqual(
22            src.calculator.divide(10, 0),
23            'brmph?! I cannot divide by 0. Try again...'
24        )

the test already passes (thanks to chapters 3–4).


how to run the app

In the terminal I type:

uv run streamlit run src/streamlit_app.py

A browser window opens automatically with my beautiful calculator!


close the project


review

I now have three different versions of the same calculator:

  • Pure Python (chapters 1–8)

  • Flask website (chapter 9)

  • Streamlit web app (chapter 10) — the fastest and most beautiful version

The core calculator code never changed. All my tests still protect it. This is the real power of Test-Driven Development.

code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

You have completed an amazing journey from pure functions to real web applications!

You now know how to:

  • Build programs with Test-Driven Development

  • Turn them into Flask websites

  • Turn them into beautiful Streamlit apps


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too