Introduction

Welcome to this journey into the world of web servers and the Flask framework! In the previous weeks, you’ve successfully set up a web server using GitHub Pages, converting Jupyter Notebooks into Markdown for a seamless online presentation. Today, we’ll take that knowledge to the next level as we dive into creating your very own web server using Flask.

Understanding Web Servers

What is a Web Server?

Traditionally, we had librarians at libraries that would help you find books or information. Today in the digital world, thousands upon thousands of home pages, search engines, and digital archives have been built using web servers.

GitHub Pages vs. Flask

You’ve already experienced a form of web server through GitHub Pages. Think of GitHub Pages as a library that has established rules for publishing Markdown notes and Jupyter Notebooks neatly on a bookshelf.

Now, let’s introduce Flask, your personal web server. Flask can create and manage any type of content, including customizing everything according to your preferences, and even serve additional information (like a database with APIs).

The Flask Framework Flask is a micro web framework written in Python. It’s designed to be minimal and easy to use, making it perfect for building web applications, APIs, and, yes, even your web server. Today, we will start with the basics of Flask and see how it empowers you to create and manage web content.

Our Goals for Today

Here’s what we’ll accomplish in this session:

  • Create a minimal Flask server.
  • Explore the Python/Flask process.
  • Access data from our Flask server using Python.
  • Access data from our Flask server using JavaScript.
  • Learn how to stop the Python/Flask process gracefully.

Note: Jupyter magic commmand %%python --bg that follows runs the server in background. This enables us to continue interacting with the subsequent Notebook cells.

# %%python --bg

from flask import Flask, jsonify
from flask_cors import CORS

# initialize a flask application (app)
app = Flask(__name__)
CORS(app, supports_credentials=True, origins='*')  # Allow all origins (*)

# ... your existing Flask

# add an api endpoint to flask app
@app.route('/api/data')
def get_data():
    # start a list, to be used like a information database
    InfoDb = []

    # add a row to list, an Info record
    InfoDb.append({
        "FirstName": "John",
        "LastName": "Mortensen",
        "DOB": "October 21",
        "Residence": "San Diego",
        "Email": "jmortensen@powayusd.com",
        "Owns_Cars": ["2015-Fusion", "2011-Ranger", "2003-Excursion", "1997-F350", "1969-Cadillac"]
    })

    # add a row to list, an Info record
    InfoDb.append({
        "FirstName": "Shane",
        "LastName": "Lopez",
        "DOB": "February 27",
        "Residence": "San Diego",
        "Email": "slopez@powayusd.com",
        "Owns_Cars": ["2021-Insight"]
    })
    
    return jsonify(InfoDb)

# add an HTML endpoint to flask app
@app.route('/')
def say_hello():
    html_content = """
    <html>
    <head>
        <title>Hellox</title>
    </head>
    <body>
        <h2>Hello, Aditya!</h2>
    </body>
    </html>
    """
    return html_content

if __name__ == '__main__':
    # starts flask server on default port, http://127.0.0.1:5001
    app.run(port=5001)

 * Serving Flask app '__main__'
 * Debug mode: off


INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug:127.0.0.1 - - [19/Sep/2023 21:26:25] "GET / HTTP/1.1" 200 -

Show Python/Flask process

This script discovers the running flask process

%%script bash

# After app.run(), see the the Python process
lsof -i :5001
# see the the Python app
lsof -i :5001 | awk '/Python/ {print $2}' | xargs ps


Access API with Python

This script extracts data from Web Server.

import requests
res = requests.get('http://127.0.0.1:5001/api/data')
res.json()
---------------------------------------------------------------------------

ConnectionRefusedError                    Traceback (most recent call last)

~/.local/lib/python3.10/site-packages/urllib3/connection.py in _new_conn(self)
    202         try:
