Flask 1

Note: delete all other stuff running in background

help docs in idle:

  • from flask import Flask

help(flask)

# p01_server_without flask.py
Use sockets, HTTP, HTML, CSS to generate web page with random colours and text every time it is requested
Run code, then view http://127.0.0.1:8000/ ⇒ refresh to see randomised changes
import random
import socket
# HTTP uses \r\n to end lines instead of \n
EOL = b'\r\n'
# list of possible colours
COLOURS = [
‘red’, ‘orange’, ‘blue’, ‘purple’,
‘#C0C000’, # dark yellow
‘#00C000’ # dark green
]
# list of possible messages
MESSAGES = ['Hello, World!', 'Computing is Fun', 'Jinjjaaa']
# server start listening for web browser requests on port 8000
listen_socket = socket.socket()
listen_socket.bind((‘127.0.0.1’, 8000))
listen_socket.listen()
def handle_request(new_socket):
# keep loading request until end of first line (\r\n)
request = b''
while EOL not in request:
received = new_socket.recv(1024)
# when recv() returns empty bytes object
# close connection so program stops processing request immediately
if received == b'':
new_socket.close()
return
request += received
# extract 1st line & split into 3
# requested path is always the 2nd part
# ==> extract path component from 1st line of request
index = request.index(EOL)
first_line = request[:index].decode()
path = first_line.split()[1]
# start new HTTP response with standard HTTP status line
response = b'HTTP/1.1 200 OK' + EOL
# generate CSS document for response if path is '\css'
# else, generate HTML document for response
if path == '/css':
# generate css document
border_colour = random.choice(COLOURS)
text_colour = random.choice(COLOURS)
body = 'p {{ border: 5px solid {0}; color: {1};'
body += ‘font-size: 72px; padding: 20px; }}‘
body = body.format(border_colour, text_colour)
body = body.encode()
# generate and append HTTP header fields to response
response += b’Content-Type: text/css’ + EOL
response += b’Content-Length: ’
response += str(len(body)).encode() + EOL
else:
# generate HTML document
msg = random.choice(MESSAGES)
body = '<!DOCTYPE html> \n<html>'
body += ‘<head><title>{0}</title>’
body += ‘<link rel=“stylesheet” href =“/css”></head>’
body += ‘<body><p>{0}</p></body></html>‘
body = body.format(msg).encode()
# append HTTP header fields to response
response += b’Content-Type: text/html’ + EOL
response += b’Content-Length: ’
response += str(len(body)).encode() + EOL
# append empty line to response (header field)
response += EOL
# append document to response as message body
response += body

# send completed response back to web browser
new_socket.sendall(response)
# close socket so server can handle the next HTTP request
new_socket.close()
while True: # until Ctrl-C is pressed
# accept HTTP request sent when browser visits http://127.0.0.1:8000/
new_socket, addr = listen_socket.accept()
handle_request(new_socket)

⇒ complex, tedious, error-prone

  • Must parse or construct HTTP request / status lines & header fields to follow HTTP standard
  • Must assemble long pieces of HTML and CSS code from Python strings

⇒ Should use a framework instead

  • = module or library with ready-made generic solutions that a programmer can selectively override to customise certain behaviours
  • Gets more done in less time as the frame work already provides a working solution; programmer only needs to configure or customise for his or her needs
Run web server at http://127.0.0.1:5000/
Repeats the 1st line of each HTTP request back to browser
E.g.
URL | Output
http://127.0.0.1:5000/ | GET / HTTP/1.1
http://127.0.0.1:5000/hello | GET /hello HTTP/1.1
http://127.0.0.1:5000/200/300/ | GET /200/300/ HTTP/1.1
http://127.0.0.1:5000//300//400// | GET //300//400// HTTP/1.1
http://127.0.0.1:5000/?key=value | GET /?key=value HTTP/1.1
import socket
EOL = b'\r\n'
listen_socket = socket.socket()
listen_socket.bind((‘127.0.0.1’, 5000))
listen_socket.listen()
def handle_request(new_socket):
request = b''
while EOL not in request:
received = new_socket.recv(1024)
if received == b'':
return
request += received
index = request.index(EOL)
first_line = request[:index]
response = b'HTTP/1.1 200 OK' + EOL
response += b’Content-Type: text/plain’ + EOL
response += b’Content-Length: ’
response += str(len(first_line)).encode() + EOL
response += EOL
response += first_line
new_socket.sendall(response)
new_socket.close()
while True:
new_socket, addr = listen_socket.accept()
handle_request(new_socket)

