Advanced Python
In-Class Exercise Solutions, Session 3

Notes if you are using Jupyter Notebook: to call exit() from a notebook, please use sys.exit() (requires import sys); if a strange error occurs, it may be because Jupyter retains variables from all executed cells. To reset the notebook, click 'Restart Kernel' (the circular arrow) -- this will reset variables, but will not undo any changes made.

REQUESTS: VIEWING RESPONSE

Ex. 3.1 Issue a GET request, view response text with .text. Use requests.get() with a url to retrieve a response from the weather service. Print the .text attribute to see the body of the response.
import requests

url = 'https://forecast.weather.gov/product.php?site=NWS&issuedby=CTP&product=AFD'   # str

response = requests.get(url)        # requests.response object

print(response.text)                # str, '<!DOCTYPE html PUBLIC "-//W3C...' (start of HTML page)
 
Ex. 3.2 Issue a GET request, view headers. Print the .headers attribute of the response to see the headers sent back by the weather server.

You can also loop through the dict-like response.headers to see each key/value pair clearly.

import requests

url = 'https://forecast.weather.gov/product.php?site=NWS&issuedby=CTP&product=AFD'#

response = requests.get(url)                     # requests.response object

for key, val in response.headers.items():        # str 'Server', str 'Apache' (first item)
    print(f'{key}:  {val}')
 
Ex. 3.3 Issue a GET request, view status code. Print the .status_code attribute from the response,

Next, change one of the parameters to see how the code changes; also, change the spelling of the word 'product'. In addition, use requests.status_codes._codes[response.status_code] with the status code to see the meaning of the response code.

import requests

url = 'https://forecast.weather.gov/product.php?site=NWS&issuedby=CTP&product=AFD'#

response = requests.get(url)        # requests.response object

print(response.status_code)         # int, 200

print(requests.status_codes._codes[response.status_code])
 

REQUESTS: ENCODED (text) and UNENCODED (bytes) RESPONSE

Ex. 3.4 Issue a web request, view response.text (decoded) and response.content (undecoded).

With the first URL, check the type of response.text and the type of response.content. Each contains the response content, but one is decoded as a string and the other is encoded as bytes. Next, check the value of response.encoding to see what encoding the google page uses. The check the same with the Microsoft English page and the Microsoft French page.

import requests

url = 'http://www.google.com'                   # str, 'http://www.google.com'

url = 'https://www.microsoft.com/en-us/'        # (url for Microsoft in English)
url = 'https://www.microsoft.com/fr-fr/'        # (url for Microsoft in French)

response = requests.get(url)                    # requests.response object

print(type(response.text))                      # <class 'str'>
print(type(response.content))                   # &tl;class 'bytes'>

print(response.encoding)                        # UTF-8

.text returns the response text decoded to string; .content returns the encoded text, a bytestring. .encoding tells us which encoding is used. This is either the encoding that was returned with the page (in the Content-type: header) or the "apparent encoding" which is the value returned from chardet.

 
Ex. 3.5 Saving an image, sound or zip file. Issue the below web request, open a local file with 'wb' (meaning, "write as bytes") and write the unencoded text to the file. (To access unencoded text, use response.content instead of reponse.text).
import requests

url = 'https://davidbpython.com/advanced_python/supplementary/python.png'# a URL to an image

response = requests.get(url)                # requests.response object

image_bytes = response.content              # bytes
print(f'{len(image_bytes)} bytes')

wfh = open('../python_strip.png', 'wb')        # 'file' object open for writing bytes
wfh.write(image_bytes)                      # int, size of bytes
wfh.close()

Check the folder where this script or notebook is located; you should see the python_strip.png image there.

 

LAB

Ex. 3.6 Retrieve a page and save to disk.

