Skip to the content.

BI 4.1 Internet - Web Server

Quick launch into Flask, sending and receiving data from a web server.

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.

Install required libraries

These libraries are required to run and interact with the Python web server.

!pip install flask flask-cors requests
Collecting flask
  Using cached flask-3.1.0-py3-none-any.whl.metadata (2.7 kB)
Collecting flask-cors
  Using cached Flask_Cors-5.0.0-py2.py3-none-any.whl.metadata (5.5 kB)
Requirement already satisfied: requests in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (2.32.3)
Collecting Werkzeug>=3.1 (from flask)
  Using cached werkzeug-3.1.3-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: Jinja2>=3.1.2 in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (from flask) (3.1.4)
Collecting itsdangerous>=2.2 (from flask)
  Using cached itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
Collecting click>=8.1.3 (from flask)
  Using cached click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
Collecting blinker>=1.9 (from flask)
  Using cached blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (from requests) (3.4.0)
Requirement already satisfied: idna<4,>=2.5 in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (from requests) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (from requests) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (from requests) (2024.8.30)
Requirement already satisfied: MarkupSafe>=2.0 in /home/hannahli11/hannahli_2025/venv/lib/python3.12/site-packages (from Jinja2>=3.1.2->flask) (3.0.2)
Using cached flask-3.1.0-py3-none-any.whl (102 kB)
Using cached Flask_Cors-5.0.0-py2.py3-none-any.whl (14 kB)
Using cached blinker-1.9.0-py3-none-any.whl (8.5 kB)
Using cached click-8.1.7-py3-none-any.whl (97 kB)
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Using cached werkzeug-3.1.3-py3-none-any.whl (224 kB)
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask, flask-cors
Successfully installed Werkzeug-3.1.3 blinker-1.9.0 click-8.1.7 flask-3.1.0 flask-cors-5.0.0 itsdangerous-2.2.0

Start Web Server

This Python code provides a simple server with an accessible API.

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, World!</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)
Explore the Python/Flask process with Linux
This script discovers the running flask process on your machine using Linux commands.

lsof - list open files 
lsof and awk return the process id, so ps can list details, the vericle bar is called a pipe. A pipe flows output from one command to the next.
curl is a Linux utiltity that is easiest way to test if web server is responding

%%script bash
# After app.run(), see the the Python open files on port 5001
echo "Python open files on port 5001" 
lsof -i :5001
# see the the Python process 
echo
echo "Python process"
lsof -i :5001 | awk '/Python/ {print $2}' | xargs ps
# show ontent of the Python server using curl
echo
echo "Content of the Python root endpoint (aka /), using curl",  
curl http://localhost:5001/
Python open files on port 5001

Python process


your 131072x1 screen size is bogus. expect trouble


    PID TTY          TIME CMD
    831 pts/0    00:00:00 sh
    832 pts/0    00:00:00 sh
    837 pts/0    00:00:00 sh
    841 pts/0    00:01:12 node
    891 pts/0    00:00:11 node
    905 pts/0    00:04:49 node
    942 pts/0    00:00:25 node
    981 pts/0    00:00:01 pet
   1292 pts/0    00:00:13 node
   3810 pts/0    00:00:02 node
   6242 pts/0    00:00:05 node
  11887 pts/0    00:00:07 node
  11901 pts/0    00:02:07 node
  11977 pts/0    00:00:02 pet
  12318 pts/0    00:00:51 node
  14568 pts/0    00:00:02 node
  15891 pts/0    00:00:05 python
  18031 pts/0    00:00:05 node
  20760 pts/0    00:00:00 bash
  20767 pts/0    00:00:00 xargs
  20768 pts/0    00:00:00 ps

Content of the Python root endpoint (aka /), using curl,


  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (7) Failed to connect to localhost port 5001 after 8 ms: Couldn't connect to server



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

CalledProcessError                        Traceback (most recent call last)

