how to make a calculator 10: part 3

I want the numbers to stay when I press a button so that I can make numbers that have more than one digit.


preview

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

  1import random
  2import streamlit.testing.v1
  3import tests.test_calculator
  4import unittest
  5
  6
  7class TestStreamlitCalculator(unittest.TestCase):
  8
  9    def press_button(self, key):
 10        self.tester.button(key).click().run()
 11
 12    def setUp(self):
 13        self.tester = streamlit.testing.v1.AppTest.from_file(
 14            'src/streamlit_calculator.py'
 15        )
 16        self.tester.run()
 17
 18    def test_streamlit_calculator_title(self):
 19        self.assertEqual(self.tester.title[0].value, 'Calculator')
 20
 21    def test_streamlit_calculator_display(self):
 22        display = (
 23            self.tester.main.children[1].proto
 24                .flex_container
 25        )
 26        self.assertEqual(display.gap_config.gap_size, 1)
 27        self.assertEqual(display.direction, 1)
 28        self.assertEqual(display.justify, 1)
 29        self.assertEqual(display.align, 1)
 30        self.assertTrue(display.border)
 31
 32    def test_streamlit_calculator_columns_and_buttons(self):
 33        self.assertEqual(len(self.tester.columns), 4)
 34
 35        for column, keys in (
 36            (0, ('<-', '7', '4', '1', '+/-')),
 37            (1, ('C', '8', '5', '2', '0')),
 38            (2, ('AC', '9', '6', '3', '.')),
 39            (3, ('/', 'X', r'\-', r'\+', '=')),
 40        ):
 41            for key in keys:
 42                with self.subTest(key=key):
 43                    self.assertEqual(
 44                        (
 45                            self.tester.columns[column]
 46                                .button(key)
 47                                .label
 48                        ),
 49                        key
 50                    )
 51
 52    def test_streamlit_calculator_operations_buttons(self):
 53        for key in ('/', 'X', r'\-', r'\+', '=', 'C', 'AC'):
 54            with self.subTest(key=key):
 55                self.assertEqual(
 56                    self.tester.button(key).proto.type,
 57                    'primary'
 58                )
 59
 60    def test_streamlit_session_state(self):
 61        numbers = '0123456789'
 62        self.assertEqual(self.tester.session_state['number'], '0')
 63
 64        expectation = random.choice(numbers)
 65        while expectation == '0':
 66            expectation = random.choice(numbers)
 67        else:
 68            self.press_button(expectation)
 69
 70        self.assertEqual(
 71            self.tester.session_state['number'], expectation
 72        )
 73
 74        for _ in range(0, len(numbers)):
 75            a_random_number = random.choice(numbers)
 76            self.press_button(a_random_number)
 77            expectation += a_random_number
 78
 79        self.assertEqual(
 80            self.tester.session_state['number'],
 81            expectation
 82        )
 83
 84    def test_streamlit_calculator_w_decimals(self):
 85        for key in ('0.23.5.6.7.89'):
 86            self.press_button(key)
 87
 88        self.assertEqual(
 89            self.tester.session_state['number'],
 90            '0.2356789'
 91        )
 92
 93    def test_streamlit_calculator_backspace(self):
 94        a_random_number = tests.test_calculator.a_random_number()
 95        while a_random_number < 0:
 96            a_random_number = tests.test_calculator.a_random_number()
 97        a_random_number = str(a_random_number)
 98
 99        for key in a_random_number:
100            self.press_button(key)
101        self.press_button('<-')
102
103        self.assertEqual(
104            self.tester.session_state['number'],
105            a_random_number[:-1]
106        )
107
108        self.press_button('<-')
109        self.assertEqual(
110            self.tester.session_state['number'],
111            a_random_number[:-2]
112        )
113
114    def test_streamlit_calculator_w_plus_minus(self):
115        a_number = '963.0258741'
116        for key in a_number:
117            self.press_button(key)
118        self.assertEqual(
119            self.tester.session_state['number'], a_number
120        )
121
122        self.press_button('+/-')
123        self.assertEqual(
124            self.tester.session_state['number'], f'-{a_number}'
125        )
126
127        self.press_button('+/-')
128        self.assertEqual(
129            self.tester.session_state['number'], a_number
130        )
131
132
133# Exceptions seen
134# NameError
135# AttributeError
136# AssertionError
137# SyntaxError
138# KeyError
139# streamlit.errors.StreamlitDuplicateElementKey

open the project

  • I change directory to the calculator folder

    cd calculator
    
  • I use pytest-watcher to run the tests

    uv run pytest-watcher . --now
    

    the terminal is my friend, and shows

    rootdir: .../pumping_python/calculator
    configfile: pyproject.toml
    collected 12 items
    
    tests/test_calculator.py .....                                [ 41%]
    tests/test_calculator_website.py ...                          [ 66%]
    tests/test_streamlit_calculator.py ....                       [100%]
    
    ======================== 12 passed in X.YZs =========================
    
  • I open another terminal then use uv in the calculator folder

    uv run streamlit run src/streamlit_calculator.py
    

    the terminal is my friend, and shows

    Collecting usage statistics.
    To deactivate, set browser.gatherUsageStats to false.
    
    
      You can now view your Streamlit app in your browser.
    
      Local URL: http://localhost:8501
      Network URL: http://ABC.DEF.GHI.JKL:8501
      External URL: http://MNO.PQR.STU.VWX:8501
    

    I use ctrl/option on the keyboard and click on http://localhost:8501 with the mouse to open the browser and it shows

    Calculator Streamlit Primary Buttons

test_streamlit_session_state

streamlit has a session state object that I can use to keep values in between button presses. They work the same as class attributes and they are dictionaries - I can add key-value pairs to them


RED: make it fail


  • I hold ctrl/command on the keyboard and click on tests/test_streamlit_calculator.py with the mouse to open it in the editor

  • I add a test for the session state object, I want it to hold the number when I click the buttons, in test_streamlit_calculator.py

    47    def test_streamlit_calculator_operations_buttons(self):
    48        for key in ('/', 'X', r'\-', r'\+', '=', 'C', 'AC'):
    49            with self.subTest(key=key):
    50                self.assertEqual(
    51                    self.tester.button(key).proto.type,
    52                    'primary'
    53                )
    54
    55    def test_streamlit_session_state(self):
    56        self.assertIsNone(self.tester.session_state['number'])
    57
    58
    59# Exceptions seen
    

    the terminal is my friend, and shows KeyError

    KeyError: 'st.session_state has no key "number".
    Did you forget to initialize it?
    More info: https://docs.streamlit.io/develop/concepts/architecture/session-state#initialization'
    

GREEN: make it pass


  • I use the setdefault method to add a key that will hold the numbers to show in the Calculator, in streamlit_calculator.py

    90def main():
    91    streamlit.title('Calculator')
    92    streamlit.session_state.setdefault('number', '0')
    93    display = streamlit.container(border=True)
    94
    95    column_1, column_2, column_3, operations = streamlit.columns(4)
    96    add_buttons_to_column_1(column_1, display)
    97    add_buttons_to_column_2(column_2, display)
    98    add_buttons_to_column_3(column_3, display)
    99    add_buttons_to_column_4(operations)
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 0 is not None
    
  • I add 0 as the expectation in test_streamlit_calculator.py

    55    def test_streamlit_session_state(self):
    56        self.assertIsNone(self.tester.session_state['number'], '0')
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '0' is not None : 0
    
  • I change the assertIsNone to assertEqual

    56        self.assertEqual(self.tester.session_state['number'], '0')
    

    the test passes


