how to make a calculator 10: part 1
open the project
I `change directory`_ to the
calculatorfolder_cd calculatorI make a new test file_ for the Streamlit_ website
touch tests/test_streamlit_calculator.pyI add streamlit_ to the
requirements.txtfile_echo "streamlit" >> requirements.txtStreamlit_ 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.txtcat requirements.txtthe terminal_ shows
pytest pytest-watcher flask streamlitI install the `Python packages`_ that I wrote in the requirements file_
uv add --requirement requirements.txtthe terminal shows it installed the `Python packages`_
I use
pytest-watcherto run the testsuv run pytest-watcher . --nowthe 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.pyfrom thetestsfolder_ in the editorI add a new test in
test_streamlit_calculator.py1import 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 )NameError: name 'streamlit' is not definedI 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
I add an `import statement`_ at the top of the file_
1import streamlit 2import unittest 3 4 5class TestStreamlitCalculator(unittest.TestCase):the terminal_ shows AttributeError
AttributeError: module 'streamlit' has no attribute 'testing'I add AttributeError to the list of Exceptions seen
13# Exceptions seen 14# NameError 15# AttributeErrorI add more to the `import statement`_
1import streamlit.testing 2import unittestthe terminal_ shows AttributeError
AttributeError: module 'streamlit.testing' has no attribute 'v1'I add
v1to the `import statement`_1import streamlit.testing.v1 2import unittestthe test passes
streamlit.testing.v1.AppTest.from_filetests the website I am making with streamlit_AppTest_ is a class from
v1intestingin the `streamlit library`_.from_fileuses the `from_file method`_ to run the Python module that I use to make the application
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.pyis empty7 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# AssertionErrorI make a new file_ in the
srcfolder_ and call itstreamlit_calculator.pyI open
streamlit_calculator.pyin the editorI add code to make a streamlit_ application with a title, in
streamlit_calculator.py1import streamlit 2 3 4def main(): 5 streamlit.title('Calculator')the terminal_ shows AssertionError
AssertionError: ElementList() != 'Calculator'I add an if statement to run the
mainfunction when the module gets called as a script4def 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__':callsmain()only whensrc/streamlit_calculator.pygets called like a script, for examplein the terminal_
python3 src/streamlit_calculator.pyor in
test_streamlit_calculator.pytester = 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.py12 self.assertEqual(tester.title[0], 'Calculator')the terminal_ shows AssertionError
AssertionError: Title(tag='h1') != 'Calculator'I use the
valueattribute of theTitleclass7 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
calculatorfolder_uv run streamlit run src/streamlit_calculator.pythe 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:8501it might also show a dialog box like this, and I click on
Open in Browser
or I use ctrl/option on the keyboard and click on
http://localhost:8501with the mouse to open the browser and it shows
Success!
I click the 3 dots by
Deployon the right hand side
I click on
SettingsI click the check marks by
Run on saveandWide modeto make sure the website changes as I make changes to the code
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
childrenattribute19 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 )NameError: name 'Title' is not definedI change the
Titleobject totester.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
mainfunction instreamlit_calculator.py4def 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.py19 self.assertEqual( 20 tester.main.children, 21 { 22 0: tester.title[0], 23 1: Block(type='flex_container'), 24 } 25 )NameError: name 'Block' is not definedI 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 dictionary19 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] )-
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 expectation20 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 seenthe 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# SyntaxErrorI change the assertion to use the
protoattribute since it looks like a dictionary20 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_containerattribute instead20 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_sizeattribute directly20 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.maxDiff14 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 seenI add a variable for the
flex_containerobject14 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_containerobject24 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_containerobject24 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_containerobject24 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 seenthe 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 - SMALLdirection- 1 - VERTICALjustify- 1 - JUSTIFY_STARTalign- 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.py24 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 seenthe terminal_ shows AssertionError
AssertionError: False is not trueI add a border to the container in
streamlit_calculator.py4def main(): 5 streamlit.title('Calculator') 6 streamlit.container(border=True)the test passes
I go to the browser and click refresh
there is a box under the
Calculatortitle
close the project
I close
test_streamlit_calculator.py,streamlit_calculator.pyin the editorI 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
calculatorcd ..the terminal_ shows
.../pumping_pythonI am back in the
pumping_pythondirectory_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
calculatorcd ..the terminal_ shows
.../pumping_pythonI am back in the
pumping_pythondirectory_
review
I made a website using Streamlit_ with a title and a display
what is next?
You now know how to:
Build a website with streamlit_
How to test the parts of the website
Would you like to continue with adding buttons to the calculator?