how to make a calculator 10: part 1


open the project

  • I `change directory`_ to the calculator folder_

    cd calculator
    
  • I make a new test file_ for the Streamlit_ website

    touch tests/test_streamlit_calculator.py
    
  • I add streamlit_ to the requirements.txt file_

    echo "streamlit" >> requirements.txt
    

    Streamlit_ is a Python_ library that is used for making websites, it is not part of `The Python Standard Library`_

  • I check to see what is in my requirements.txt

    cat requirements.txt
    

    the terminal_ shows

    pytest
    pytest-watcher
    flask
    streamlit
    
  • I install the `Python packages`_ that I wrote in the requirements file_

    uv add --requirement requirements.txt
    

    the terminal shows it installed the `Python packages`_

  • I use pytest-watcher to run the tests

    uv run pytest-watcher . --now
    

    the terminal_ shows

    rootdir: .../pumping_python/calculator
    configfile: pyproject.toml
    collected 8 items
    
    tests/test_calculator.py .....                                [ 62%]
    tests/test_calculator_website.py ...                          [100%]
    
    ======================== 8 passed in X.YZs =========================
    

test_streamlit_calculator_title


RED: make it fail


  • I open test_streamlit_calculator.py from the tests folder_ in the editor

  • I add a new test in test_streamlit_calculator.py

    1import unittest
    2
    3
    4class TestStreamlitCalculator(unittest.TestCase):
    5
    6    def test_streamlit_calculator_title(self):
    7        tester = streamlit.testing.v1.AppTest.from_file(
    8            'src/streamlit_calculator.py'
    9        )
    

    the terminal_ shows NameError

    NameError: name 'streamlit' is not defined
    
  • I add NameError to the list of Exceptions seen

     1import unittest
     2
     3
     4class TestStreamlitCalculator(unittest.TestCase):
     5
     6    def test_streamlit_calculator_title(self):
     7        tester = streamlit.testing.v1.AppTest.from_file(
     8            'src/streamlit_calculator.py'
     9        )
    10
    11
    12# Exceptions seen
    13# NameError
    

GREEN: make it pass



REFACTOR: make it better


  • I add more to the test to see what happens when I try to run the application even though src/streamlit_calculator.py is empty

     7    def test_streamlit_calculator_title(self):
     8        tester = streamlit.testing.v1.AppTest.from_file(
     9            'src/streamlit_calculator.py'
    10        )
    11        tester.run()
    

    the test is still green

  • I add an assertion for the title of the application

     7    def test_streamlit_calculator_title(self):
     8        tester = streamlit.testing.v1.AppTest.from_file(
     9            'src/streamlit_calculator.py'
    10        )
    11        tester.run()
    12        self.assertEqual(tester.title, 'Calculator')
    

    the terminal_ shows AssertionError

    AssertionError: ElementList() != 'Calculator'
    
  • I add AssertionError to the list of Exceptions seen

    15# Exceptions seen
    16# NameError
    17# AttributeError
    18# AssertionError
    
  • I make a new file_ in the src folder_ and call it streamlit_calculator.py

  • I open streamlit_calculator.py in the editor

  • I add code to make a streamlit_ application with a title, in streamlit_calculator.py

    1import streamlit
    2
    3
    4def main():
    5    streamlit.title('Calculator')
    

    the terminal_ shows AssertionError

    AssertionError: ElementList() != 'Calculator'
    
  • I add an if statement to run the main function when the module gets called as a script

    4def main():
    5    streamlit.title('Calculator')
    6
    7
    8if __name__ == '__main__':
    9    main()
    

    the terminal_ shows AssertionError

    AssertionError: ElementList(_list=[Title(tag='h1')]) != 'Calculator'
    
    • when I import a module nothing happens until I call or use the things in it

    • if __name__ == '__main__': calls main() only when src/streamlit_calculator.py gets called like a script, for example

      • in the terminal_

        python3 src/streamlit_calculator.py
        
      • or in test_streamlit_calculator.py

        tester = streamlit.testing.v1.AppTest.from_file(
            'src/streamlit_calculator.py'
        )
        tester.run()
        

      it does not get called when the module is imported

    • ElementList(_list=[Title(tag='h1')]) has a list and I know how to work with lists

  • I change the assertion to get the first item from the list, in test_streamlit_calculator.py

    12        self.assertEqual(tester.title[0], 'Calculator')
    

    the terminal_ shows AssertionError

    AssertionError: Title(tag='h1') != 'Calculator'
    
  • I use the value attribute of the Title class

     7    def test_streamlit_calculator_title(self):
     8        tester = streamlit.testing.v1.AppTest.from_file(
     9            'src/streamlit_calculator.py'
    10        )
    11        tester.run()
    12        self.assertEqual(tester.title[0].value, 'Calculator')
    

    the test passes. Time to run the application