REFACTOR: make it better


  • I add the session state object to the show function so that when a button is clicked, it will be added to the session state object then shown in the disply

     4def show(display, number):
     5    # display.write(number)
     6    streamlit.session_state['number'] += number
     7    display.write(streamlit.session_state['number'])
     8
     9
    10def add_buttons_to_column_1(column_1, display):
    

    the test passes

  • I remove the commented line

    4def show(display, number):
    5    streamlit.session_state['number'] += number
    6    display.write(streamlit.session_state['number'])
    
  • I add an assertion to test what happens to the session state object when I press a button for a number

    55    def test_streamlit_session_state(self):
    56        self.assertEqual(self.tester.session_state['number'], '0')
    57        self.tester.button('1').click().run()
    58        self.assertEqual(self.tester.session_state['number'], '0')
    
    • .click() presses the button

    • .run() runs the program - streamlit_calculator.py, which is what happens when a person uses the application in the browser

    the terminal is my friend, and shows AssertionError

    AssertionError: '01' != '0'
    

    this is a problem

  • I change the expectation to match

    58        self.assertEqual(self.tester.session_state['number'], '01')
    

    the test passes

  • I add more button presses and another assertion

    55    def test_streamlit_session_state(self):
    56        self.assertEqual(self.tester.session_state['number'], '0')
    57        self.tester.button('1').click().run()
    58        self.assertEqual(self.tester.session_state['number'], '01')
    59        self.tester.button('2').click().run()
    60        self.tester.button('3').click().run()
    61        self.tester.button('4').click().run()
    62        self.assertEqual(self.tester.session_state['number'], '01')
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '01234' != '01'
    
  • I change the expectation to match

    62        self.assertEqual(
    63            self.tester.session_state['number'], '01234'
    64        )
    

    the test passes

  • I refresh the browser and click on all the numbers

    Many Numbers

    the calculator keeps numbers when I press the buttons and the number starts with 0 like the test

  • I want the calculator to remove 0 when it is the first number after I click on other numbers. I change the assertions

    55    def test_streamlit_session_state(self):
    56        self.assertEqual(self.tester.session_state['number'], '0')
    57        self.tester.button('1').click().run()
    58        self.assertEqual(self.tester.session_state['number'], '1')
    59        self.tester.button('2').click().run()
    60        self.tester.button('3').click().run()
    61        self.tester.button('4').click().run()
    62        self.assertEqual(
    63            self.tester.session_state['number'], '1234'
    64        )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '01' != '1'
    
  • I add a condition to the show function in streamlit_calculator.py

    4def show(display, number):
    5    if streamlit.session_state['number'] == '0':
    6        streamlit.session_state['number'] = number
    7    else:
    8        streamlit.session_state['number'] += number
    9    display.write(streamlit.session_state['number'])
    

    the test passes

  • I refresh the browser and try all the numbers

    Many Numbers Without 0 first

    the number no longer starts with 0

  • I import the random module to use random numbers for the test, in test_streamlit_calculator.py

    1import random
    2import streamlit.testing.v1
    3import unittest
    4
    5
    6class TestStreamlitCalculator(unittest.TestCase):
    
  • I use it in test_streamlit_session_state

    56    def test_streamlit_session_state(self):
    57        numbers = '0123456789'
    58        self.assertEqual(self.tester.session_state['number'], '0')
    59        # self.tester.button('1').click().run()
    60        x = random.choice(numbers)
    61        self.tester.button(x).click().run()
    62        self.assertEqual(self.tester.session_state['number'], '1')
    
    • numbers = '0123456789' makes a string with ten numbers

    • x = random.choice(numbers) picks a random number from the ten numbers and points x to it

    the terminal is my friend, and shows. AssertionError

    AssertionError: 'X' != '1'
    

    where X is a random number

  • I change the expectation

    56    def test_streamlit_session_state(self):
    57        numbers = '0123456789'
    58        self.assertEqual(self.tester.session_state['number'], '0')
    59        # self.tester.button('1').click().run()
    60        x = random.choice(numbers)
    61        self.tester.button(x).click().run()
    62        self.assertEqual(self.tester.session_state['number'], x)
    63        self.tester.button('2').click().run()
    64        self.tester.button('3').click().run()
    65        self.tester.button('4').click().run()
    66        self.assertEqual(
    67            self.tester.session_state['number'], '1234'
    68        )
    

    when x is not 1, the terminal is my friend, and shows AssertionError

    AssertionError: 'X234' != '1234'
    
  • I use random numbers for the other button presses

    62        self.assertEqual(self.tester.session_state['number'], x)
    63        # self.tester.button('2').click().run()
    64        # self.tester.button('3').click().run()
    65        # self.tester.button('4').click().run()
    66        y = random.choice(numbers)
    67        self.tester.button(y).click().run()
    68        z = random.choice(numbers)
    69        self.tester.button(z).click().run()
    70        a = random.choice(numbers)
    71        self.tester.button(a).click().run()
    72        self.assertEqual(
    73            self.tester.session_state['number'], '1234'
    74        )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'XYZA' != '1234'
    
  • I use the random numbers in the expectation of the assertion

    72        self.assertEqual(
    73            self.tester.session_state['number'], f'{x}{y}{z}{a}'
    74        )
    

    when x is 0, the terminal is my friend, and shows AssertionError

    AssertionError: 'YZA' != '0YZA'
    