Cell In[12], line 1
----> 1 get_ipython().run_cell_magic('script', 'bash', '# After app.run(), see the the Python open files on port 5001\necho "Python open files on port 5001" \nlsof -i :5001\n# see the the Python process \necho\necho "Python process"\nlsof -i :5001 | awk \'/Python/ {print $2}\' | xargs ps\n# show ontent of the Python server using curl\necho\necho "Content of the Python root endpoint (aka /), using curl",  \ncurl http://localhost:5001/\n')


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2541, in InteractiveShell.run_cell_magic(self, magic_name, line, cell)
   2539 with self.builtin_trap:
   2540     args = (magic_arg_s, cell)
-> 2541     result = fn(*args, **kwargs)
   2543 # The code below prevents the output from being displayed
   2544 # when using magics with decorator @output_can_be_silenced
   2545 # when the last Python token in the expression is a ';'.
   2546 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/IPython/core/magics/script.py:315, in ScriptMagics.shebang(self, line, cell)
    310 if args.raise_error and p.returncode != 0:
    311     # If we get here and p.returncode is still None, we must have
    312     # killed it but not yet seen its return code. We don't wait for it,
    313     # in case it's stuck in uninterruptible sleep. -9 = SIGKILL
    314     rc = p.returncode or -9
--> 315     raise CalledProcessError(rc, cell)


CalledProcessError: Command 'b'# After app.run(), see the the Python open files on port 5001\necho "Python open files on port 5001" \nlsof -i :5001\n# see the the Python process \necho\necho "Python process"\nlsof -i :5001 | awk \'/Python/ {print $2}\' | xargs ps\n# show ontent of the Python server using curl\necho\necho "Content of the Python root endpoint (aka /), using curl",  \ncurl http://localhost:5001/\n'' returned non-zero exit status 7.

Access data from our Flask server using Python

The code block below shows alternate ways to access the Web Server.

  1. Import requests and use it to obtain response from endpoints
  2. The response is a Python object that contains status codes and data
  3. The data can be in different forms, we will be focussed on JSON responses in Full-Stack.
import requests
from IPython.display import HTML, display

# call api root endpoint (aka '/'), often called home page
response = requests.get('http://127.0.0.1:5001/')
# output response in different forms
print("Print Status Message:", response)
print("\nPrint Raw HTML:\n", response.text)
display(HTML(response.text))

# call unknown api endpoint
response = requests.get('http://127.0.0.1:5001/api/data')
print("Print Status Message:", response)
---------------------------------------------------------------------------

ConnectionRefusedError                    Traceback (most recent call last)

File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:196, in HTTPConnection._new_conn(self)
    195 try:
--> 196     sock = connection.create_connection(
    197         (self._dns_host, self.port),
    198         self.timeout,
    199         source_address=self.source_address,
    200         socket_options=self.socket_options,
    201     )
    202 except socket.gaierror as e:


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/util/connection.py:85, in create_connection(address, timeout, source_address, socket_options)
     84 try:
---> 85     raise err
     86 finally:
     87     # Break explicitly a reference cycle


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/util/connection.py:73, 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)

File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connectionpool.py:789, in HTTPConnectionPool.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)
    788 # Make the request on the HTTPConnection object
--> 789 response = self._make_request(
    790     conn,
    791     method,
    792     url,
    793     timeout=timeout_obj,
    794     body=body,
    795     headers=headers,
    796     chunked=chunked,
    797     retries=retries,
    798     response_conn=response_conn,
    799     preload_content=preload_content,
    800     decode_content=decode_content,
    801     **response_kw,
    802 )
    804 # Everything went great!


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connectionpool.py:495, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    494 try:
--> 495     conn.request(
    496         method,
    497         url,
    498         body=body,
    499         headers=headers,
    500         chunked=chunked,
    501         preload_content=preload_content,
    502         decode_content=decode_content,
    503         enforce_content_length=enforce_content_length,
    504     )
    506 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
    507 # legitimately able to close the connection after sending a valid response.
    508 # With this behaviour, the received response is still readable.


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:398, in HTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
    397     self.putheader(header, value)
