Welcome!
Everything is fine.

Flask

Today, we'll work with our major middleware, namely Flask.

Goal

By the end of today, you will be able to use the Flask module to do development of web applications in your Tempest account.

Plan

  1. (5) Announcements
  2. (20) Review and your Questions
  3. (5) Ports and Flask on Tempest
  4. (15) Venv (together)
  5. (10) Examples
  6. (20) Breakouts

Announcements

  • Please use name tents. It helps all of us!
  • About half are done with Contacts
  • The soft deadline policy:
    • intended to ease the pressure around deadlines, but
    • you should try not to fall too far behind
    • please get help if you need it!
  • Please make sure you submit P0 by Friday, whether you submit H4 ER on time or not
    • I'll send a Google Doc over the weekend. Please take time to read it.
    • Let's talk about team formation

URL Anatomy

Let's take a moment to talk a bit about URLs, starting with some examples:

https://cs.wellesley.edu:1942/
https://cs.wellesley.edu:1942/about/
https://cs.wellesley.edu:1942/nm/123
https://cs.wellesley.edu:1942/search/?text=gold+silver

Here, the endpoints are / and /about/ and /nm/<nm> and /search/ so we would expect to see handler functions associated with each of these endpoints.

In general, a URL breaks down into:

protocol://host.domain:port/endpoint/?querystring

Here's some more detail:

  • protocol: Almost always https but you occasionally see things like github or sftp or ssh. We'll use http: because encryption will get in our way. If your browser enforces https: like mine does, I'll show you a work-around.
  • host: the name of the server. Ours is cs.
  • domain: you buy these from a domain registrar. The college owns wellesley.edu
  • port: the particular "door" on the server that is listening for web requests.
  • endpoint: the application or program or handler that receives your web request
  • query string: the data that a form using GET sends to the endpoint

(There are some other variations that are a distraction for now.)

Flask Overview

In development mode, Flask creates a long-running process that accepts web requests (on a configurable port), figures out which function to call based on the incoming URL, and prints the return value of that function as the response.

In short, your program is a web server, except that:

  • The responses are not files but the result of function calls
  • The functions are called handlers
  • The requests have endpoints (the end of a URL)
  • Patterns of URLs can be handled by one function

Flask Summary

This introduction to Flask covered a lot of ground and a lot of new concepts. We'll see these many times over the next few weeks, so if it's overwhelming right now, rest assured that you'll get lots of practice and review.

Here are some of the things we learned:

  • Flask is a Python module that we import into our Python program
  • Flask turns our running python program into a tiny web server (a program that receives and responds to web requests).
  • Flask handles a lot of the routine parts of web applications for us, making our lives as web developers much easier.
  • Our Flask web server runs for a longish amount of time (minutes to hours) while we are working and debugging. A long-running program is called a process.
  • Our Flask process listens for web requests on a particular port: a number on the server.
  • Each process has to use a different port, so I've adopted the convention that each of us will use our user id (UID) as our port number.
  • One of the important things that Flask does for us is routing: looking at the URL in the web request and invoking one of our functions to handle that request.
  • We tell Flask which function handles which route by using the @app.route(url) decorator function. We decorate the handler function by preceding its definition with the @app.route(url) to specify the url that should be routed to that handler function. See below.
  • The url or endpoint is often a fixed string, like /about/ for the "About" page or /login/ for the login page, but it can also be parameterized, which means that a set of matching URLs are all handled by one handler function. Some examples:

Examples of defining handlers:

# this maps our homepage to the minimal endpoint, namely /
@app.route('/')
def home_page():
    return '<h1>Welcome!</h1> <p>Welcome to my app.</p>'

@app.route('/about/')
def about():
    # the about page is static, but long, so it's in a file
    return render_page('about.html') 

# this is a parameterized endpoint. It handles all profile pages

@app.route('/profile/<username>')
def profile_page(username):
   # look up info about this user and then render their custom profile page
   data = database.lookup(username)
   render_page('profile.html', info = data)

Recap

The key ideas:

  • Flask is Python module
  • A Flask program is a long-running process that listens on a port for HTTP requests
  • All network programs send and receive information on ports; that's just how networking works.
  • (A port is a number in a network packet, that is part of the source and destination of a network packet.)
  • Once Flask gets a request, it can route it to a particular function that you have written to respond to that request
  • Your response can be a simple string or a full web page.

Example:

@app.route('/about/')
def about():
    return '''This app is pretty simple-minded: 
    it just echoes back the text you send it. '''

More advanced:

  • The endpoint can be parameterized, meaning many different kinds of requests can be handled by a single function.