what is a while loop?

  • I can use a while loop to make sure that x is never 0, since the session_state['number'] is always 0 at the beginning. I add a while loop to test_streamlit_session_state

    56    def test_streamlit_session_state(self):
    57        numbers = '0123456789'
    58        self.assertEqual(self.tester.session_state['number'], '0')
    59        # self.tester.button('1').click().run()
    60        x = random.choice(numbers)
    61        while x == '0':
    62            x = random.choice(numbers)
    63        else:
    64            self.tester.button(x).click().run()
    65
    66        self.assertEqual(self.tester.session_state['number'], x)
    67        # self.tester.button('2').click().run()
    
    • x = random.choice(numbers) points x to a random number from the numbers variable

      • if x is not equal to '0' it goes to the next block which is else: self.tester.button(x).click().run()

      • if x is equal to '0' it goes to while x == '0': which makes a loop that will continue to run as long as x is equal to '0'

        • inside the loop x = random.choice(numbers) points x to a random number from the numbers string then checks again to see if x is equal to '0'

          • if x is equal to 0 the loop runs again

          • if x is not equal to 0 in the loop, it leaves the loop and goes to the next block which is else: self.tester.button(x).click().run()

    I use ctrl+s on the keyboard a few times and there are no more random failures

  • I add a for loop to test with a 10 digit number

    56    def test_streamlit_session_state(self):
    57        numbers = '0123456789'
    58        self.assertEqual(self.tester.session_state['number'], '0')
    59        # self.tester.button('1').click().run()
    60        x = random.choice(numbers)
    61        while x == '0':
    62            x = random.choice(numbers)
    63        else:
    64            self.tester.button(x).click().run()
    65
    66        self.assertEqual(self.tester.session_state['number'], x)
    67        # self.tester.button('2').click().run()
    68        # self.tester.button('3').click().run()
    69        # self.tester.button('4').click().run()
    70        y = random.choice(numbers)
    71        self.tester.button(y).click().run()
    72        z = random.choice(numbers)
    73        self.tester.button(z).click().run()
    74        a = random.choice(numbers)
    75        self.tester.button(a).click().run()
    76        self.assertEqual(
    77            self.tester.session_state['number'], f'{x}{y}{z}{a}'
    78        )
    79
    80        for _ in range(0, len(numbers)):
    81            a_random_number = random.choice(numbers)
    82            (
    83                self.tester.button(a_random_number)
    84                    .click().run()
    85            )
    86            x += a_random_number
    87
    88        self.assertEqual(
    89            self.tester.session_state['number'],
    90            x
    91        )
    92
    93
    94# Exceptions seen
    

    for _ in range(0, 10): uses _ because I do not need a name for each number from the range object in the for loop since I do not use it

    the terminal is my friend, and shows AssertionError

    AssertionError: 'XYZABCDEFGHIJK' != 'XBCDEFGHIJK'
    

    the first number and the last 10 numbers are the same in the two strings

  • I comment out the other lines

    56    def test_streamlit_session_state(self):
    57        numbers = '0123456789'
    58        self.assertEqual(self.tester.session_state['number'], '0')
    59        # self.tester.button('1').click().run()
    60        x = random.choice(numbers)
    61        while x == '0':
    62            x = random.choice(numbers)
    63        else:
    64            self.tester.button(x).click().run()
    65
    66        self.assertEqual(self.tester.session_state['number'], x)
    67        # self.tester.button('2').click().run()
    68        # self.tester.button('3').click().run()
    69        # self.tester.button('4').click().run()
    70        # y = random.choice(numbers)
    71        # self.tester.button(y).click().run()
    72        # z = random.choice(numbers)
    73        # self.tester.button(z).click().run()
    74        # a = random.choice(numbers)
    75        # self.tester.button(a).click().run()
    76        # self.assertEqual(
    77        #     self.tester.session_state['number'], f'{x}{y}{z}{a}'
    78        # )
    79
    80        for _ in range(0, len(numbers)):
    81            a_random_number = random.choice(numbers)
    82            (
    83                self.tester.button(a_random_number)
    84                    .click().run()
    85            )
    86            x += a_random_number
    87
    88        self.assertEqual(
    89            self.tester.session_state['number'],
    90            x
    91        )
    

    I use ctrl+s on the keyboard to run the tests a few times and the test is still green

  • I remove the commented lines

    56    def test_streamlit_session_state(self):
    57        numbers = '0123456789'
    58        self.assertEqual(self.tester.session_state['number'], '0')
    59
    60        x = random.choice(numbers)
    61        while x == '0':
    62            x = random.choice(numbers)
    63        else:
    64            self.tester.button(x).click().run()
    65
    66        self.assertEqual(self.tester.session_state['number'], x)
    67
    68        for _ in range(0, len(numbers)):
    69            a_random_number = random.choice(numbers)
    70            (
    71                self.tester.button(a_random_number)
    72                    .click().run()
    73            )
    74            x += a_random_number
    75
    76        self.assertEqual(
    77            self.tester.session_state['number'],
    78            x
    79        )
    
  • I use the Rename Symbol feature of the Integrated Development Environment (IDE) to change x to expectation because I like the name better

    55    def test_streamlit_session_state(self):
    56        numbers = '0123456789'
    57        self.assertEqual(self.tester.session_state['number'], '0')
    58
    59        expectation = random.choice(numbers)
    60        while expectation == '0':
    61            expectation = random.choice(numbers)
    62        else:
    63            self.tester.button(expectation).click().run()
    64
    65        self.assertEqual(
    66            self.tester.session_state['number'], expectation
    67        )
    68
    69        for _ in range(0, len(numbers)):
    70            a_random_number = random.choice(numbers)
    71            (
    72                self.tester.button(a_random_number)
    73                    .click().run()
    74            )
    75            expectation += a_random_number
    76
    77        self.assertEqual(
    78            self.tester.session_state['number'],
    79            expectation
    80        )
    81
    82
    83# Exceptions seen
    

test_streamlit_calculator_w_decimals


RED: make it fail


  • I add a test for decimal numbers

     78        self.assertEqual(
     79            self.tester.session_state['number'],
     80            expectation
     81        )
     82
     83    def test_streamlit_calculator_w_decimals(self):
     84        self.tester.button('0').click().run()
     85        self.tester.button('.').click().run()
     86        self.tester.button('2').click().run()
     87        self.tester.button('3').click().run()
     88        self.tester.button('.').click().run()
     89        self.tester.button('5').click().run()
     90        self.tester.button('.').click().run()
     91        self.tester.button('6').click().run()
     92        self.tester.button('.').click().run()
     93        self.tester.button('7').click().run()
     94        self.tester.button('.').click().run()
     95        self.tester.button('8').click().run()
     96        self.tester.button('9').click().run()
     97        self.assertEqual(
     98            self.tester.session_state['number'],
     99            '0.2356789'
    100        )
    101
    102
    103# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '.23.5.6.7.89' != '0.2356789'
    
  • I refresh the browser and do the same thing

    Many Decimals

    the Calculator allows me add many decimal points


GREEN: make it pass


  • I add a function for decimals in streamlit_calculator.py

     1import streamlit
     2
     3
     4def handle_decimals(display, number):
     5    if streamlit.session_state['number'].count('.') >= 1:
     6        return None
     7    else:
     8        streamlit.session_state['number'] += number
     9    display.write(streamlit.session_state['number'])
    10
    11
    12def show(display, number):
    

    the terminal still shows AssertionError

  • I change the on_click parameter for the . button in the add_buttons_to_column_3 function to use the new function

    78    column_3.button(
    79        label='.', key='.', width='stretch',
    80        # on_click=show, args=[display, '.'],
    81        on_click=handle_decimals, args=[display, '.'],
    82    )
    

    the test passes

  • I remove the commented line

    62def add_buttons_to_column_3(column_3, display):
    63    column_3.button(
    64        label='AC', key='AC', width='stretch', type='primary',
    65    )
    66    column_3.button(
    67        label='9', key='9', width='stretch',
    68        on_click=show, args=[display, '9'],
    69    )
    70    column_3.button(
    71        label='6', key='6', width='stretch',
    72        on_click=show, args=[display, '6'],
    73    )
    74    column_3.button(
    75        label='3', key='3', width='stretch',
    76        on_click=show, args=[display, '3'],
    77    )
    78    column_3.button(
    79        label='.', key='.', width='stretch',
    80        on_click=handle_decimals, args=[display, '.'],
    81    )
    82
    83
    84def add_buttons_to_column_4(column_4):
    