how to view the streamlit calculator website

  • I open another terminal_ then use uv_ in the calculator folder_

    uv run streamlit run src/streamlit_calculator.py
    

    the terminal_ 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
    

    it might also show a dialog box like this, and I click on Open in Browser

    Confirm you want to view Streamlit app in browser

    or 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 App with Title

    Success!

  • I click the 3 dots by Deploy on the right hand side

    Streamlit Deploy Menu
  • I click on Settings

  • I click the check marks by Run on save and Wide mode to make sure the website changes as I make changes to the code

    Streamlit Deploy Settings

test_streamlit_calculator_display

I want the calculator to have a place to show results as the user clicks numbers


RED: make it fail


I add a test to see all the attributes of the application

 7    def test_streamlit_calculator_title(self):
 8        tester = streamlit.testing.v1.AppTest.from_file(
 9            'src/streamlit_calculator.py'
10        )
11        tester.run()
12        self.assertEqual(tester.title[0].value, 'Calculator')
13
14    def test_streamlit_calculator_display(self):
15        tester = streamlit.testing.v1.AppTest.from_file(
16            'src/streamlit_calculator.py'
17        )
18        tester.run()
19        self.assertIsNone(tester.main)

the terminal_ shows AssertionError

E       AssertionError: SpecialBlock(
E           type='main',
E           children={
E               0: Title(tag='h1')
E           }
E       ) is not None

I see that the children object is a dictionary. I know how to work with dictionaries.


GREEN: make it pass


  • I add an expectation with the children attribute

    19        self.assertIsNone(tester.main.children, {})
    

    the terminal_ shows AssertionError

    AssertionError: {0: Title(tag='h1')} is not None : {}
    
  • I change assertIsNone_ to assertEqual_

    19        self.assertEqual(tester.main.children, {})
    

    the terminal_ shows AssertionError

    AssertionError: {0: Title(tag='h1')} != {}
    

    the only thing in the application is the title

  • I copy the dictionary from the terminal_ and use it as the expectation

    14    def test_streamlit_calculator_display(self):
    15        tester = streamlit.testing.v1.AppTest.from_file(
    16            'src/streamlit_calculator.py'
    17        )
    18        tester.run()
    19        self.assertEqual(
    20            tester.main.children,
    21            {0: Title(tag='h1')}
    22        )
    

    the terminal_ shows NameError

    NameError: name 'Title' is not defined
    
  • I change the Title object to tester.title[0]

    19        self.assertEqual(
    20            tester.main.children,
    21            {0: tester.title[0]}
    22        )
    

    the test passes