Flask Framework

  • Not built into Python ⇒ pip3 install flask; then import flask
  • HTTP requests and routing
  • How HTTP request path is mapped / routed to Python function
  • Fixed routes
  • Variable routes
  • Routing by HTTP Methods
  • HTTP Responses and Status Codes
  • Changing status code
  • Changing the response headers
  • Redirecting to another URL
  • ⇒ Creates dynamic web pages
# p02_minimal.py
Run basic web server that Flask provides
Implements HTTP and its many requirements
Create a flask.Flask object with the module’s __name__ as an argument
Call the object’s run() method
import flask
app = flask.Flask(__name__)
if __name__ == '__main__':
app.run()
⇒ when run, start-up message should indicate that the server can be accessed at http://127.0.0.1:5000/
Since default web server is not configured to recognise any paths yet, will receive a 404 (Not Found) error when visit that URL using web browser
But notice that Flask alr provides a complete web server that correctly implements HTTP without additional work from programmer
5000 is default port number used by Flask
To use another port number, use e.g. app.run(port=12345)
To stop Flask server, press Ctrl-C in IDLE’s shell window or Ctrl-F6 to restart shell
pip3 install flask==0.12 to overcome incompatibility with latest Flask version

HTTP Requests and rounding

  • Configure which paths are recognised to customise web server that Flask provides
  • Each HTTP request starts with a request line specifying method & path + version of HTTP used
  • Whether a HTTP request succeeds depends on whether path refers to a web document that is recognised by server & whether the method used is allowed for that web document
  • E.g. HTTP server running on 127.0.0.1 & listening on port 5000; when visit http://127.0.0.1:5000/readme.txt, browser sends HTTP request of GET /readme.txt HTTP/1.1
  • To web servers, HTTP paths are just strings, they do NOT refer to real files or folders
  • Thus web servers can dynamically generate an HTML error page when we visit a URL that the server does not recognise
  • To handle HTTP requests, can map specific paths to Python functions
  • E.g. the path / can be mapped to a function home();
    /readme.txt can be mapped to a function readme()
  • When a HTTP request is received, Flask examines the received path and looks for a mapping ⇒ routing
  • Each HTTP request is routed to a Python function for processing based on the requested path and method used
  • Route = each mapping of a path to a Python function
  • If mapping found: Flask runs the associated Python function to generate a response
  • Else: no mapping found, 404 (Not Found) status code produced
  • E.g.
  • Use decorators to declare a route and associate a path to a Python function
  • Add decorations immediately before function’s definition ⇒ alter behaviour of a function without modifying its source code
  • Start with an at-sign or “pie” symbol (@) followed by a decorator
  • Use decorators generated using the route method of the main Flask object named app
  • E.g.