Download the following page and save to disk in a new file (write response.text to the file). Make sure to close the file. After writing the file, please open the file in a browser (use your browser's File > Open File menu command). In another tab on your browser, open the url directly. Compate the two pages. (The locally saved file will not have correct formatting or colors - this is because these are supplied by a separate CSS file that was not downloaded.)

import requests

url = 'https://pycoders.com/'           # str, 'https://pycoders.com'

response = requests.get(url)            # requests.response object

text = response.text                    # str, '\n\n<!doctype html>\n<html lang=...' (start of HTML page)

wfh = open('../pycoders.html', 'w')        # 'file' object
wfh.write(text)                         # int, size of data written
wfh.close()
 
Ex. 3.7 View response status code.

Continuing the previous exercise, use the .status_code attribute to view the response status. Now change the URL to something you wouldn't expect to be correct, run and view the status code.

import requests

url = 'https://pycoders.com/'           # str, 'https://pycoders.com'

response = requests.get(url)            # requests.response object

text = response.text                    # str, '\n\n<!doctype html>\n<html lang=...' (start of HTML page)
print(f'status code:  {response.status_code}')

wfh = open('../pycoders.html', 'w')        # 'file' object
wfh.write(text)                         # int, (size of data written)
wfh.close()
 
Ex. 3.8 View response headers.

Continuing the previous exercise, use the .headers attribute to retrieve the headers coming back as a dict from the response. Loop through and print each key/value pair in the dict.

import requests

url = 'https://pycoders.com/'           # str, 'https://pycoders.com/'

response = requests.get(url)            # requests.response object

text = response.text                    # str, '\n\n<!doctype html>\n<html lang=...' (start of HTML page)

print(f'status code:  {response.status_code}')
print(f'headers:  {response.headers}')

wfh = open('../pycoders.html', 'w')        # 'file' object
wfh.write(text)                         # int, (size of data written)
wfh.close()
 
Ex. 3.9 Retrieve and save an image.

Use the below URL to retrieve an image from the internet, and save it locally to a filename of your choice (make sure it has a .jpg file extension so it is recognized properly by your image viewer). The data must be read and written as binary data (i.e., not plaintext). Therefore when writing, use 'wb' (write binary) instead of 'w'.

import requests

url = 'https://cdn.vox-cdn.com/uploads/chorus_image/image/32167377/monty-python-3by2.0.jpg'#

response = requests.get(url)         # requests.response object

content = response.content           # bytes

wfh = open('../monty.jpg', 'wb')        # 'file' object, open to write bytes
wfh.write(content)                   # int, (size of data written)
wfh.close()
 

REQUESTS: CONFIGURING THE REQUEST

Ex. 3.10 Demo exercise: issue a request and view elements of the request.

First, issue the following request and view the headers reflected back. Next, uncomment the 'headers' lines to see how http_reflect "sees" you (i.e., what browser and platform it thinks you are requesting from). Finally, change 'text/plain' to 'text/html' and see http_reflect respond with HTML instead of plain text.

import requests

spoof_browser = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"

response = requests.get('http://davidbpython.com/cgi-bin/http_reflect',
                         headers={        # requests.response object
                                    'User-Agent':  spoof_browser,
                                    'Accept':      'text/plain',
                                 }
                        )                    # requests.response object

print(response.text)
 
Ex. 3.11 Demo exercise: issue a request and send parameters with .get().

In the below program, add the params= argument with the dict 'my_params' to send key/value pairs to the http_reflect service as part of the query string. In the response, find the parameters you sent.

import requests

my_params = {'a': 1, 'b': 'hello'}               # dict, {'a': 1, 'b': 'hello'}

response = requests.get('http://davidbpython.com/cgi-bin/http_reflect',#
                        params=my_params)        # requests.response object

print(response.text)
 
Ex. 3.12 Issue a request and send parameters with .post().

In the below program, add the data= argument with the dict 'my_data' to send key/value pairs to the http_reflect service as part of the body of the request. In the response, find the parameters you sent.

import requests

my_data = {'a': 1, 'b': 'hello'}              # dict, {'a'}

response = requests.post('http://davidbpython.com/cgi-bin/http_reflect',
                         data=my_data)        # requests.response object

print(response.text)
 

REQUESTS: UPLOADING A FILE

Ex. 3.13 Upload a file to a server program. Add the files= parameter to .post() to upload the below file.

Note that the file has been opened with 'rb', which stands for 'read binary': we are uploading encoded bytes.

import requests

# open file for reading without decoding (returns a bytestring)
file_bytes = open('../test_file.txt', 'rb')             # 'file' object

file_dict = { 'file':  ('test_file.txt', file_bytes,
                        'text/plain') }                 # dict of dicts

response = requests.post('https://davidbpython.com/cgi-bin/http_reflect',
                         files=file_dict)               # requests.response object

print(response.text)
 

LAB

Ex. 3.14 Demo exercise: issue request with headers, parameters and data.

Please note below the approach for setting headers, parameters and data in a request, and note that each have been successfully sent to (and reflected back from) the server.

import requests

# link to my reflection program
url = 'http://davidbpython.com/cgi-bin/http_reflect'     # str, 'http ...'

div_bar = '=' * 10                                       # str, '=========='


# headers, parameters and message data to be passed to request
# change to 'text/html' for an HTML response
header_dict =  { 'Accept': 'text/plain' }                # dict, {'Accept': ...}
param_dict =   { 'key1': 'val1', 'key2': 'val2' }        # dict, {'key1' ... }
data_dict =    { 'text1': "We're all out of gouda." }    # dict, {'text1' ... }


# a GET request (change to .post for a POST request)
response = requests.get(url, headers=header_dict,
                             params=param_dict,
                             data = data_dict)           # requests.response object


# status of the response (OK, Not Found, etc.)
response_status = response.status_code                   # int, 200

# headers sent by the server
response_headers = response.headers                      # dict, {'Content-Type': ... }

# body sent by server
response_text = response.text                            # str, '***** HTTP REFLECT ...'

# outputting response elements (status, headers, body)

# response status
print(f'{div_bar} response status {div_bar}\n')
print(response_status)
print(); print()

# response headers
print(f'{div_bar} response headers {div_bar}\n')
for key in response_headers:                              # str, 'Content-type' (first item)
    print(f'{key}:  {response_headers[key]}\n')
print()

# response body
print(f'{div_bar} response body {div_bar}\n')
print(response_text)
 
Ex. 3.15 Use a parameter to configure a web API request.

Complete a request from the below URL by using the parameter term with a value that is a term you would like to search on urbandictionary.com. (Warning: urbandictionary.com definitions can use offensive language. For a "clean" set of definitions, try the definition for squee as shown below.) Read and save the resulting JSON text to a file.

import requests

url = 'http://api.urbandictionary.com/v0/define'    # str, 'http://api...'

params = {'term':  'squee'}                         # dict, {'term': 'squee'}

response = requests.get(url, params=params)         # requests.response object

wfh = open('../squee.json', 'w')                    # 'file' object

wfh.write(response.text)                            # int, (bytes written)

wfh.close()
 
Ex. 3.16 Read and parse JSON data.

Continue the previous program by using the .json() method of the response object to read the data as a JSON object. You should find that the object is a dict, that it has one key list with a value that is a list of, and that if you loop through this list of dicts, the key author is the author's name, and definition is the definition.

import requests

url = 'http://api.urbandictionary.com/v0/define'    # str, 'http://api...'

params = {'term':  'squee'}                         # dict, {'term': 'squee'}

response = requests.get(url, params=params)         # requests.response object

obj = response.json()                               # dict, {'list':[{'definition':'verb ...}

print(type(obj))                                    # <class 'dict'>
print(obj.keys())                                   # dict_keys(['list'])
print(type(obj['list']))                            # <class 'list'>

for dftn in obj['list']:                            # dict, [{'definition'  ...
    print(f"author:  {dftn['author']}")
    print(f"definition:  {dftn['definition']}")
    print('==============================')
    print()
 
Ex. 3.17 Read and parse CSV data.

Request the following data and retrieve as text. Load the text as a csv.reader object, then read line-by-line and print out each parsed line. (Hint: to load the data into text into csv.reader, split the text using .splitlines() first, and pass this list to csv.reader().)

import csv
import requests

url = 'http://davidbpython.com/advanced_python/supplementary/dated_file.csv'

response = requests.get(url)    # requests.response object

text = response.text            # str, '10/15/2018,A,B,28.3
\n09/03 ...'
lines = text.splitlines()       # list, ['10/15/2018,A,B,28.3', '09/03...']

reader = csv.reader(lines)      # csv.reader object

for row in reader:              # str, '<!DOCTYPE html>'
    print(row)
 

WEB SCRAPING

Ex. 3.18 BeautifulSoup object: the below code reads a string read from an html file and parses the file and its tags into a BeautifulSoup object.

Explore the following attributes of the object named 'soup':

  • print the type of the object
  • print the object itself
  • print the .text attribute

  • from bs4 import BeautifulSoup
    
    scrapee = '../dormouse.html'                # str, 'dormouse.html'
    
    text = open(scrapee).read()                 # text, "<'!doctype html>\n>html> ... "
    soup = BeautifulSoup(text, 'html.parser')   # bs4.BeautifulSoup object
    
     
    Ex. 3.19 "first tag" attribute access; .text attribute: access the object for soup.title, soup.body, soup.p, soup.meta.

    Print the type of 1 of these objects. Print the .text attribute of each of these objects.

    Suggested Solution:
    from bs4 import BeautifulSoup
    
    scrapee = '../dormouse.html'                # str, 'dormouse.html'
    
    text = open(scrapee).read()                 # text, "<'!doctype html>\n<html> ... "
    soup = BeautifulSoup(text, 'html.parser')   # bs4.BeautifulSoup object
    
    print(soup.title.text)
    print(type(soup.title.text))
    
    print(soup.p.text)
    
    print(soup.meta.text)
    
     
    Ex. 3.20 Access tag by parameter using a dict. Use a dict with .find() to specify a 'class' parameter value (e.g. {'class': 'story_title'})
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    scrapee = '../dormouse.html'                    # str, 'dormouse.html'
    
    text = open(scrapee).read()                     # text, "<'!doctype html>\n<html> ... "
    soup = BeautifulSoup(text, 'html.parser')       # bs4.BeautifulSoup object
    
    tag = soup.find('p', attrs={'class': 'story_title'})  # bs4.Tag: <p class="story_title"...>
    
     
    Ex. 3.21 Individual parameter values: for a tag that has them, use a subscript to access parameter values of a tag (e.g. tag['value'] for the first meta tag)
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    scrapee = '../dormouse.html'                # str, 'dormouse.html'
    
    text = open(scrapee).read()                 # text, "<'!doctype html>\n<html> ... "
    soup = BeautifulSoup(text, 'html.parser')   # bs4.BeautifulSoup object
    
    ts = soup.meta['value']                     # str, '2019-08-06 12:23:07'
    
     
    Ex. 3.22 Find all parameter values: for those tags that have them, use .find_all() to access multiple tags with the same criteria.
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    scrapee = '../dormouse.html'                # str, 'dormouse.html'
    
    text = open(scrapee).read()                 # text, "<'!doctype html>\n<html> ... "
    soup = BeautifulSoup(text, 'html.parser')   # bs4.BeautifulSoup object
    
    tags = soup.find_all('p')    # [bs4.Tag: <p class="story_title" ...>, <p class="story"]
    
     

    LAB

    The following exercises will parse the test_scrape.html page in this week's session folder.

    <html>
      <head>
        <title>This is a page title.</title>
      </head>
      <body>
        <h3 class="heading">This is a page heading.</h3>#
        <p>This is some text.</p>
        <p>This is some more text.</p>
        <h3 class="midpage">This is a midpage heading.</h3>#
        <p>This is even more text.</p>
        <div content="Some div parameter content we want!">#
        Some div text!
        </div>
      </body>
    </html>
    Solutions below start with this text:
    from bs4 import BeautifulSoup
    
    fname = '../test_scrape.html'                    # str, '../test_scrape.html'
    
    fh = open(fname)                                 # 'file' object
    text = fh.read()                                 # str, '<html>\n  <head>...'
    
    soup = BeautifulSoup(text, 'html.parser')        # bs4.BeautifulSoup object
    
    Ex. 3.23 In test_scrape.html, scrape the page title (the title within the <title> tags) and print it.
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    fname = '../test_scrape.html'                    # str, '../test_scrape.html'
    
    fh = open(fname)                                 # 'file' object
    text = fh.read()                                 # str, '<html>\n  <head>...'
    
    soup = BeautifulSoup(text, 'html.parser')        # bs4.BeautifulSoup object
    
    tag = soup.find('title')                         # bs4.Tag: <title>This is...
    print(tag.text)
    

    .find(name) will find the very first tag with this name. (The name of a tag is the first name you see after the <, as in <title>). We can use find() because we know there is only one <title> tag.

     
    Ex. 3.24 Scrape and print the <h3> tag in the middle of the page (not the first <h3> tag)
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    fname = '../test_scrape.html'                    # str, '../test_scrape.html'
    
    fh = open(fname)                                 # 'file' object
    text = fh.read()                                 # str, '<html>\n  <head>...'
    
    soup = BeautifulSoup(text, 'html.parser')        # bs4.BeautifulSoup object
    
    tag = soup.find('h3', attrs={'class': 'midpage'})      # bs4.Tag: <h3 class="midpage">This is...
    print(tag.text)
    

    This exercise demonstrates how to locate a tag by its name + parameter value. There are two <h3> tags but only one has a class="midpage" parameter value. We're using a dict to identify the parameter value because class is a meaningful word in Python syntax (the class definition) and Python would be confused if we used the other form (soup.find('h3', class="midpage")).

     
    Ex. 3.25 Scrape and print the <div> text as well as the "class" parameter value.
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    fname = '../test_scrape.html'               # str, '../test_scrape.html'
    
    fh = open(fname)                            # 'file' object
    text = fh.read()                            # str, '<html>\n  <head>...'
    
    soup = BeautifulSoup(text, 'html.parser')   # bs4.BeautifulSoup object
    
    tag = soup.find('div')                      # bs4.Tag: <div content="Some ...>
    print(tag['content'])
    print(tag.text)
    

    This exercise demonstrates the use of the .text attribute (to retrieve the text between open and close tags) and subscript syntax, which retrieves the value for a particular parameter.

     
    Ex. 3.26 Scrape and print each of the <p> tag texts.
    Suggested Solution:
    from bs4 import BeautifulSoup
    
    fname = '../test_scrape.html'                    # str, '../test_scrape.html'
    
    fh = open(fname)                                 # 'file' object
    text = fh.read()                                 # str, '<html>\n  <head>...'
    
    soup = BeautifulSoup(text, 'html.parser')        # bs4.BeautifulSoup object
    
    tags = soup.find_all('p')       # [bs4.Tag: , bs4.Tag: , bs4.Tag:  ...]
    for tag in tags:                # bs4.Tag: <p>This is some text.</p>
        print(tag.text)
    

    This exercise demonstrates the use of find_all() to retrieve all tags within a page. Once you review the NYTimes page we are expected to scrape, you'll see that this is what we're doing for the homework assignment as well.

     

    WORKING WITH ENCODINGS AND UNICODE

    Ex. 3.27 Use ord() and chr() to encode characters to integers and decode integers to characters.

    Use the ord() function to convert this character to an integer; then use the chr() function to convert back from integer to character.

    char = 'A'              # str, 'A'
    
    idx = ord(char)         # int, 65
    char2 = chr(idx)        # str, 'A'
    print(idx, char2)
    
     
    Ex. 3.28 Demonstration: note how you can use integers to retrieve characters.

    You can use integers to retrieve characters. Run the following program:

    for idx in range(65, 70):     # int, 65
        print(chr(idx))           # str, 'A'
    
     
    Ex. 3.29 Use str.encode() and bytes.decode() to convert a string to a bytestring and back to string.

    greet.encode() should include an encoding ('ascii', 'latin-1' or 'utf-8'):

    greet = 'Hello, world!'                # str, 'Hello, world!'
    
    bytestr = greet.encode('ascii')        # bytes
    print(bytestr)
    
    # subscript the bytestring to see individual characters
    print(bytestr[0])                      # int, 72
    
    
    # now call bytestr.decode() with the same encoding to see the string again
    print(bytestr.decode('ascii'))
    
     
    Ex. 3.30 Encode a latin-1 string to bytes and back to string, then try to encode as ascii

    The below string contains a non-ascii character. Encode into the following encodings: 'latin-1', 'utf-8' and 'ascii'.

    string = 'voilà'                      # str, 'voilà'
    
    bytestr = string.encode('latin-1')    # bytes
    
    print(bytestr)
    
    bytestr = string.encode('utf-8')      # bytes
    bytestr = string.encode('ascii')      # UnicodeEncodeError: 'ascii' codec can't
    
                                          # encode character '\xe0' in position 4:
                                          # ordinal not in range(128)
    
     
    Ex. 3.31 Open a file in various encodings. The text of the following file is French, and we are opening it in utf-8 (Python's default). Try to open the file with encoding='latin-1' and encoding='ascii'.
    filename = '../la_vie.txt'                   # str, '../la_vie.txt'
    
    fh = open(filename, encoding='ascii')        # 'file' object
    
    print(fh.read())
    

    'utf-8' is the correct encoding. Note that 'latin-1' actually translates some characters differently, as it is not a subset of utf-8 but has different encodings. 'ascii' fails because of the accented French characters, which are not part of the ascii set.

     
    Ex. 3.32 Use the chardet library to "sniff" an encoding. Given the below strings, use chardet.detect() with each of the two bytestring to get Python's best guess as to its encoding. Print the resulting dict from .detect().
    import chardet
    
    bytestr1 = 'voilà'.encode('utf-8')        # bytes
    
    bytestr2 = 'hello'.encode('utf-8')        # bytes
    
    
    print(chardet.detect(bytestr1))           # {'encoding': 'utf-8', 'confidence': 0.505, 'language': ''}
    print(chardet.detect(bytestr2))           # {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}
    

    Note that chardet is only partly sure that this is a unicode string. What else could it be? latin-1? It's uncertain, because strings don't contain encoding information.

     

    optional: FLASK

    In these exercises, we'll create a basic Flask website with pages -- the "puppy mood" application as described in the slides. Of course if you don't particularly want to look at pictures of puppies and their moods, you can come up with your own application idea -- one that uses user input to choose and display images.

    Ex. 3.33 Create a simple Flask 'hello world' app that greets the user, and start the application.

    This solution is provided as hello.py in this week's course materials.

    Suggested Solution:
    #!/usr/bin/env python
    
    import flask
    
    # a Flask "god" object
    # (available everywhere, "master controller"
    #  of flask behavior and configuration)
    app = flask.Flask(__name__)
    
    
    # called when visiting web URL 127.0.0.1:5000/hello/
    # expected to return a string (usu. the HTML to display)
    @app.route('/hello')
    def hello_world():
        print('*** DEBUG:  inside hello_world() ***')
        return '<PRE>Hello, World!</PRE>'
    
    
    
    if __name__ == '__main__':
        app.run(debug=True, port=5000)
        # app starts serving in debug mode on port 5000
    
     
    Ex. 3.34 Call the application from a browser.
     
    Ex. 3.35 (Exploratory exercise): view log messages at command line.
     
    Ex. 3.36 Add another @app.route() function.
    Suggested Solution:
    #!/usr/bin/env python
    
    import flask
    app = flask.Flask(__name__)
    
    @app.route('/hello')
    def hello_world():
        print('*** DEBUG:  inside hello_world() ***')
        return 'Hello, World!'
    
    @app.route('/goodbye')
    def goodbye_world():
        return "Goodbye, now!  Come on back, y'hear?"
    
    if __name__ == '__main__':
        app.run(debug=True, port=5000)
    
     
    Ex. 3.37 Add templates for /hello and /goodbye so that when the user visits, it returns a template rather than a string.
    Here is my directory structure:
    flask_test/
               hello.py
               templates/
                         hello.html
                         goodbye.html
    Here is my script:
    #!/usr/bin/env python
    
    import flask
    app = flask.Flask(__name__)
    
    @app.route('/hello')
    def hello_world():
        print('*** DEBUG:  inside hello_world() ***')
        return flask.render_template('hello.html')
    
    @app.route('/goodbye')
    def goodbye_world():
        return flask.render_template('goodbye.html')
    
    if __name__ == '__main__':
        app.run(debug=True, port=5000)
    
     
    Ex. 3.38 Add a "static" HTML page with a link that calls the hello/ function, and another link that calls the goodbye/ function.
    Here is my file/directory structure:
    flask_test/
               hello.py
               launch.html
               templates/
                         hello.html
                         goodbye.html
     
    Ex. 3.39 Add an <A HREF=""> link on the hello/ page that calls the goodbye/ page, and one on the goodbye/ page that calls the hello/ page.
    Here is my hello.html template:
    <html>
      <head><title>A Greeting</title></head>
      <body>
          <h1>Hello, Template!</h1>
          <A HREF="{{ url_for('goodbye_world') }}">say goodbye</A>
      </body>
    </html>
    Here is my goodbye.html template:
    <html>
      <head><title>A Farewell</title></head>
      <body>
          <h1>Goodbye, now!  Come on back, y'hear?!</h1>
          <A HREF="{{ url_for('hello_world') }}">say hello</A>
      </body>
    </html>
    Here is my hello.py script:
    #!/usr/bin/env python
    
    import flask
    app = flask.Flask(__name__)
    
    @app.route('/hello')
    def hello_world():
        print('*** DEBUG:  inside hello_world() ***')
        return flask.render_template('hello.html')
    
    @app.route('/goodbye')
    def goodbye_world():
        return flask.render_template('goodbye.html')
    
    if __name__ == '__main__':
        app.run(debug=True, port=5000)
    
     
    Ex. 3.40 Add the form to the hello.html template, which features a <select> dropdown and and an <input> field. Call the app through the form by submitting the form.

    Expected Output: You should see the goodbye output as before.

     
    Ex. 3.41 Read the caption and mood field values and include them in the goodbye.html template.
    Here is my goodbye.html template:
    <html>
      <head><title>A Farewell</title></head>
      <body>
          <h1>Goodbye, now!  Come on back, y'hear?!</h1>
          Caption:  {{ caption }}<br>
          Mood: {{ mood }}<br>
          <A HREF="{{ url_for('hello_world') }}">say hello</A>
      </body>
    </html>
    Here is my hello.py script:
    #!/usr/bin/env python
    
    import flask
    app = flask.Flask(__name__)
    
    @app.route('/hello')
    def hello_world():
        print('*** DEBUG:  inside hello_world() ***')
        return flask.render_template('hello.html')
    
    @app.route('/goodbye')
    def goodbye_world():
        caption = flask.request.args.get('caption')
        mood = flask.request.args.get('mood')
        return flask.render_template('goodbye.html',
                                     caption=caption,
                                     mood=mood)
    
    if __name__ == '__main__':
        app.run(debug=True, port=5000)
    
     
    Ex. 3.42 Add an image to the goodbye page.
    Here is my file/directory structure:
    flask_test/
               hello.py
               launch.html
               templates/
                         hello.html
                         goodbye.html
               static/
                      images/
                             happup.jpg
    Here is my goodbye.html template:
    <html>
      <head><title>A Farewell</title></head>
      <body>
          <h1>Goodbye, now!  Come on back, y'hear?!</h1>
          <img src="{{ url_for('static', filename='images/happup.jpg') }}"><br>
          Caption:  {{ caption }}<br>
          Mood: {{ mood }}<br>
          <A HREF="{{ url_for('hello_world') }}">say hello</A>
      </body>
    </html>
     
    Ex. 3.43 Choose an image based on the user's dropdown choice.
    Here is my file/directory structure:
    flask_test/
               hello.py
               launch.html
               templates/
                         hello.html
                         goodbye.html
               static/
                      images/
                             happup.jpg
                             sadpup.jpg
    Here is my goodbye.html template:
    <html>
      <head><title>A Farewell</title></head>
      <body>
          <h1>Goodbye, now!  Come on back, y'hear?!</h1>
          <img src="{{ url_for('static', filename=image_path) }}"><br>
          {{ caption }}<br><br>
          <A HREF="{{ url_for('hello_world') }}">say hello</A>
      </body>
    </html>
    Here is my script:
    #!/usr/bin/env python
    
    import flask
    app = flask.Flask(__name__)
    
    @app.route('/hello')
    def hello_world():
        print('*** DEBUG:  inside hello_world() ***')
        return flask.render_template('hello.html')
    
    @app.route('/goodbye')
    def goodbye_world():
    
        caption = flask.request.args.get('caption')
        mood = flask.request.args.get('mood')
    
        if mood == 'happy':
            image_path = 'images/happup.jpg'
        else:
            image_path = 'images/sadpup.jpg'
    
        return flask.render_template('goodbye.html',
                                     caption=caption,
                                     mood=mood,
                                     image_path=image_path)
    
    if __name__ == '__main__':
        app.run(debug=True, port=5000)
    
     
    [pr]