--> 203             sock = connection.create_connection(
    204                 (self._dns_host, self.port),


~/.local/lib/python3.10/site-packages/urllib3/util/connection.py in create_connection(address, timeout, source_address, socket_options)
     84         try:
---> 85             raise err
     86         finally:


~/.local/lib/python3.10/site-packages/urllib3/util/connection.py in create_connection(address, timeout, source_address, socket_options)
     72                 sock.bind(source_address)
---> 73             sock.connect(sa)
     74             # Break explicitly a reference cycle


ConnectionRefusedError: [Errno 111] Connection refused


The above exception was the direct cause of the following exception:


NewConnectionError                        Traceback (most recent call last)

~/.local/lib/python3.10/site-packages/urllib3/connectionpool.py in urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    789             # Make the request on the HTTPConnection object
--> 790             response = self._make_request(
    791                 conn,


~/.local/lib/python3.10/site-packages/urllib3/connectionpool.py in _make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    495         try:
--> 496             conn.request(
    497                 method,


~/.local/lib/python3.10/site-packages/urllib3/connection.py in request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
    394             self.putheader(header, value)
--> 395         self.endheaders()
    396 


/usr/lib/python3.10/http/client.py in endheaders(self, message_body, encode_chunked)
   1277             raise CannotSendHeader()
-> 1278         self._send_output(message_body, encode_chunked=encode_chunked)
   1279 


/usr/lib/python3.10/http/client.py in _send_output(self, message_body, encode_chunked)
   1037         del self._buffer[:]
-> 1038         self.send(msg)
   1039 


/usr/lib/python3.10/http/client.py in send(self, data)
    975             if self.auto_open:
--> 976                 self.connect()
    977             else:


~/.local/lib/python3.10/site-packages/urllib3/connection.py in connect(self)
    242     def connect(self) -> None:
--> 243         self.sock = self._new_conn()
    244         if self._tunnel_host:


~/.local/lib/python3.10/site-packages/urllib3/connection.py in _new_conn(self)
    217         except OSError as e:
--> 218             raise NewConnectionError(
    219                 self, f"Failed to establish a new connection: {e}"


NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7f42f8b05d80>: Failed to establish a new connection: [Errno 111] Connection refused


The above exception was the direct cause of the following exception:


MaxRetryError                             Traceback (most recent call last)

~/.local/lib/python3.10/site-packages/requests/adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
    485         try:
--> 486             resp = conn.urlopen(
    487                 method=request.method,


~/.local/lib/python3.10/site-packages/urllib3/connectionpool.py in urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    843 
--> 844             retries = retries.increment(
    845                 method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]


~/.local/lib/python3.10/site-packages/urllib3/util/retry.py in increment(self, method, url, response, error, _pool, _stacktrace)
    514             reason = error or ResponseError(cause)
--> 515             raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    516 


MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=5001): Max retries exceeded with url: /api/data (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f42f8b05d80>: Failed to establish a new connection: [Errno 111] Connection refused'))


During handling of the above exception, another exception occurred:


ConnectionError                           Traceback (most recent call last)

/tmp/ipykernel_92726/2595936190.py in <module>
      1 import requests
----> 2 res = requests.get('http://127.0.0.1:5001/api/data')
      3 res.json()


~/.local/lib/python3.10/site-packages/requests/api.py in get(url, params, **kwargs)
     71     """
     72 
---> 73     return request("get", url, params=params, **kwargs)
     74 
     75 


~/.local/lib/python3.10/site-packages/requests/api.py in request(method, url, **kwargs)
     57     # cases, and look like a memory leak in others.
     58     with sessions.Session() as session:
---> 59         return session.request(method=method, url=url, **kwargs)
     60 
     61 


~/.local/lib/python3.10/site-packages/requests/sessions.py in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    587         }
    588         send_kwargs.update(settings)
--> 589         resp = self.send(prep, **send_kwargs)
    590 
    591         return resp


~/.local/lib/python3.10/site-packages/requests/sessions.py in send(self, request, **kwargs)
    701 
    702         # Send the request
--> 703         r = adapter.send(request, **kwargs)
    704 
    705         # Total elapsed time of the request (approximately)


~/.local/lib/python3.10/site-packages/requests/adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
    517                 raise SSLError(e, request=request)
    518 
--> 519             raise ConnectionError(e, request=request)
    520 
    521         except ClosedPoolError as e:


ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=5001): Max retries exceeded with url: /api/data (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f42f8b05d80>: Failed to establish a new connection: [Errno 111] Connection refused'))

Access API with JavaScript

This code extracts data “live” from a local Web Server with JavaScript fetch. Additionally, it formats the data into a table.

First Name Last Name Residence

Kill Python/Flask process

This script ends Python/Flask process

%%script bash

lsof -i :5001 | awk '/Python/ {print $2}' | xargs kill -9

Usage:
 kill [options] <pid> [...]

Options:
 <pid> [...]            send signal to every <pid> listed
 -<signal>, -s, --signal <signal>
                        specify the <signal> to be sent
 -q, --queue <value>    integer value to be sent with the signal
 -l, --list=[<signal>]  list all signal names, or convert one to a name
 -L, --table            list all signal names in a nice table

 -h, --help     display this help and exit
 -V, --version  output version information and exit

For more details see kill(1).



---------------------------------------------------------------------------

CalledProcessError                        Traceback (most recent call last)

/tmp/ipykernel_92726/1062927812.py in <module>
----> 1 get_ipython().run_cell_magic('script', 'bash', "\nlsof -i :5001 | awk '/Python/ {print $2}' | xargs kill -9\n")


/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py in run_cell_magic(self, magic_name, line, cell)
   2417             with self.builtin_trap:
   2418                 args = (magic_arg_s, cell)
-> 2419                 result = fn(*args, **kwargs)
   2420             return result
   2421 


<decorator-gen-103> in shebang(self, line, cell)


/usr/lib/python3/dist-packages/IPython/core/magic.py in <lambda>(f, *a, **k)
    185     # but it's overkill for just that one bit of state.
    186     def magic_deco(arg):
--> 187         call = lambda f, *a, **k: f(*a, **k)
    188 
    189         if callable(arg):


/usr/lib/python3/dist-packages/IPython/core/magics/script.py in shebang(self, line, cell)
    243             sys.stderr.flush()
    244         if args.raise_error and p.returncode!=0:
--> 245             raise CalledProcessError(p.returncode, cell, output=out, stderr=err)
    246 
    247     def _run_script(self, p, cell, to_close):


CalledProcessError: Command 'b"\nlsof -i :5001 | awk '/Python/ {print $2}' | xargs kill -9\n"' returned non-zero exit status 123.

Hacks

Edit, stop and start the web server.

  • Add to the Home Page
  • Add your own information to the Web API
  • Use from Template to start your own Team Flask project https://github.com/nighthawkcoders/flask_portfolio