# p03_simple_routes.py
When visit http://127.0.0.1:5000/readme.txt, website shows READ ME as the path /readme.txt is mapped to the function readme() by the decoration
When visit http://127.0.0.1:5000/, website shows “Welcome”
When visit http://127.0.0.1:5000/report, website shows “Everything is awesome
When visit other URLs (e.g. http://127.0.0.1:5000/nothing), 404 (Not Found) error as no other paths have been mapped
import flask
app = flask.Flask(__name__)
@app.route('/')
def home():
return 'Welcome'
@app.route('/report')
def generate_report():
return 'Everything is awesome'
@app.route('/readme.txt')
def readme():
return 'READ ME'
if __name__ == '__main__':
app.run()
# p04_complex_routes.py
Uses multiple decorators to declare a variety of routes
import flask
app = flask.Flask(__name__)
@app.route('/')
def index():
return 'Routed to index()'
@app.route('/css')
def css():
return 'Routed to css()'
@app.route('/no_slash')
def no_slash():
return 'Routed to no_slash()'
@app.route('/optional_slash/')
def optional_slash():
return 'Routed to optional_slash()'
@app.route('/one/')
@app.route('/one/two/')
@app.route('/three/two/one')
def multiple():
return 'Routed to multiple()'
@app.route('/string/<s>/')
def string_variable(s):
return 'Routed to string_variable(), s = {}'.format(s)
@app.route('/integer/<int:i>/')
def integer_variable(i):
return 'Routed to integer_variable(), i = {}'.format(i)
@app.route('/post_only/', methods=['POST'])
def post_only():
return 'Routed to post_only()'
if __name__ == '__main__':
app.run()
  • Fixed routes
  • http://127.0.0.1:5000/: “Routed to index()”
  • Indicates that your request was handled by the index() function
  • Because the path component is just a slash character (/), which matches the route specified for index() function
  • Note: routes are case sensitive
  • E.g. http://127.0.0.1:5000/CSS: 404 Not Found error
  • etc
  • Trailing slashes
  • Request’s path and route’s path must match exactly for the route to be chosen
  • E.g. cannot have extra or less trailing slash (/) ⇒ 404 Not Found error
  • But if Flask fails to find a route for a path that does NOT end with a slash, it will append a slash to the path and try again before giving up completely
  • E.g. both http://127.0.0.1:5000/optional_slash (without a trailing slash) and http://127.0.0.1:5000/optional_slash/ (with a trailing slash) match the route for /optional_slash/ and will run the optional_slash() function
  • BUT Flask does NOT automatically remove extra slash
  • Multiple decorators
  • Multiple paths can be routed to the same function
  • E.g. the 3 decorations associate ALL of these URLs with the multiple() function:
  • http://127.0.0.1:5000/one/
  • http://127.0.0.1:5000/one/two/
  • http://127.0.0.1:5000/three/two/one
  • Variable Routes
  • Flask routes can have variable parts, with a name surrounded by < and >
  • Default: each variable part matches any non-empty sequence of characters that does not contain a slash (/)
  • Route is matched if all variable parts in the path can be matched in this way ⇒ variable parts are extracted into str values and passed to the associate Python function as keyword arguments
  • E.g. route specified by ‘/string/<s>/’, containing variable part numbed s
  • This route matches any path starting with /string/ followed by >= 1 non-slash characters and an optional trailing function
  • If match found: variable part is extracted into str value and passed to the function string_variable() as a keyword argument named s
  • Else: Flask will try to find another route that matches, returning a 404 (Not Found) error if none can be found
  • If e.g. http://127.0.0.1:5000/string/ or http://127.0.0.1:5000/string/hi/there: 404 Not Found
  • Variable parts can also specify a converter using <converter:name> to modify the matching algorithm & convert the matched string to another type before being passed over to the function as a keyword argument
  • E.g. ‘/integer/<int:i>’ with 1 variable part named i that uses the int converter ⇒ only digits are accepted ⇒ paths that start with /integer/ followed by >=1 digit(s) and an optional trialing slash will result in a match
  • If match found: digit portion of the path is extracted & converted to int before being passed to integer_variable() as an int parameter
  • Else: match fails, Flask tries to find another route that matches, returns 404 (Not Found) if no matching route can be found
  • E.g. http://127.0.0.1:5000/integer/‐123 ⇒ 404 Not Found
  • Routing by HTTP methods
  • Each routes matches based on paths + can specify if it only applies to GET, POST, or both requests
  • GET: retrieves data without making changes
  • POST: submits or makes changes to server’s data permanently
  • E.g. add, delete, update data on server
  • To limit the HTTP methods accepted by a route, pass in a list of permitted HTTP methods as a keyword argument named methods in the decorator
  • E.g. A route specified for /post_only is only accessible using POST
  • If try to access http://127.0.0.1:5000/post_only using GET by directly entering it into address bar of a web browser, get 405 (Method Not Allowed) error
  • To produce a POST request, need to reach the route by submitting a HTML form
  • Generating paths from function names
  • Routes provide a mapping from paths to Pyrgon functions
  • But often need to go in opp direction: generate path for a given Python function
  • Call url_for() function in flask nodule & pass it a string with the function’s name
  • If path has any variables (e.g. s in “/string/<s>”), they should be provided as keyword arguments to url_for()
E.g. p05_url_lookup.py
Specify some routes & print 3 generated paths in the shell or command prompt window when the “root” site http://127.0.0.1:5000/ is visited
import flask
from flask import url_for
app = flask.Flask(__name__)
@app.route('/')
def home():
url1 = url_for('fixed_route')
url2 = url_for('string-variable', s = 'example')
url3 = url_for('integer_variable', i=2020)
print(url1)
print(url2)
print(url3)
return 'Check your shell or command prompt window'
@app.route('/fixed/')
def fixed_route():
return 'Routed to fixed()'
@app.route('/string/<s>')
def string_variable(s):
return 'Routed to string_variable(), s = {}'.format(s)
@app.route('/integer/<int:i>')
def integer_variable(i):
return 'Routed to integer_variable(), i = {}'.format(i)
if __name__ == '__main__':
app.run()

HTTP Responses and Status Codes

  • Flask has been prepending various headers behind the scenes to produce valid HTTP responses
p06_hello_world.py
import flask
app = flask.Flask(__name__)
@app.route('/')
def index():
return 'Hello, World!'
if __name__ == '__main__':
app.run()
  • Visit http://127.0.0.1:5000/, examine response headers using Chrome’s Developer Tools
  • Together with the content, complete HTTP response sent to your browser, e.g.:
HTTP/1.0 200 OK
Content‐Type: text/html; charset=utf‐8
Content‐Length: 13
Server: Werkzeug/0.14.1 Python/3.7.0
Date: Tue, 22 Jan 2019 08:01:52 GMT
Hello, World!
  • Flask assumes output has a Content-Type of “text/html”
  • ⇒ Flask expects our function to return a full HTML document and not just a plain string
  • But most browsers will treat our string as a snippet of HTML intended for the document’s <body>
  • E.g.
import flask
app = flask.Flask(__name__)
@app.route('/')
def index():
return '<h1>Welcome</h1> <b>Hello, </b> <i>World!</i>'
if __name__ == '__main__':
app.run()
  • But this is non-standard behaviour ⇒ our functions that are mapped to routes should really return full HTML documents
  • Changing the Status Code
  • Default: Flask assumes our responses have a HTTP status code of 200 (OK)
  • Can override by returning a tuple instead of just a string
  • Replacement HTTP status code should be provided as 2nd item of the tuple
import flask
app = flask.Flask(__name__)
@app.route('/')
def index():
return ('', 500)
if __name__ == '__main__':
app.run()
# When visit http://127.0.0.1:5000/,
get HTTP ERROR 500 ⇒
Internal Server error
  • Changing the Response Headers
  • Can also additional response headers by putting them into a Python dictionary & returning this dictionary as the 3rd item of our return tuple
  • E.g. replace Content-Type header value with “text/plain” ⇒ for web browser to treat response as plain text instead of HTML
# p09_returning_plain_text.py
import flask
app = flask.Flask(__name__)
@app.route('/')
def index():
headers = {'Content-Type': 'text/plain'}
return(‘<b>This is not HTML!</b>’, 200, headers)
if __name__ == '__main__':
app.run()
# since text/plain not text/html, the string returned is no longer treated as HTML
  • Redirecting to another URL
  • Besides overriding HTTP status code & response headers Flask also lets us generate a response that tells web browser to load a diff. URL instead ⇒ redirect
  • Useful when location of a document has moved, or to let another Flask route take over the handling of a request
  • Import redirect() function from flask module, and call it with the destination URL or path that as the 1st argument
  • Then use the response generated by redirect() as the return value of the function
  • E.g. redirect to an external site
# p10_redirect_example.py
import flask
from flask import redirect
app = flask.Flask(__name__)
@app.route('/')
def index():
return redirect('http://example.com')
if __name__ == '__main__':
app.run()
  • E.g. redirect user to another of our routes ⇒ use url_for() to look up correct path for redirect() based on the function that we want to reach (instead of hardcoding the redirected path as a string)
# p10_redirect_using_url_for.py
import flask
from flask import redirect, url_for
app = flask.Flask(__name__)
@app.route('/new_url/')
def moved_index():
return 'You have reached the new URL!'
@app.route('/')
def index():
return redirect(url_for('moved_index'))
if __name__ == '__main__':
app.run()