--> 398 self.endheaders()
    400 # If we're given a body we start sending that in chunks.


File /usr/lib/python3.12/http/client.py:1331, in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1330     raise CannotSendHeader()
-> 1331 self._send_output(message_body, encode_chunked=encode_chunked)


File /usr/lib/python3.12/http/client.py:1091, in HTTPConnection._send_output(self, message_body, encode_chunked)
   1090 del self._buffer[:]
-> 1091 self.send(msg)
   1093 if message_body is not None:
   1094 
   1095     # create a consistent interface to message_body


File /usr/lib/python3.12/http/client.py:1035, in HTTPConnection.send(self, data)
   1034 if self.auto_open:
-> 1035     self.connect()
   1036 else:


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:236, in HTTPConnection.connect(self)
    235 def connect(self) -> None:
--> 236     self.sock = self._new_conn()
    237     if self._tunnel_host:
    238         # If we're tunneling it means we're connected to our proxy.


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:211, in HTTPConnection._new_conn(self)
    210 except OSError as e:
--> 211     raise NewConnectionError(
    212         self, f"Failed to establish a new connection: {e}"
    213     ) from e
    215 # Audit hooks are only available in Python 3.8+


NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7f02a46acb00>: 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)

File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/adapters.py:667, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    666 try:
--> 667     resp = conn.urlopen(
    668         method=request.method,
    669         url=url,
    670         body=request.body,
    671         headers=request.headers,
    672         redirect=False,
    673         assert_same_host=False,
    674         preload_content=False,
    675         decode_content=False,
    676         retries=self.max_retries,
    677         timeout=timeout,
    678         chunked=chunked,
    679     )
    681 except (ProtocolError, OSError) as err:


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connectionpool.py:843, in HTTPConnectionPool.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)
    841     new_e = ProtocolError("Connection aborted.", new_e)
--> 843 retries = retries.increment(
    844     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    845 )
    846 retries.sleep()


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/util/retry.py:519, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    518     reason = error or ResponseError(cause)
--> 519     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    521 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)


MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=5001): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f02a46acb00>: 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)

Cell In[8], line 5
      2 from IPython.display import HTML, display
      4 # call api root endpoint (aka '/'), often called home page
----> 5 response = requests.get('http://127.0.0.1:5001/')
      6 # output response in different forms
      7 print("Print Status Message:", response)


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/api.py:73, in get(url, params, **kwargs)
     62 def get(url, params=None, **kwargs):
     63     r"""Sends a GET request.
     64 
     65     :param url: URL for the new :class:`Request` object.
   (...)
     70     :rtype: requests.Response
     71     """
---> 73     return request("get", url, params=params, **kwargs)


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/api.py:59, in request(method, url, **kwargs)
     55 # By using the 'with' statement we are sure the session is closed, thus we
     56 # avoid leaving sockets open which can trigger a ResourceWarning in some
     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)


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    584 send_kwargs = {
    585     "timeout": timeout,
    586     "allow_redirects": allow_redirects,
    587 }
    588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
    591 return resp


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
    700 start = preferred_clock()
    702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
    705 # Total elapsed time of the request (approximately)
    706 elapsed = preferred_clock() - start


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/adapters.py:700, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    696     if isinstance(e.reason, _SSLError):
    697         # This branch is for urllib3 v1.22 and later.
    698         raise SSLError(e, request=request)
--> 700     raise ConnectionError(e, request=request)
    702 except ClosedPoolError as e:
    703     raise ConnectionError(e, request=request)


ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=5001): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f02a46acb00>: Failed to establish a new connection: [Errno 111] Connection refused'))
import requests
# an api endpoint most commonly returns JSON data
response = requests.get('http://127.0.0.1:5001/api/data')
response.json()
---------------------------------------------------------------------------