REFACTOR: make it better


  • I add a `streamlit container`_ to the main function in streamlit_calculator.py

    4def main():
    5    streamlit.title('Calculator')
    6    streamlit.container()
    

    the terminal_ shows AssertionError

    AssertionError: {0: Title(tag='h1'), 1: Block(
       type='flex_container'
    )} != {0: Title(tag='h1')}
    

    there is a new key-value pair because I added something to the application

  • I change the assertion in test_streamlit_calculator.py

    19        self.assertEqual(
    20            tester.main.children,
    21            {
    22                0: tester.title[0],
    23                1: Block(type='flex_container'),
    24            }
    25        )
    

    the terminal_ shows NameError

    NameError: name 'Block' is not defined
    
  • I change the assertion to get the `streamlit Block object`_

    19        self.assertEqual(
    20            tester.main.children[1],
    21            {
    22                0: tester.title[0],
    23                # 1: Block(type='flex_container')
    24            }
    25        )
    

    the terminal_ shows AssertionError

    AssertionError: Block(
       type='flex_container'
    )
    
  • I use the __dict__ attribute to get the `streamlit Block object`_ as a dictionary

    19        self.assertEqual(
    20            tester.main.children[1].__dict__,
    21            {
    22                0: tester.title[0],
    23                # 1: Block(type='flex_container')
    24            }
    25        )
    

    the terminal_ shows AssertionError

    AssertionError: {'children': {}, 'proto': flex_container {[503 chars] )
    
  • I set maxDiff_ to None

    18        tester.run()
    19        self.maxDiff = None
    20        self.assertEqual(
    21            tester.main.children[1].__dict__,
    22            {
    23                0: tester.title[0],
    24                # 1: Block(type='flex_container')
    25            }
    26        )
    

    the terminal_ shows the full difference

  • I copy the dictionary, remove the extra characters with Find and Replace (ctrl+H (Windows_) or command+option+F (MacOS_)) and use it as the expectation

    20        self.assertEqual(
    21            tester.main.children[1].__dict__,
    22            {
    23                'children': {},
    24                'proto': flex_container {
    25                    gap_config {
    26                        gap_size: SMALL
    27                    }
    28                    direction: VERTICAL
    29                    justify: JUSTIFY_START
    30                    align: ALIGN_START
    31                }
    32                height_config {
    33                    use_content: true
    34                }
    35                width_config {
    36                    use_stretch: true
    37                },
    38                'root': {
    39                    0: SpecialBlock(
    40                        type='main',
    41                        children={
    42                            0: Title(tag='h1'),
    43                            1: Block(
    44                                type='flex_container'
    45                            )
    46                        }
    47                    ),
    48                    1: SpecialBlock(
    49                        type='sidebar'
    50                    ),
    51                    2: SpecialBlock(
    52                        type='event'
    53                    )
    54                },
    55                'type': 'flex_container'
    56            }
    57        )
    58
    59
    60# Exceptions seen
    

    the terminal_ shows SyntaxError_

    SyntaxError: invalid syntax. Perhaps you forgot a comma?
    

    this dictionary has too many things

  • I add SyntaxError_ to the list of Exceptions seen

    60# Exceptions seen
    61# NameError
    62# AttributeError
    63# AssertionError
    64# SyntaxError
    
  • I change the assertion to use the proto attribute since it looks like a dictionary

    20        self.assertEqual(
    21            tester.main.children[1].proto,
    22            {}
    23        )
    

    the terminal_ shows AssertionError

    E       AssertionError: flex_container {
    E         gap_config {
    E           gap_s[155 chars]ue
    E       }
    E        != {}
    
  • I use the flex_container attribute instead

    20        self.assertEqual(
    21            tester.main.children[1].proto.flex_container,
    22            {}
    23        )
    

    the terminal_ shows AssertionError

    E       AssertionError: gap_config {
    E         gap_size: SMALL
    E       }
    E       directio[49 chars]TART
    E        != {}
    
  • I use the gap_size attribute directly

    20        self.assertEqual(
    21            (
    22                tester.main.children[1].proto
    23                      .flex_container
    24                      .gap_config.gap_size
    25            ),
    26            {}
    27        )
    

    the terminal_ shows AssertionError

    AssertionError: 1 != {}
    
  • I change the expectation

    20        self.assertEqual(
    21            (
    22                tester.main.children[1].proto
    23                      .flex_container
    24                      .gap_config.gap_size
    25            ),
    26            1
    27        )
    

    the test passes

  • I remove self.maxDiff

    14    def test_streamlit_calculator_display(self):
    15        tester = streamlit.testing.v1.AppTest.from_file(
    16            'src/streamlit_calculator.py'
    17        )
    18        tester.run()
    19
    20        self.assertEqual(
    21            (
    22                tester.main.children[1].proto
    23                      .flex_container
    24                      .gap_config.gap_size
    25            ),
    26            1
    27        )
    28
    29
    30# Exceptions seen
    
  • I add a variable for the flex_container object

    14    def test_streamlit_calculator_display(self):
    15        tester = streamlit.testing.v1.AppTest.from_file(
    16            'src/streamlit_calculator.py'
    17        )
    18        tester.run()
    19
    20        display = (
    21            tester.main.children[1].proto
    22                  .flex_container
    23        )
    24        self.assertEqual(
    
  • I use the variable in the assertion

    24        self.assertEqual(
    25            # (
    26            #     tester.main.children[1].proto
    27            #           .flex_container
    28            #           .gap_config.gap_size
    29            # ),
    30            display.gap_config.gap_size, 1
    31        )
    

    the test is still green

  • I remove the commented lines

    24        self.assertEqual(display.gap_config.gap_size, 1)
    
  • I add an assertion for the next attribute of the flex_container object

    24        self.assertEqual(display.gap_config.gap_size, 1)
    25        self.assertEqual(display.direction, '')
    

    the terminal_ shows AssertionError

    AssertionError: 1 != ''
    
  • I change the expectation

    25        self.assertEqual(display.direction, 1)
    

    the test passes

  • I add an assertion for the next attribute of the flex_container object

    24        self.assertEqual(display.gap_config.gap_size, 1)
    25        self.assertEqual(display.direction, 1)
    26        self.assertEqual(display.justify, '')
    

    the terminal_ shows AssertionError

    AssertionError: 1 != ''
    
  • I change the expectation

    26        self.assertEqual(display.justify, 1)
    

    the test passes

  • I add the last attribute of the flex_container object

    24        self.assertEqual(display.gap_config.gap_size, 1)
    25        self.assertEqual(display.direction, 1)
    26        self.assertEqual(display.justify, 1)
    27        self.assertEqual(display.align, '')
    

    the terminal_ shows AssertionError

    AssertionError: 1 != ''
    
  • I change the expectation

    27        self.assertEqual(display.align, 1)
    28
    29
    30# Exceptions seen
    

    the test passes. All the attributes have 1 as their value which stands for different things in each case, they are called enums_

    • gap_config.gap_size - 1 - SMALL

    • direction - 1 - VERTICAL

    • justify - 1 - JUSTIFY_START

    • align - 1 - ALIGN_START

  • I go to the browser and things look the same as before. I need to add a border

  • I add an assertion for a border in test_streamlit_calculator.py

    24        self.assertEqual(display.gap_config.gap_size, 1)
    25        self.assertEqual(display.direction, 1)
    26        self.assertEqual(display.justify, 1)
    27        self.assertEqual(display.align, 1)
    28        self.assertEqual(display.border, '')
    

    the terminal_ shows AssertionError

    AssertionError: False != ''
    
  • I change the assertEqual_ to assertFalse_ and remove the expectation

    28        self.assertFalse(display.border)
    

    the test passes

  • I change the assertFalse_ to assertTrue_

    14    def test_streamlit_calculator_display(self):
    15        tester = streamlit.testing.v1.AppTest.from_file(
    16            'src/streamlit_calculator.py'
    17        )
    18        tester.run()
    19
    20        display = (
    21            tester.main.children[1].proto
    22                  .flex_container
    23        )
    24        self.assertEqual(display.gap_config.gap_size, 1)
    25        self.assertEqual(display.direction, 1)
    26        self.assertEqual(display.justify, 1)
    27        self.assertEqual(display.align, 1)
    28        self.assertTrue(display.border)
    29
    30
    31# Exceptions seen
    

    the terminal_ shows AssertionError

    AssertionError: False is not true
    
  • I add a border to the container in streamlit_calculator.py

    4def main():
    5    streamlit.title('Calculator')
    6    streamlit.container(border=True)
    

    the test passes

  • I go to the browser and click refresh

    Calculator Streamlit Display

    there is a box under the Calculator title


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_ 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_ shows

    .../pumping_python
    

    I am back in the pumping_python directory_


review

I made a website using Streamlit_ with a title and a display


what is next?

You now know how to:

Would you like to continue with adding buttons to the calculator?