@app.route('/echo/<msg>')
def echo(msg):
    return 'did you just say {this}?'.format(this=msg)

Or maybe:

@app.route('/shout/<msg>')
def shout(msg):
    return msg.upper()

Or even:

@app.route('/divide/<num>/<denom>')
def divide(num, denom):
    try:
        x = int(num)
        y = int(denom)
        z = x/y
        return f'{x} divided by {y} is {z}'
    except:
        return f'error given {num} and {denom}'

Virtualenv

To install Flask, we have to use virtualenv

  • a way to install a collection of Python modules for some purpose
  • have to set it up, once for each location (e.g. server, account)
  • have to activate it every time we use it
  • deactivate when done (or just logout)

Your questions

Almost no one was comfortable with this material, so we'll take plenty of time on it.

I'll address your quiz questions on Python.

I'll address your quiz questions on Flask and Virtualenv.

Ports and Flask on Tempest

If we are all running our Flask apps on Tempest, we can't all use port 5000 or 8080 or whatever. Each of you needs a different port. So, to do this, I suggest using your UID. To determine your UID, use the id command:

[cs304guest@tempest ~]$ id 
uid=8299(cs304guest) gid=8300(cs304guest) groups=8300(cs304guest) ... 

Or, simpler:

[cs304guest@tempest ~]$ id -u 
8299

Thus, we'll change the active part of your app from this (seen in thousands of online tutorials and such):

if __name__ == '__main__': 
    app.debug = True 
    app.run() 

to this:

if __name__ == '__main__': 
    import os 
    uid = os.getuid() 
    app.debug = True 
    app.run('0.0.0.0',uid) 

The '0.0.0.0' means to have the app listen on the external interface of the machine (tempest). The default is to listen on the internal interface, which means only connections from within Tempest itself. An external interface accepts connections from other machines, including your browser.

You can mostly ignore this code and just use it as boilerplate.

Creating our Virtual Environment

We'll follow these directions on creating a venv

First Example

Here's the code for our first example, example1.py. You'll copy this code in a few minutes.

from flask import Flask
import servertime

app = Flask(__name__)

numRequests = 0

@app.route('/')
def hello_world():
    return '<h1>Hello Everyone!</h1>'
    
@app.route('/about')
def about():
    global numRequests
    numRequests += 1
    return ('''<h1>Hello, World!</h1>
<p>This is Scott's version</p>
<p>The time on Tempest is {time}.</p>
<p>There have been {n} requests so far.</p>'''
    .format(time=servertime.now(),n=numRequests))

@app.route('/bye')
def bye():
    return 'This is how we say good-bye!!'

if __name__ == '__main__':
    import os
    uid = os.getuid()
    app.debug = True
    app.run('0.0.0.0',uid)

Note that we usually name our main file app.py but for these examples, I wanted several in a single folder, so I called them example1.py, example2.py and example3.py.

SSH tunnel

Another complication can be caused by browsers that insist on HTTPS (which they often should). We can get around that by using SSH tunnels. I'll demo that as well.

If you're off-campus, then from your home laptop, set up an SSH tunnel like this:

ssh -L 8080:localhost:1942 anderson@cs.wellesley.edu

or

ssh -L 8080:localhost:8299 cs304guest@cs.wellesley.edu

Substitute your ID/Port for the 1942/8299 and your username for the anderson/cs304guest.

Remember, execute that command on your laptop, not on Tempest.

First Exercise

  1. copy the ~cs304flask/pub/downloads/flask1/ folder to your 304 folder.
  2. activate your virtualenv
  3. cd to the flask1 folder
  4. run the first example: python example1.py

Here are the commands:

cp -r ~cs304flask/pub/downloads/flask1/ flask1
source venv/bin/activate
cd flask1
python example1.py

I'll do that with you in the cs304guest account.

Did that work?

  1. Go to your browser and go to localhost:8080/ or http://149.130.15.5:yourport/
  2. Try different URLs by typing in the location bar of the browser. Try them more than once!
  3. Look in the terminal window where the app is running. You'll see a log of the requests. Error messages might get printed there as well.

Milestone

If that worked, you've achieved the most import thing for today. Everything else is extra.

Automatic Reloading

A very cool feature of development mode is that the web app monitors the example1.py file and will reload itself if the file changes. Try it!

  1. Edit the file to change the program a little.
    • Modify the hello_world page
    • Change the name in the /about route from my name to yours
  2. Save the result
  3. Check the terminal that is running the web app and notice the message about reloading.
  4. Refresh the browser window. (Get in the habit of using shift+reload.)