ConnectionRefusedError                    Traceback (most recent call last)

File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:196, in HTTPConnection._new_conn(self)
    195 try:
--> 196     sock = connection.create_connection(
    197         (self._dns_host, self.port),
    198         self.timeout,
    199         source_address=self.source_address,
    200         socket_options=self.socket_options,
    201     )
    202 except socket.gaierror as e:


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/util/connection.py:85, in create_connection(address, timeout, source_address, socket_options)
     84 try:
---> 85     raise err
     86 finally:
     87     # Break explicitly a reference cycle


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/util/connection.py:73, 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)

File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connectionpool.py:789, in HTTPConnectionPool.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)
    788 # Make the request on the HTTPConnection object
--> 789 response = self._make_request(
    790     conn,
    791     method,
    792     url,
    793     timeout=timeout_obj,
    794     body=body,
    795     headers=headers,
    796     chunked=chunked,
    797     retries=retries,
    798     response_conn=response_conn,
    799     preload_content=preload_content,
    800     decode_content=decode_content,
    801     **response_kw,
    802 )
    804 # Everything went great!


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connectionpool.py:495, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    494 try:
--> 495     conn.request(
    496         method,
    497         url,
    498         body=body,
    499         headers=headers,
    500         chunked=chunked,
    501         preload_content=preload_content,
    502         decode_content=decode_content,
    503         enforce_content_length=enforce_content_length,
    504     )
    506 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
    507 # legitimately able to close the connection after sending a valid response.
    508 # With this behaviour, the received response is still readable.


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:398, in HTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
    397     self.putheader(header, value)
--> 398 self.endheaders()
    400 # If we're given a body we start sending that in chunks.


File /usr/lib/python3.12/http/client.py:1331, in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1330     raise CannotSendHeader()
-> 1331 self._send_output(message_body, encode_chunked=encode_chunked)


File /usr/lib/python3.12/http/client.py:1091, in HTTPConnection._send_output(self, message_body, encode_chunked)
   1090 del self._buffer[:]
-> 1091 self.send(msg)
   1093 if message_body is not None:
   1094 
   1095     # create a consistent interface to message_body


File /usr/lib/python3.12/http/client.py:1035, in HTTPConnection.send(self, data)
   1034 if self.auto_open:
-> 1035     self.connect()
   1036 else:


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:236, in HTTPConnection.connect(self)
    235 def connect(self) -> None:
--> 236     self.sock = self._new_conn()
    237     if self._tunnel_host:
    238         # If we're tunneling it means we're connected to our proxy.


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connection.py:211, in HTTPConnection._new_conn(self)
    210 except OSError as e:
--> 211     raise NewConnectionError(
    212         self, f"Failed to establish a new connection: {e}"
    213     ) from e
    215 # Audit hooks are only available in Python 3.8+


NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7f028ac445f0>: 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)

File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/adapters.py:667, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    666 try:
--> 667     resp = conn.urlopen(
    668         method=request.method,
    669         url=url,
    670         body=request.body,
    671         headers=request.headers,
    672         redirect=False,
    673         assert_same_host=False,
    674         preload_content=False,
    675         decode_content=False,
    676         retries=self.max_retries,
    677         timeout=timeout,
    678         chunked=chunked,
    679     )
    681 except (ProtocolError, OSError) as err:


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/connectionpool.py:843, in HTTPConnectionPool.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)
    841     new_e = ProtocolError("Connection aborted.", new_e)
--> 843 retries = retries.increment(
    844     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    845 )
    846 retries.sleep()


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/urllib3/util/retry.py:519, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    518     reason = error or ResponseError(cause)
--> 519     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    521 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)


MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=5001): Max retries exceeded with url: /api/data (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f028ac445f0>: 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)

Cell In[9], line 3
      1 import requests
      2 # an api endpoint most commonly returns JSON data