REFACTOR: make it better


  • I change the else clause to an if statement to make it simpler

     4def handle_decimals(display, number):
     5    if streamlit.session_state['number'].count('.') >= 1:
     6        return None
     7    # else:
     8    if streamlit.session_state['number'].count('.') == 0:
     9        streamlit.session_state['number'] += number
    10    display.write(streamlit.session_state['number'])
    

    the test is still green

  • I remove the other if statement

     4def handle_decimals(display, number):
     5    if streamlit.session_state['number'].count('.') == 0:
     6        streamlit.session_state['number'] += number
     7    display.write(streamlit.session_state['number'])
     8
     9
    10def show(display, number):
    

    still green

    • if streamlit.session_state['number'].count('.') == 0: checks if . is in session_state['number']

      • streamlit.session_state['number'].count('.') counts how many times . shows up in session_state['number']

      • if . is NOT in session_state['number'], then . gets added to session_state['number'] and the program runs the next line, which is display.write(streamlit.session_state['number'])

      • if . is in session_state['number'], then the program runs the next line, which is display.write(streamlit.session_state['number'])

  • I refresh the browser and try many decimals again

    One Decimal

    Yes! I can only do one decimal in a number

  • I add a for loop to test_streamlit_calculator_w_decimals in test_streamlit_calculator.py

     83    def test_streamlit_calculator_w_decimals(self):
     84        for key in ('0.23.5.6.7.89'):
     85            (
     86                self.tester.button(key)
     87                    .click().run()
     88            )
     89
     90        self.tester.button('0').click().run()
     91        self.tester.button('.').click().run()
     92        self.tester.button('2').click().run()
     93        self.tester.button('3').click().run()
     94        self.tester.button('.').click().run()
     95        self.tester.button('5').click().run()
     96        self.tester.button('.').click().run()
     97        self.tester.button('6').click().run()
     98        self.tester.button('.').click().run()
     99        self.tester.button('7').click().run()
    100        self.tester.button('.').click().run()
    101        self.tester.button('8').click().run()
    102        self.tester.button('9').click().run()
    103        self.assertEqual(
    104            self.tester.session_state['number'],
    105            '0.2356789'
    106        )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '0.235678902356789' != '0.2356789'
    
  • I remove the other button presses

    83    def test_streamlit_calculator_w_decimals(self):
    84        for key in ('0.23.5.6.7.89'):
    85            (
    86                self.tester.button(key)
    87                    .click().run()
    88            )
    89
    90        self.assertEqual(
    91            self.tester.session_state['number'],
    92            '0.2356789'
    93        )
    94
    95
    96# Exceptions seen
    

    the test passes


  • I keep writing self.tester.button(key).click().run(), time to add a method for button presses

     6class TestStreamlitCalculator(unittest.TestCase):
     7
     8    def press_button(self, key):
     9        self.tester.button(key).click().run()
    10
    11    def setUp(self):
    
  • I use the new method in test_streamlit_session_state

    59    def test_streamlit_session_state(self):
    60        numbers = '0123456789'
    61        self.assertEqual(self.tester.session_state['number'], '0')
    62
    63        expectation = random.choice(numbers)
    64        while expectation == '0':
    65            expectation = random.choice(numbers)
    66        else:
    67            # self.tester.button(expectation).click().run()
    68            self.press_button(expectation)
    69
    70        self.assertEqual(
    71            self.tester.session_state['number'], expectation
    72        )
    73
    74        for _ in range(0, len(numbers)):
    75            a_random_number = random.choice(numbers)
    76            # (
    77            #     self.tester.button(a_random_number)
    78            #         .click().run()
    79            # )
    80            self.press_button(a_random_number)
    81            expectation += a_random_number
    82
    83        self.assertEqual(
    84            self.tester.session_state['number'],
    85            expectation
    86        )
    

    the tests are still green

  • I remove the commented lines

    59    def test_streamlit_session_state(self):
    60        numbers = '0123456789'
    61        self.assertEqual(self.tester.session_state['number'], '0')
    62
    63        expectation = random.choice(numbers)
    64        while expectation == '0':
    65            expectation = random.choice(numbers)
    66        else:
    67            self.press_button(expectation)
    68
    69        self.assertEqual(
    70            self.tester.session_state['number'], expectation
    71        )
    72
    73        for _ in range(0, len(numbers)):
    74            a_random_number = random.choice(numbers)
    75            self.press_button(a_random_number)
    76            expectation += a_random_number
    77
    78        self.assertEqual(
    79            self.tester.session_state['number'],
    80            expectation
    81        )
    82
    83    def test_streamlit_calculator_w_decimals(self):
    
  • I use the press_button method in test_streamlit_calculator_w_decimals

    83    def test_streamlit_calculator_w_decimals(self):
    84        for key in ('0.23.5.6.7.89'):
    85            # (
    86            #     self.tester.button(key)
    87            #         .click().run()
    88            # )
    89            self.press_button(key)
    90
    91        self.assertEqual(
    92            self.tester.session_state['number'],
    93            '0.2356789'
    94        )
    

    still green

  • I remove the commented lines

    83    def test_streamlit_calculator_w_decimals(self):
    84        for key in ('0.23.5.6.7.89'):
    85            self.press_button(key)
    86
    87        self.assertEqual(
    88            self.tester.session_state['number'],
    89            '0.2356789'
    90        )
    91
    92
    93# Exceptions seen
    

test_streamlit_calculator_backspace

I want to be able to remove the last digit of a number with the <- button


RED: make it fail


  • I add a new test

     87        self.assertEqual(
     88            self.tester.session_state['number'],
     89            '0.2356789'
     90        )
     91
     92    def test_streamlit_calculator_backspace(self):
     93        self.press_button('1')
     94        self.press_button('2')
     95        self.press_button('3')
     96        self.press_button('4')
     97        self.press_button('<-')
     98        self.assertEqual(
     99            self.tester.session_state['number'],
    100            '123'
    101        )
    102
    103
    104# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '1234' != '123'
    
  • I try the same thing in the browser and it clears the screen


GREEN: make it pass


  • I add a function to streamlit_calculator.py

     1import streamlit
     2
     3
     4def backspace(display):
     5    number = streamlit.session_state['number'][:-1]
     6    streamlit.session_state['number'] = number
     7    display.write(streamlit.session_state['number'])
     8
     9
    10def handle_decimals(display, number):
    

    the terminal still shows AssertionError

  • I add the on_click parameter to the <- button in the add_buttons_to_column_1 function

    24def add_buttons_to_column_1(column_1, display):
    25    column_1.button(
    26        label='<-', key='<-', width='stretch',
    27        on_click=backspace, args=[display],
    28    )
    29    column_1.button(
    30        label='7', key='7', width='stretch',
    31        on_click=show, args=[display, '7'],
    32    )
    33    column_1.button(
    34        label='4', key='4', width='stretch',
    35        on_click=show, args=[display, '4'],
    36    )
    37    column_1.button(
    38        label='1', key='1', width='stretch',
    39        on_click=show, args=[display, '1'],
    40    )
    41    column_1.button(
    42        label='+/-', key='+/-', width='stretch',
    43        on_click=show, args=[display, '+/-'],
    44    )
    45
    46
    47def add_buttons_to_column_2(column_2, display):
    

    the test passes