Errors

Try putting an error in the code, just to see what happens.

When I first did the example, I omitted the n= in the .format() method. Try that.

In development mode, run-time errors will get printed to the console. You can also put print statements in there.

Certain kinds of errors can be debugged in the browser window, using a security key.

Exiting

When you're done using your Flask application, just kill it:

  1. Go to the terminal window that it's running in
  2. type control c (C-c)
  3. refresh the browser window, and you'll see that it's gone.

Routing

  1. Allows many URLs to be handled by one web app (file), each being handled by a different function (a handler)
  2. Allows patterns of URLS to be handled by a function (we'll talk about this more next)
  3. The large if statement is invisible. All we have are the pairs of URLs and functions.

Routing Example

Let's look at the second example, example2.py:

from flask import Flask, render_template
import random
app = Flask(__name__)
 
@app.route('/')
def welcome():
    # Note that this isn't the right way to do these URLs; 
    # we'll see the right way next time.
    return '''<h1>Welcome</h1>
<p>Go to <a href="/fred">fred</a> to see info about fred
<p>Go to <a href="/george">george</a> to see info about george
'''
 
def lucky():
    return '<p>Your lucky number is {n}'.format(n=random.randrange(1,100))
 
@app.route('/fred')
def fred():
    return '''<p>This function handles the 'fred' URL'''+lucky()
 
@app.route('/george')
def george():
    return '''<p>However, this function handles 'george'.'''+lucky()
 
if __name__ == '__main__':
    import os
    uid = os.getuid()
    app.debug = True
    app.run('0.0.0.0',uid)

I'll demo in the guest account.

  1. Start it, as before
  2. What is the main URL?
  3. Go to a web browser and try the main URL.

Obviously, these functions can easy share code, as they do with the lucky() function.

Nevertheless, your main app file should focus on routing, not on supporting functionality. You can define modules and import them to do the heavy lifting. That keeps your controller file concise (and they tend to get long).

Variable Rules

Sometimes, our URLs can be patterns that can then be parsed and involved in the computation. Flask makes this easy.

Code from example3.py:

from flask import Flask, render_template
import math
import random
app = Flask(__name__)
  
@app.route('/')
def welcome():
    return '''<h1>Welcome</h1>'''
 
@app.route('/square/<int:n>')
def square(n):
    return('<p>{x}<sup>2</sup> is {x2}</p>'.format(x=n,x2=n*n))
 
@app.route('/sqrt/<float:n>')
def sqrt(n):
    return ('<p>{x} is the square root of {y}'
            .format(x=math.sqrt(abs(n)),y=n))
             
@app.route('/country/<city>')
def country(city):
    known = {'Paris':'France',
             'London':'England',
             'Madrid':'Spain',
             'Beijing':'China'}
    if city in known:
        return ('<p>{city} is the capital of {country}'
                .format(city=city,country=known[city]))
    else:
        return ('''<p>I don't know the country whose capital is {city}'''
                .format(city=city))
 
     
if __name__ == '__main__':
    import os
    uid = os.getuid()
    app.debug = True
    app.run('0.0.0.0',uid)

Templates

Example 4 is a subdirectory with the greeting app in it:

cd greeting_app
python app.py

Try it!

Breakout

Play with Flask, modifying the .py file and testing in the browser. For example, try

  • adding a few cities to the last example
  • defining an endpoint

Killing Python Processes

Sometimes you forget to kill Flask before you logout or something. Later, you try to run Flask, and you can't open your port because your other process already has it open. How can you find that other process and kill it? You can find open network programs using the netstat command. You can then kill the process with the kill command.

Here's an example, where I'm UID 1942:

 
$ netstat -ntlp | grep 1942 
(Not all processes could be identified, non-owned process info 
 will not be shown, you would have to be root to see it all.) 
tcp        0      0 0.0.0.0:1942            0.0.0.0:*               LISTEN      13173/python 
$ kill 13173 
$ netstat -ntlp | grep 1942 
(Not all processes could be identified, non-owned process info 
 will not be shown, you would have to be root to see it all.) 

This is described in our FAQ on kill

Checklist for Starting a Flask App and Viewing from Off Campus

  • set up the SSH tunnel in an ordinary terminal (or use the VPN)
  • In VS Code:
    1. login
    2. activate your venv
    3. navigate to your app
    4. run app.py (or the equivalent)
  • In a browser, go to localhost:8080

Summary

You've written your first Flask applications!

You know about

  1. virtualenv
  2. starting and stopping a Flask application
  3. routing and the decorator syntax
  4. routing with variable URLs