----> 3 response = requests.get('http://127.0.0.1:5001/api/data')
      4 response.json()


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/api.py:73, in get(url, params, **kwargs)
     62 def get(url, params=None, **kwargs):
     63     r"""Sends a GET request.
     64 
     65     :param url: URL for the new :class:`Request` object.
   (...)
     70     :rtype: requests.Response
     71     """
---> 73     return request("get", url, params=params, **kwargs)


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/api.py:59, in request(method, url, **kwargs)
     55 # By using the 'with' statement we are sure the session is closed, thus we
     56 # avoid leaving sockets open which can trigger a ResourceWarning in some
     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)


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    584 send_kwargs = {
    585     "timeout": timeout,
    586     "allow_redirects": allow_redirects,
    587 }
    588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
    591 return resp


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
    700 start = preferred_clock()
    702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
    705 # Total elapsed time of the request (approximately)
    706 elapsed = preferred_clock() - start


File ~/nighthawk/hannahli2025/venv/lib/python3.12/site-packages/requests/adapters.py:700, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    696     if isinstance(e.reason, _SSLError):
    697         # This branch is for urllib3 v1.22 and later.
    698         raise SSLError(e, request=request)
--> 700     raise ConnectionError(e, request=request)
    702 except ClosedPoolError as e:
    703     raise ConnectionError(e, request=request)


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

Access data from our Flask server using JavaScript

This sample is very similar to Full-Stack as the JavaScript is running through Jupyter and the Web server is a Python Process running on our machine (local server).

  1. HTML is used to setup basics of a table
  2. The script block, has javascript fetch that passes endpoint (url) and options. The options are critical to communicating request requirements.
  3. Similar to python examples, data is extracted and that data is written to the document, which is what is viewable to the user as the page is rendered. Headings are static in the document, but rows are dynamically extracted according to the information contained in the server.

%%html

<h1>Access data from our Flask server using JavaScript</h1>

<p>This code extracts data "live" from a local Web Server with JavaScript fetch.  Additionally, it formats the data into a table.</p>

<!-- Head contains information to Support the Document -->


<!-- HTML table fragment for page -->
<table id="demo" class="table">
  <thead>
      <tr>
          <th>First Name</th>
          <th>Last Name</th>
          <th>Residence</th>
      </tr>
  </thead>
  <tbody id="result">
    <!-- javascript generated data -->
  </tbody>
</table>

<script>
  // prepare HTML result container for new output
  let resultContainer = document.getElementById("result");
  
  // prepare URL
  url = "http://127.0.0.1:5001/api/data";

  // set options for cross origin header request
  let options = {
    method: 'GET', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'include', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json',
    },
  };

  // fetch the API
  fetch(url, options)
    // response is a RESTful "promise" on any successful fetch
    .then(response => {
      // check for response errors and display
      if (response.status !== 200) {
          console.error(response.status);
          return;
      }
      // valid response will contain json data
      response.json().then(data => {
          console.log(data);
          for (const row of data) {
            // tr and td build out for each row
            const tr = document.createElement("tr");
            const firstname = document.createElement("td");
            const lastname = document.createElement("td");
            const residence = document.createElement("td");
            // data is specific to the API
            firstname.innerHTML = row.FirstName; 
            lastname.innerHTML = row.LastName; 
            residence.innerHTML = row.Residence; 
            // this builds each td into tr
            tr.appendChild(firstname);
            tr.appendChild(lastname);
            tr.appendChild(residence);
            // add HTML to container
            resultContainer.appendChild(tr);
          }
      })
  })
  
</script>

Stop the Python/Flask process

This script ends Python/Flask process using pipes to obtain the python process. Then echo the python process to kill -9.

%%script bash

python_ps=$(lsof -i :5001 | awk '/Python/ {print $2}')
echo "Killing python process with PID: $python_ps"
echo $python_ps | xargs kill -9

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/flocker_backend