REFACTOR: make it better


  • I add an import statement for tests/test_calculator.py in test_streamlit_calculator.py

    1import random
    2import streamlit.testing.v1
    3import tests.test_calculator
    4import unittest
    5
    6
    7class TestStreamlitCalculator(unittest.TestCase):
    
  • I add a variable with a for loop for a random number in test_streamlit_calculator_backspace

     93    def test_streamlit_calculator_backspace(self):
     94        a_random_number = tests.test_calculator.a_random_number()
     95        a_random_number = str(a_random_number)
     96
     97        for key in a_random_number:
     98            self.press_button(key)
     99        self.press_button('<-')
    100
    101        self.assertEqual(
    102            self.tester.session_state['number'],
    103            a_random_number
    104        )
    105
    106        self.press_button('1')
    107        self.press_button('2')
    108        self.press_button('3')
    109        self.press_button('4')
    110        self.press_button('<-')
    111        self.assertEqual(
    112            self.tester.session_state['number'],
    113            '123'
    114        )
    
    • a_random_number = tests.test_calculator.a_random_number() uses the a_random_number function from test_calculator.py that I made in how to use random numbers

    • a_random_number = str(a_random_number) changes the random number to a string since all the tests of the calculator have been done with strings so far

    I use ctrl+s on the keyboard to run the test a few times because I am using random numbers. If the number is positive, the terminal is my friend, and shows AssertionError

    AssertionError: 'LMN.OPQRSTUVWXYZ' != 'LMN.OPQRSTUVWXYZA'
    

    the last number was not removed

    If the random number is negative, the terminal is my friend, and shows KeyError

    KeyError: '-'
    

    I need a test for the ‘+/-’ button

  • I add a while loop to make sure a_random_number is never negative

    93    def test_streamlit_calculator_backspace(self):
    94        a_random_number = tests.test_calculator.a_random_number()
    95        while a_random_number < 0:
    96            a_random_number = tests.test_calculator.a_random_number()
    97        a_random_number = str(a_random_number)
    98
    99        for key in a_random_number:
    

    I use ctrl+s on the keyboard to run the tests a few times and the terminal is my friend, and shows AssertionError

    AssertionError: 'BCD.EFGHIJKLMNOP' != 'BCD.EFGHIJKLMNOPQ'
    

    I remove this while statement when I test the ‘+/-’ button

  • I change the expectation to remove the last digit from the random number

    103        self.assertEqual(
    104            self.tester.session_state['number'],
    105            a_random_number[:-1]
    106        )
    

    the terminal shows AssertionError

    AssertionError: 'RS.TUVWXYZABCDEF123' != '123'
    

    because I have button presses after the test. I need a way to reset the numbers back to 0

  • I comment out the other button presses

     93    def test_streamlit_calculator_backspace(self):
     94        a_random_number = tests.test_calculator.a_random_number()
     95        while a_random_number < 0:
     96            a_random_number = tests.test_calculator.a_random_number()
     97        a_random_number = str(a_random_number)
     98
     99        for key in a_random_number:
    100            self.press_button(key)
    101        self.press_button('<-')
    102
    103        self.assertEqual(
    104            self.tester.session_state['number'],
    105            a_random_number[:-1]
    106        )
    107
    108        # self.press_button('1')
    109        # self.press_button('2')
    110        # self.press_button('3')
    111        # self.press_button('4')
    112        self.press_button('<-')
    113        self.assertEqual(
    114            self.tester.session_state['number'],
    115            '123'
    116        )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'GHI.JKLMNOPQRS' != '123'
    
  • I change the expectation

     93    def test_streamlit_calculator_backspace(self):
     94        a_random_number = tests.test_calculator.a_random_number()
     95        while a_random_number < 0:
     96            a_random_number = tests.test_calculator.a_random_number()
     97        a_random_number = str(a_random_number)
     98
     99        for key in a_random_number:
    100            self.press_button(key)
    101        self.press_button('<-')
    102
    103        self.assertEqual(
    104            self.tester.session_state['number'],
    105            a_random_number[:-1]
    106        )
    107
    108        # self.press_button('1')
    109        # self.press_button('2')
    110        # self.press_button('3')
    111        # self.press_button('4')
    112        self.press_button('<-')
    113        self.assertEqual(
    114            self.tester.session_state['number'],
    115            # '123'
    116            a_random_number[:-2]
    117        )
    

    the test passes

  • I remove the commented lines

     93    def test_streamlit_calculator_backspace(self):
     94        a_random_number = tests.test_calculator.a_random_number()
     95        while a_random_number < 0:
     96            a_random_number = tests.test_calculator.a_random_number()
     97        a_random_number = str(a_random_number)
     98
     99        for key in a_random_number:
    100            self.press_button(key)
    101        self.press_button('<-')
    102
    103        self.assertEqual(
    104            self.tester.session_state['number'],
    105            a_random_number[:-1]
    106        )
    107
    108        self.press_button('<-')
    109        self.assertEqual(
    110            self.tester.session_state['number'],
    111            a_random_number[:-2]
    112        )
    113
    114
    115# Exceptions seen
    
  • I go to the browser and click on a few numbers, then click on <- and it removes the last number. Fantastic!


test_streamlit_calculator_w_plus_minus

Nothing happens when I click +/- in the calculator. I want it to

  • change positive numbers to negative numbers

  • change negative numbers to positive numbers


RED: make it fail


I add a new test for the +/- button

108          self.press_button('<-')
109          self.assertEqual(
110              self.tester.session_state['number'],
111              a_random_number[:-2]
112          )
113
114      def test_streamlit_calculator_w_plus_minus(self):
115          self.press_button('9')
116          self.assertEqual(
117              self.tester.session_state['number'], '9'
118          )
119
120          self.press_button('+/-')
121          self.assertEqual(
122              self.tester.session_state['number'], '-9'
123          )
124
125
126  # Exceptions seen

the terminal is my friend, and shows AssertionError

AssertionError: '9' != '-9'

GREEN: make it pass


  • I add a function for the +/- button in streamlit_calculator.py

     1import streamlit
     2
     3
     4def plus_minus(display):
     5    if not streamlit.session_state['number'].startswith('-'):
     6        number = '-' + streamlit.session_state['number']
     7        streamlit.session_state['number'] = number
     8        display.write(streamlit.session_state['number'])
     9
    10
    11def backspace(display):
    

    the terminal still shows AssertionError

  • I add the on_click parameter for the +/- button in the add_buttons_to_column_1 function

    31def add_buttons_to_column_1(column_1, display):
    32    column_1.button(
    33        label='<-', key='<-', width='stretch',
    34        on_click=backspace, args=[display],
    35    )
    36    column_1.button(
    37        label='7', key='7', width='stretch',
    38        on_click=show, args=[display, '7'],
    39    )
    40    column_1.button(
    41        label='4', key='4', width='stretch',
    42        on_click=show, args=[display, '4'],
    43    )
    44    column_1.button(
    45        label='1', key='1', width='stretch',
    46        on_click=show, args=[display, '1'],
    47    )
    48    column_1.button(
    49        label='+/-', key='+/-', width='stretch',
    50        on_click=plus_minus, args=[display],
    51    )
    52
    53
    54def add_buttons_to_column_2(column_2, display):
    

    the test passes. I can turn a positive number to a negative number with the +/- button. Can I turn a negative number to a positive number with the +/- button?


