
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¶
- (5) Announcements
- (20) Review and your Questions
- (5) Ports and Flask on Tempest
- (15) Venv (together)
- (10) Examples
- (20) Breakouts
- Flask
- Goal
- Plan
- Announcements
- URL Anatomy
- Flask Overview
- Flask Summary
- Recap
- Virtualenv
- Your questions
- Ports and Flask on Tempest
- Creating our Virtual Environment
- First Example
- SSH tunnel
- First Exercise
- Milestone
- Automatic Reloading
- Errors
- Exiting
- Routing
- Routing Example
- Variable Rules
- Templates
- Breakout
- Killing Python Processes
- Checklist for Starting a Flask App and Viewing from Off Campus
- Summary
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 likegithub
orsftp
orssh
. We'll usehttp:
because encryption will get in our way. If your browser enforceshttps:
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:@app.route('/title/<tt>/')
is how IMDB does movie pages, like https://www.imdb.com/title/tt5311514/@app.route('name/<nm>')
is how IMDB does person pages, like https://www.imdb.com/name/nm3918035/@app.route('<username>')
is how Facebook does profile pages
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¶
- copy the
~cs304flask/pub/downloads/flask1/
folder to your 304 folder. - activate your virtualenv
- cd to the
flask1
folder - 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?
- Go to your browser and go to
localhost:8080/
orhttp://149.130.15.5:yourport/
- Try different URLs by typing in the location bar of the browser. Try them more than once!
- 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!
- 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
- Save the result
- Check the terminal that is running the web app and notice the message about reloading.
- 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:
- Go to the terminal window that it's running in
- type
control c
(C-c) - refresh the browser window, and you'll see that it's gone.
Routing¶
- Allows many URLs to be handled by one web app (file), each being handled by a different function (a handler)
- Allows patterns of URLS to be handled by a function (we'll talk about this more next)
- 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.
- Start it, as before
- What is the main URL?
- 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:
- login
- activate your venv
- navigate to your app
- run
app.py
(or the equivalent)
- In a browser, go to
localhost:8080
Summary¶
You've written your first Flask applications!
You know about
- virtualenv
- starting and stopping a Flask application
- routing and the decorator syntax
- routing with variable URLs