REFACTOR: make it better


  • I add a button press and assertion to make sure I can turn a negative number to a positive number with the +/- button, in test_streamlit_calculator.py

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        self.press_button('9')
    116        self.assertEqual(
    117            self.tester.session_state['number'], '9'
    118        )
    119
    120        self.press_button('+/-')
    121        self.assertEqual(
    122            self.tester.session_state['number'], '-9'
    123        )
    124
    125        self.press_button('+/-')
    126        self.assertEqual(
    127            self.tester.session_state['number'], '9'
    128        )
    129
    130
    131# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '-9' != '9'
    
  • I add another if statement to the plus_minus function in streamlit_calculator.py

     4def plus_minus(display):
     5    if streamlit.session_state['number'].startswith('-'):
     6        number = streamlit.session_state['number'][1:]
     7    if not streamlit.session_state['number'].startswith('-'):
     8        number = '-' + streamlit.session_state['number']
     9    streamlit.session_state['number'] = number
    10
    11    display.write(streamlit.session_state['number'])
    

    the test passes

  • I use else for the second if statement

     4def plus_minus(display):
     5    if streamlit.session_state['number'].startswith('-'):
     6        number = streamlit.session_state['number'][1:]
     7    # if not streamlit.session_state['number'].startswith('-'):
     8    else:
     9        number = '-' + streamlit.session_state['number']
    10    streamlit.session_state['number'] = number
    11
    12    display.write(streamlit.session_state['number'])
    

    the test is still green

  • I remove the commented line

     4def plus_minus(display):
     5    if streamlit.session_state['number'].startswith('-'):
     6        number = streamlit.session_state['number'][1:]
     7    else:
     8        number = '-' + streamlit.session_state['number']
     9    streamlit.session_state['number'] = number
    10
    11    display.write(streamlit.session_state['number'])
    12
    13
    14def backspace(display):
    
  • I add a variable to remove repetition from test_streamlit_calculator_w_plus_minus in test_streamlit_calculator.py

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        a_number = '9'
    116        self.press_button('9')
    117        self.assertEqual(
    
  • I use the variable in the assertions and button presses

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        a_number = '9'
    116        # self.press_button('9')
    117        self.press_button(a_number)
    118        self.assertEqual(
    119            # self.tester.session_state['number'], '9'
    120            self.tester.session_state['number'], a_number
    121        )
    122
    123        self.press_button('+/-')
    124        self.assertEqual(
    125            # self.tester.session_state['number'], '-9'
    126            self.tester.session_state['number'], f'-{a_number}'
    127        )
    128
    129        self.press_button('+/-')
    130        self.assertEqual(
    131            # self.tester.session_state['number'], '9'
    132            self.tester.session_state['number'], a_number
    133        )
    

    the test is still green

  • I remove the commented lines

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        a_number = '9'
    116        self.press_button(a_number)
    117        self.assertEqual(
    118            self.tester.session_state['number'], a_number
    119        )
    120
    121        self.press_button('+/-')
    122        self.assertEqual(
    123            self.tester.session_state['number'], f'-{a_number}'
    124        )
    125
    126        self.press_button('+/-')
    127        self.assertEqual(
    128            self.tester.session_state['number'], a_number
    129        )
    
  • I try a bigger number

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        a_number = '96'
    116        self.press_button(a_number)
    

    the terminal is my friend, and shows KeyError

    KeyError: '96'
    

    I need separate button presses for the two numbers

  • I add a for loop

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        a_number = '96'
    116        for key in a_number:
    117            self.press_button(key)
    118        self.assertEqual(
    119            self.tester.session_state['number'], a_number
    120        )
    121
    122        self.press_button('+/-')
    

    the test passes

  • I try a number with all the digits and decimals

    114    def test_streamlit_calculator_w_plus_minus(self):
    115        a_number = '963.0258741'
    116        for key in a_number:
    117            self.press_button(key)
    118        self.assertEqual(
    119            self.tester.session_state['number'], a_number
    120        )
    121
    122        self.press_button('+/-')
    123        self.assertEqual(
    124            self.tester.session_state['number'], f'-{a_number}'
    125        )
    126
    127        self.press_button('+/-')
    128        self.assertEqual(
    129            self.tester.session_state['number'], a_number
    130        )
    131
    132
    133# Exceptions seen
    

    the test is still green

  • I refresh the browser and try to make a negative number

    Negative Number

    when I click on the +/- button it turns a positive number negative

  • I try the +/- button again

    Negative Number

    the - is removed from the number to make it a positive number. Progress!


REFACTOR: make it better

  • The last 4 functions in streamlit_calculator.py - plus_minus, backspace, handle_decimals and show look the same. 2 of the functions have number in the function signature, all 4 have display in the function signature

    def function_name(display):
    def function_name(display, number):
        statements
        display.write(streamlit.session_state['number'])
    
  • I add number in the parentheses for the plus_minus function to make it have the same signature as the show and handle_decimals functions

     4def plus_minus(display, number):
     5    if streamlit.session_state['number'].startswith('-'):
     6        number = streamlit.session_state['number'][1:]
     7    else:
     8        number = '-' + streamlit.session_state['number']
     9    streamlit.session_state['number'] = number
    10
    11    display.write(streamlit.session_state['number'])
    12
    13
    14def backspace(display):
    

    the terminal is my friend, and shows AssertionError

    '963.0258741' != '-963.0258741'
    

    and TypeError

    TypeError: plus_minus() missing 1 required positional argument: 'number'
    
  • I add a second argument to the args parameter for the +/- button in the add_buttons_to_column_1 function

    34def add_buttons_to_column_1(column_1, display):
    35    column_1.button(
    36        label='<-', key='<-', width='stretch',
    37        on_click=backspace, args=[display],
    38    )
    39    column_1.button(
    40        label='7', key='7', width='stretch',
    41        on_click=show, args=[display, '7'],
    42    )
    43    column_1.button(
    44        label='4', key='4', width='stretch',
    45        on_click=show, args=[display, '4'],
    46    )
    47    column_1.button(
    48        label='1', key='1', width='stretch',
    49        on_click=show, args=[display, '1'],
    50    )
    51    column_1.button(
    52        label='+/-', key='+/-', width='stretch',
    53        on_click=plus_minus, args=[display, '+/-'],
    54    )
    55
    56
    57def add_buttons_to_column_2(column_2, display):
    

    the test is green again

  • I add number in the parentheses for the backspace function to make it have the same signature as the show, handle_decimals and plus_minus functions

    14def backspace(display, number):
    15    number = streamlit.session_state['number'][:-1]
    16    streamlit.session_state['number'] = number
    17    display.write(streamlit.session_state['number'])
    18
    19
    20def handle_decimals(display, number):
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'RST.UVWXYZABCDEFGH' != 'RST.UVWXYZABCDEFG'
    

    it also shows TypeError

    TypeError: backspace() missing 1 required positional argument: 'number'
    
  • I add a second argument to the args parameter for the <- button in the add_buttons_to_column_1 function

    34def add_buttons_to_column_1(column_1, display):
    35    column_1.button(
    36        label='<-', key='<-', width='stretch',
    37        on_click=backspace, args=[display, '<-'],
    38    )
    39    column_1.button(
    40        label='7', key='7', width='stretch',
    41        on_click=show, args=[display, '7'],
    42    )
    43    column_1.button(
    44        label='4', key='4', width='stretch',
    45        on_click=show, args=[display, '4'],
    46    )
    47    column_1.button(
    48        label='1', key='1', width='stretch',
    49        on_click=show, args=[display, '1'],
    50    )
    51    column_1.button(
    52        label='+/-', key='+/-', width='stretch',
    53        on_click=plus_minus, args=[display, '+/-'],
    54    )
    55
    56
    57def add_buttons_to_column_2(column_2, display):
    

    the test is green again

  • I add a new function for showing the number key of the session state object

    1import streamlit
    2
    3
    4def show_number(display):
    5    display.write(streamlit.session_state['number'])
    6
    7
    8def plus_minus(display, number):
    
  • I use the new function in the plus_minus function

     8def plus_minus(display, number):
     9    if streamlit.session_state['number'].startswith('-'):
    10        number = streamlit.session_state['number'][1:]
    11    else:
    12        number = '-' + streamlit.session_state['number']
    13    streamlit.session_state['number'] = number
    14
    15    # display.write(streamlit.session_state['number'])
    16    show_number(display)
    

    the test is still green

  • I remove the commented line

     8def plus_minus(display, number):
     9    if streamlit.session_state['number'].startswith('-'):
    10        number = streamlit.session_state['number'][1:]
    11    else:
    12        number = '-' + streamlit.session_state['number']
    13    streamlit.session_state['number'] = number
    14
    15    show_number(display)
    16
    17
    18def backspace(display, number):
    
  • I use the new function in the backspace function

    18def backspace(display, number):
    19    number = streamlit.session_state['number'][:-1]
    20    streamlit.session_state['number'] = number
    21    # display.write(streamlit.session_state['number'])
    22    show_number(display)
    

    the test is still green

  • I remove the commented line

    18def backspace(display, number):
    19    number = streamlit.session_state['number'][:-1]
    20    streamlit.session_state['number'] = number
    21    show_number(display)
    22
    23
    24def handle_decimals(display, number):
    
  • I use the new function in the handle_decimals function

    24def handle_decimals(display, number):
    25    if streamlit.session_state['number'].count('.') == 0:
    26        streamlit.session_state['number'] += number
    27    # display.write(streamlit.session_state['number'])
    28    show_number(display)
    

    the test is still green

  • I remove the commented line

    24def handle_decimals(display, number):
    25    if streamlit.session_state['number'].count('.') == 0:
    26        streamlit.session_state['number'] += number
    27    show_number(display)
    28
    29
    30def show(display, number):
    
  • I use the new function in the show function

    30def show(display, number):
    31    if streamlit.session_state['number'] == '0':
    32        streamlit.session_state['number'] = number
    33    else:
    34        streamlit.session_state['number'] += number
    35    # display.write(streamlit.session_state['number'])
    36    show_number(display)
    

    the test is still green

  • I remove the commented line

    30def show(display, number):
    31    if streamlit.session_state['number'] == '0':
    32        streamlit.session_state['number'] = number
    33    else:
    34        streamlit.session_state['number'] += number
    35    show_number(display)
    36
    37
    38def add_buttons_to_column_1(column_1, display):
    
  • I add a new function for adding the number to the session state object

    24def handle_decimals(display, number):
    25    if streamlit.session_state['number'].count('.') == 0:
    26        streamlit.session_state['number'] += number
    27    show_number(display)
    28
    29
    30def add_number_to_state(number):
    31    if streamlit.session_state['number'] == '0':
    32        streamlit.session_state['number'] = number
    33    else:
    34        streamlit.session_state['number'] += number
    35
    36
    37def show(display, number):
    
  • I add a function to handle all the button clicks

    37def show(display, number):
    38    if streamlit.session_state['number'] == '0':
    39        streamlit.session_state['number'] = number
    40    else:
    41        streamlit.session_state['number'] += number
    42    show_state(display)
    43
    44
    45def on_click(function, display, value):
    46    function(value)
    47    show_state(display)
    48
    49
    50def add_buttons():
    
  • I try the on_click function with the 7 button in the add_buttons_to_column_1 function

    50def add_buttons_to_column_1(column_1, display):
    51    column_1.button(
    52        label='<-', key='<-', width='stretch',
    53        on_click=backspace, args=[display, '<-'],
    54    )
    55    column_1.button(
    56        label='7', key='7', width='stretch', on_click=on_click,
    57        # on_click=show, args=[display, '7'],
    58        args=[add_number_to_state, display, '7'],
    59    )
    60    column_1.button(
    61        label='4', key='4', width='stretch',
    62        on_click=show, args=[display, '4'],
    63    )
    64    column_1.button(
    65        label='1', key='1', width='stretch',
    66        on_click=show, args=[display, '1'],
    67    )
    68    column_1.button(
    69        label='+/-', key='+/-', width='stretch',
    70        on_click=plus_minus, args=[display, '+/-'],
    71    )
    

    the tests are still green! Yes!!

  • I use the on_click function with all the other number buttons in the add_buttons_to_column_1 function

    50def add_buttons_to_column_1(column_1, display):
    51    column_1.button(
    52        label='<-', key='<-', width='stretch',
    53        on_click=backspace, args=[display, '<-'],
    54    )
    55    column_1.button(
    56        label='7', key='7', width='stretch', on_click=on_click,
    57        # on_click=show, args=[display, '7'],
    58        args=[add_number_to_state, display, '7'],
    59    )
    60    column_1.button(
    61        label='4', key='4', width='stretch', on_click=on_click,
    62        # on_click=show, args=[display, '4'],
    63        args=[add_number_to_state, display, '4'],
    64    )
    65    column_1.button(
    66        label='1', key='1', width='stretch', on_click=on_click,
    67        # on_click=show, args=[display, '1'],
    68        args=[add_number_to_state, display, '1'],
    69    )
    70    column_1.button(
    71        label='+/-', key='+/-', width='stretch',
    72        on_click=plus_minus, args=[display, '+/-'],
    73    )
    

    still green

  • I remove the commented lines

    50def add_buttons_to_column_1(column_1, display):
    51    column_1.button(
    52        label='<-', key='<-', width='stretch',
    53        on_click=backspace, args=[display, '<-'],
    54    )
    55    column_1.button(
    56        label='7', key='7', width='stretch', on_click=on_click,
    57        args=[add_number_to_state, display, '7'],
    58    )
    59    column_1.button(
    60        label='4', key='4', width='stretch', on_click=on_click,
    61        args=[add_number_to_state, display, '4'],
    62    )
    63    column_1.button(
    64        label='1', key='1', width='stretch', on_click=on_click,
    65        args=[add_number_to_state, display, '1'],
    66    )
    67    column_1.button(
    68        label='+/-', key='+/-', width='stretch',
    69        on_click=plus_minus, args=[display, '+/-'],
    70    )
    71
    72
    73def add_buttons_to_column_2(column_2, display):
    
  • I use the on_click function with all the number buttons in the add_buttons_to_column_2 function

    73def add_buttons_to_column_2(column_2, display):
    74    column_2.button(
    75        label='C', key='C', width='stretch', type='primary',
    76    )
    77    column_2.button(
    78        label='8', key='8', width='stretch', on_click=on_click,
    79        # on_click=show, args=[display, '8'],
    80        args=[add_number_to_state, display, '8'],
    81    )
    82    column_2.button(
    83        label='5', key='5', width='stretch', on_click=on_click,
    84        # on_click=show, args=[display, '5'],
    85        args=[add_number_to_state, display, '5'],
    86    )
    87    column_2.button(
    88        label='2', key='2', width='stretch', on_click=on_click,
    89        # on_click=show, args=[display, '2'],
    90        args=[add_number_to_state, display, '2'],
    91    )
    92    column_2.button(
    93        label='0', key='0', width='stretch', on_click=on_click,
    94        # on_click=show, args=[display, '0'],
    95        args=[add_number_to_state, display, '0'],
    96    )
    

    green

  • I remove the commented lines

    73def add_buttons_to_column_2(column_2, display):
    74    column_2.button(
    75        label='C', key='C', width='stretch', type='primary',
    76    )
    77    column_2.button(
    78        label='8', key='8', width='stretch', on_click=on_click,
    79        args=[add_number_to_state, display, '8'],
    80    )
    81    column_2.button(
    82        label='5', key='5', width='stretch', on_click=on_click,
    83        args=[add_number_to_state, display, '5'],
    84    )
    85    column_2.button(
    86        label='2', key='2', width='stretch', on_click=on_click,
    87        args=[add_number_to_state, display, '2'],
    88    )
    89    column_2.button(
    90        label='0', key='0', width='stretch', on_click=on_click,
    91        args=[add_number_to_state, display, '0'],
    92    )
    93
    94
    95def add_buttons_to_column_3(column_3, display):
    
  • I use the on_click function with all the number buttons in the add_buttons_to_column_3 function

     95def add_buttons_to_column_3(column_3, display):
     96    column_3.button(
     97        label='AC', key='AC', width='stretch', type='primary',
     98    )
     99    column_3.button(
    100        label='9', key='9', width='stretch', on_click=on_click,
    101        # on_click=show, args=[display, '9'],
    102        args=[add_number_to_state, display, '9'],
    103    )
    104    column_3.button(
    105        label='6', key='6', width='stretch', on_click=on_click,
    106        # on_click=show, args=[display, '6'],
    107        args=[add_number_to_state, display, '6'],
    108    )
    109    column_3.button(
    110        label='3', key='3', width='stretch', on_click=on_click,
    111        # on_click=show, args=[display, '3'],
    112        args=[add_number_to_state, display, '3'],
    113    )
    114    column_3.button(
    115        label='.', key='.', width='stretch',
    116        on_click=handle_decimals, args=[display, '.'],
    117    )
    

    still green

  • I remove the commented lines

     95def add_buttons_to_column_3(column_3, display):
     96    column_3.button(
     97        label='AC', key='AC', width='stretch', type='primary',
     98    )
     99    column_3.button(
    100        label='9', key='9', width='stretch', on_click=on_click,
    101        args=[add_number_to_state, display, '9'],
    102    )
    103    column_3.button(
    104        label='6', key='6', width='stretch', on_click=on_click,
    105        args=[add_number_to_state, display, '6'],
    106    )
    107    column_3.button(
    108        label='3', key='3', width='stretch', on_click=on_click,
    109        args=[add_number_to_state, display, '3'],
    110    )
    111    column_3.button(
    112        label='.', key='.', width='stretch',
    113        on_click=handle_decimals, args=[display, '.'],
    114    )
    115
    116
    117def add_buttons_to_column_4(column_4):
    
  • I remove the show function because it is no longer used

    30def add_number_to_state(number):
    31    if streamlit.session_state['number'] == '0':
    32        streamlit.session_state['number'] = number
    33    else:
    34        streamlit.session_state['number'] += number
    35
    36
    37def on_click(function, display, value):
    

  • I add a new function to make decimal numbers

    24def handle_decimals(display, number):
    25    if streamlit.session_state['number'].count('.') == 0:
    26        streamlit.session_state['number'] += number
    27    show_number(display)
    28
    29
    30def add_decimal():
    31    if streamlit.session_state['number'].count('.') == 0:
    32        streamlit.session_state['number'] += '.'
    33
    34
    35def add_number_to_state(number):
    
  • I use the new function with the . button in the add_buttons_to_column_3 function

     92def add_buttons_to_column_3(column_3, display):
     93    column_3.button(
     94        label='AC', key='AC', width='stretch', type='primary',
     95    )
     96    column_3.button(
     97        label='9', key='9', width='stretch', on_click=on_click,
     98        args=[add_number_to_state, display, '9'],
     99    )
    100    column_3.button(
    101        label='6', key='6', width='stretch', on_click=on_click,
    102        args=[add_number_to_state, display, '6'],
    103    )
    104    column_3.button(
    105        label='3', key='3', width='stretch', on_click=on_click,
    106        args=[add_number_to_state, display, '3'],
    107    )
    108    column_3.button(
    109        label='.', key='.', width='stretch', on_click=on_click,
    110        # on_click=handle_decimals, args=[display, '.'],
    111        args=[add_decimal, display]
    112    )
    

    the terminal is my friend, and shows KeyError

    FAILED ... - KeyError: 'I'
    FAILED ... - KeyError: '2'
    FAILED ... - KeyError: '0'
    

    and TypeError

    TypeError: on_click() missing 1 required positional argument: 'value'
    
  • I make the value parameter a choice in the on_click function

    42def on_click(function, display, *value):
    43    function(*value)
    44    show_number(display)
    45
    46
    47def add_buttons_to_column_1(column_1, display):
    

    the test passes

  • I remove the commented line from the add_buttons_to_column_3 function

     92def add_buttons_to_column_3(column_3, display):
     93    column_3.button(
     94        label='AC', key='AC', width='stretch', type='primary',
     95    )
     96    column_3.button(
     97        label='9', key='9', width='stretch', on_click=on_click,
     98        args=[add_number_to_state, display, '9'],
     99    )
    100    column_3.button(
    101        label='6', key='6', width='stretch', on_click=on_click,
    102        args=[add_number_to_state, display, '6'],
    103    )
    104    column_3.button(
    105        label='3', key='3', width='stretch', on_click=on_click,
    106        args=[add_number_to_state, display, '3'],
    107    )
    108    column_3.button(
    109        label='.', key='.', width='stretch', on_click=on_click,
    110        args=[add_decimal, display]
    111    )
    112
    113
    114def add_buttons_to_column_4(column_4):
    
  • I remove the handle_decimals function because it is no longer used

    18def backspace(display, number):
    19    number = streamlit.session_state['number'][:-1]
    20    streamlit.session_state['number'] = number
    21    show_number(display)
    22
    23
    24def add_decimal():
    

  • I change the on_click and args parameters for the <- button in the add_buttons_to_column_1 function

    41def add_buttons_to_column_1(column_1, display):
    42    column_1.button(
    43        label='<-', key='<-', width='stretch', on_click=on_click,
    44        # on_click=backspace, args=[display, '<-'],
    45        args=[backspace, display],
    46    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'MNO.PQRSTUVWXYZABCD' != 'MNO.PQRSTUVWXYZABC'
    

    and TypeError

    TypeError: backspace() missing 2 required positional arguments: 'display' and 'number'
    
  • I change the backspace function

    18# def backspace(display, number):
    19def backspace():
    20    number = streamlit.session_state['number'][:-1]
    21    streamlit.session_state['number'] = number
    22    # show_number(display)
    

    the test passes

  • I remove the commented lines from the backspace function

    18def backspace():
    19    number = streamlit.session_state['number'][:-1]
    20    streamlit.session_state['number'] = number
    21
    22
    23def add_decimal():
    

  • I change the on_click and args parameters for the +/- button in the add_buttons_to_column_1 function

    40def add_buttons_to_column_1(column_1, display):
    41    column_1.button(
    42        label='<-', key='<-', width='stretch', on_click=on_click,
    43        # on_click=backspace, args=[display, '<-'],
    44        args=[backspace, display],
    45    )
    46    column_1.button(
    47        label='7', key='7', width='stretch', on_click=on_click,
    48        args=[add_number_to_state, display, '7'],
    49    )
    50    column_1.button(
    51        label='4', key='4', width='stretch', on_click=on_click,
    52        args=[add_number_to_state, display, '4'],
    53    )
    54    column_1.button(
    55        label='1', key='1', width='stretch', on_click=on_click,
    56        args=[add_number_to_state, display, '1'],
    57    )
    58    column_1.button(
    59        label='+/-', key='+/-', width='stretch', on_click=on_click,
    60        # on_click=plus_minus, args=[display, '+/-'],
    61        args=[plus_minus, display],
    62    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: '963.0258741' != '-963.0258741'
    

    and TypeError

    TypeError: plus_minus() missing 2 required positional arguments: 'display' and 'number'
    
  • I change the plus_minus function

     8# def plus_minus(display, number):
     9def plus_minus():
    10    if streamlit.session_state['number'].startswith('-'):
    11        number = streamlit.session_state['number'][1:]
    12    else:
    13        number = '-' + streamlit.session_state['number']
    14    streamlit.session_state['number'] = number
    15
    16    # show_number(display)
    

    the test passes

  • I remove the commented lines from the plus_minus function

     8def plus_minus():
     9    if streamlit.session_state['number'].startswith('-'):
    10        number = streamlit.session_state['number'][1:]
    11    else:
    12        number = '-' + streamlit.session_state['number']
    13    streamlit.session_state['number'] = number
    14
    15
    16def backspace():
    
  • I remove the commented lines from the add_buttons_to_column_1 function

    42def add_buttons_to_column_1(column_1, display):
    43    column_1.button(
    44        label='<-', key='<-', width='stretch', on_click=on_click,
    45        args=[backspace, display],
    46    )
    47    column_1.button(
    48        label='7', key='7', width='stretch', on_click=on_click,
    49        args=[add_number_to_state, display, '7'],
    50    )
    51    column_1.button(
    52        label='4', key='4', width='stretch', on_click=on_click,
    53        args=[add_number_to_state, display, '4'],
    54    )
    55    column_1.button(
    56        label='1', key='1', width='stretch', on_click=on_click,
    57        args=[add_number_to_state, display, '1'],
    58    )
    59    column_1.button(
    60        label='+/-', key='+/-', width='stretch', on_click=on_click,
    61        args=[plus_minus, display],
    62    )
    63
    64
    65def add_buttons_to_column_2(column_2, display):
    

That was a lot! I am going to take a nap. I will work on the operations after that.


close the project

  • I close test_streamlit_calculator.py, streamlit_calculator.py in the editor

  • I click in the first terminal, then use q on the keyboard to leave the tests. The terminal goes back to the command line

  • I change directory to the parent of calculator

    cd ..
    

    the terminal is my friend, and shows

    .../pumping_python
    

    I am back in the pumping_python directory

  • I click in the second terminal, then use ctrl+c on the keyboard to close the web server. The terminal goes back to the command line

  • I change directory to the parent of calculator

    cd ..
    

    the terminal is my friend, and shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

I made a website using Streamlit with a

I used while loops and added tests for

code from the chapter

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


what is next?

You now know how to:

Would you like to continue with the operation buttons of the calculator?


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