Flask: Difference between revisions

From XPUB & Lens-Based wiki
Line 304: Line 304:
==Running Flask applications on the collective hub.xpub.nl servers==
==Running Flask applications on the collective hub.xpub.nl servers==


===gunicorn===
There have been different attempts in the past:


First install gunicorn and python-dotenv:
* use the officially recommended PrefixMiddleware! (100% success rate, see below)
 
* for Kamo's notes: https://git.xpub.nl/kamo/pad-bis#nginx-configuration
$ uv pip3 install gunicorn python-dotenv
* use gunicorn: see [[Flask/Gunicorn]]
 
* Flask official deployment page: https://flask.palletsprojects.com/en/stable/deploying/
And then make or change the following files:


'''Makefile'''
===PrefixMiddleware===


<pre>
To install your flask application on the sandbox server (sergio, cerealbox, etc), you need to take a few steps:
include .env
export


default: local
# upload your code to the server (i would recommend to work with a git repository!)
# add prefix.py to your flask applications
# configure nginx
# install your flask application as a systemd background process


local:
====add prefix.py====
        @flask --app ${APPLICATION_NAME} --debug run
Add these two lines to your flask application script (there is an example [https://git.xpub.nl/manetta/flask-observations/src/branch/main/observations.py here]):


server:
<syntaxhighlight lang="python">
        @SCRIPT_NAME=${APPLICATION_ROOT} gunicorn -b localhost:${PORTNUMBER} --reload ${APPLICATION_NAME}:app
# there should be no slash at the end of this line!!
</pre>
app.wsgi_app = PrefixMiddleware(app.wsgi_app, '/sergio/SI30/fieldwork-tools/YOURFOLDER')
</syntaxhighlight>


'''.env'''
Add the following script to your flask folder and save it as <code>prefix.py</code>:


<pre>
<syntaxhighlight lang="python">
APPLICATION_NAME=observations
class PrefixMiddleware(object):
APPLICATION_ROOT=/sergio/manettasobservations
PORTNUMBER=5100
</pre>


'''settings.py'''
    def __init__(self, app, prefix=''):
        self.app = app
        self.prefix = prefix


<pre>
    def __call__(self, environ, start_response):
import os
from dotenv import main


# Load environment variables from the .env file
        if environ['PATH_INFO'].startswith(self.prefix):
main.load_dotenv()
            environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
            environ['SCRIPT_NAME'] = self.prefix
            return self.app(environ, start_response)
        else:
            start_response('404', [('Content-Type', 'text/plain')])
            return ["This url does not belong to the app.".encode()]
</syntaxhighlight>


# Bind them to Python variables
====configure nginx====
APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/')
PORTNUMBER = int(os.environ.get('PORTNUMBER', 5000))
</pre>


'''app.py'''
Edit the nginx <code>hub.xpub.nl</code> config file:


<pre>
sudo nano /etc/nginx/sites-enabled/hub.xpub.nl
# load the settings of the applicaiton
# (to handle the routing correctly)
app.config.from_pyfile('settings.py')
</pre>


'''nginx'''
Scroll down until you find "Flask applications here".


<pre>
Copy this example into that section, and edit "YOURFOLDER" to your foldername.
# overlap flask application
location ^~ /overlap/static/ {
    alias /var/www/html/breadbrick/SI21/week5/static/;
    autoindex on;
}


location ^~ /overlap/ {
<span style="color:magenta;">Tip! Give your tool a name. Wouldn't it be cool to not call it "Manetta's observations", but something else, so it describes what it does? And multiple people can use it?</span>
    proxy_pass http://localhost:5000/breadcube/overlap/;
}
</pre>


===Other ways to do deployment===
        location /sergio/SI30/fieldwork-tools/YOURFOLDER/ {
                proxy_pass http://localhost:5100/sergio/SI30/fieldwork-tools/YOURFOLDER/;
                include proxy_params;
        }


* for Kamo's notes: https://git.xpub.nl/kamo/pad-bis#nginx-configuration
====systemd background process====
* Flask official deployment page: https://flask.palletsprojects.com/en/stable/deploying/


==See also==
==See also==


* [[File uploads on a web page]] using Flask
* [[File uploads on a web page]] using Flask

Revision as of 15:52, 8 April 2026

Flask

https://flask.palletsprojects.com/

Flask is a microframework for python to create web applications. It basically connects the webserver with your python code, and it uses Jinja as a template engine.

So basically... it brings HTML + CSS + PYTHON + JINJA together!

Documentation

Flask in practice

Flask was used in the past for different things at XPUB, including the XPPL, Euna's graduation work Frabjousish, the Padliography made by Kamo, octomode made by Manetta, and ... more!

Flask is something that you can use to make things like interactive web pages and artistic tools. It's not too difficult to work with and can do great stuff!

It's great for connecting the operating system of your computer/server with a web page.

However

  • dynamic, not static
  • relies on full server access (web-hosting is not enough)
    • alternative: php
    • alternatice: javascript + node
  • adding layers of complexity: nginx, background service
  • can quickly disappear (harder to archive compared to static pages)
  • and! the trap of making something "interactive"
  • also: security is something to keep into account

So never forget to keep in mind:

  • how can this project be archived? (for example: static export)
  • for who are you making this interactive thing?
  • how public are my POST requests

Install

$ pip install Flask

or

$ uv venv
$ uv pip install flask

or

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install flask

Simple text serving

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"
  • with @app.route you can define the url flask respons to.
  • the function definition after is mandetory as well as the return.
  • everything that comes after return gets sent back to the browser (http GET request)

Save the code above as hello.py.

Run it with:

$ flask --app hello run

And if you want to enable the debugger (recommended!):

$ flask --app hello --debug run

Paths

You can use any route you like

@app.route("/any/route/you/like")

You can also use variable routes (example for int)

@app.route("/book/<int:id>")
def book(id):

as you can see you can grab the variable in the url through the function’s parameter

(example for string)

@app.route("/book/<bookname>")
def book(bookname):

HTTP Methods

Methods like GET or POST (DELETE, PUT…) can be handled by flask

Therefore you need to add the wanted methods to the route definition like:

from flask import Flask
app = Flask(__name__)

@app.route('/address_to_post', methods= ['POST','GET'])
def respond_to_post():
    answer = ""
    if request.method == 'GET':
        answer = "get"
    if request.method == 'POST':
        answer = "post"
    return answer

with request.method you can determine the incoming kind of request.

Templates

To be able return full html pages, flask uses templates using Jinja to insert variable content.

@app.route('/')
def home():
    message = "Welcome Home!"
    return render_template('home.html', message=message)

You can pass as many variables to a template and keep the code separate from the HTML it generates. In this example we pass message to this template:

<p>{{ message }}</p>

!important: The template file needs to be saved inside a templates folder called "templates" inside your project folder, this is a Flask default.

Helpful function

404 Page not found

@app.errorhandler(404)
def page_not_found(error):
    """Custom 404 page."""
    return render_template('404.html'), 404

Examples

Observations

This prototype is prepared for class during SI30 in April 2026.

A field work tool prototype made in Flask.

To run this example, you can use this command:

flask --app observations --debug run

Make a folder for this flask application.

Install a venv + flask in this folder:

cd /path/to/your/folder/
uv venv
uv pip install flask 

Save the files below in this folder, it should look like this:

.
├── observations.py
└── templates
    └── observations.html

Python script

observations.py

from flask import Flask, render_template, request
app = Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def main():
    if request.method == "POST":
        observation = request.form["observation"]
        
        # WRITE TO DATABASE
        with open("database.txt", "a") as database:
            database.write(observation + "\n")
    
    # READ DATABASE
    database = open("database.txt", "r").readlines()
    
    return render_template("observations.html", database=database)

Template

templates/observations.html

<style>
body{
    text-align: center;
}
div#archive{
    background-color: lightgray;
    padding: 1em;
}
div.observation{
    background-color: white;
    border: 1px solid black;
    padding: 1em;
    margin: 0.5em 0;
}
form{
    margin: 1em;
}
form textarea{
    width: 100%;
    height: 200px;
}
form input{
    margin-top: 1em;
    font-size: 12pt;
}
</style>

<h1>My observations</h1>

<div id="archive">
{% for observation in database %}
<div class="observation">{{ observation }}</div>
{% endfor %}
</div>

<form method="POST">
    <textarea name="observation"></textarea>
    <input type="submit" value="SAVE">
</form>

Next steps

  • save the observations to a observations.txt file
  • read the observations from the file (not from the list anymore)
  • (optional) explore other types of possible field work tools: can flask be used to annotate a map?
  • (optional) install your flask application on the sandbox server: configure nginx, make a systemd file to let it run as a background process and use gunicorn for the URL routing

My Unicode converter

Example of a flask application that converts characters to unicode points

This is a small flask application that converts character into unicode points. It is made to show how you can make tiny tools, and how you can connect python to the web.

from flask import Flask, request
app = Flask(__name__)

html_template = """
<h1>transformations</h1>
<form action="/" method="post">
<input type="text" name="search">
<br><br>
<input type="submit" value="transform">
</form>
"""

@app.route("/", methods=["POST","GET"])
def transformations():
    if request.method == "POST":

        search = request.form["search"]
        
        result_list = []
        for character in search:
            unicode_point = format(ord(character))
            result_list.append(character + " " + unicode_point)

        result_string = "<br>\n".join(result_list)

        return html_template + f"<pre>{ result_string }</pre>"
    
    if request.method == "GET":
    
        return html_template

Deploying

Running with uwsgi

Flask warns you that using "the development server" isn't good for "production use".

Using software like uwsgi to run your flask project on a server is a good idea. It:

  • Is able to deal with many users at the same time
  • When configured right lets large static files (like images / videos) be served in a way that works well

https://hackersandslackers.dev/deploy-flask-uwsgi-nginx/

Making a systemd service file

When you deploy your application on a server, you need to make sure the application runs uninterrupted. If the application crashes, you'd want it to automatically restart, and if the server experiences a power outage, you'd want the application to start immediately once power is restored.

SEE: https://blog.miguelgrinberg.com/post/running-a-flask-application-as-a-service-with-systemd

Useful: https://containersolutions.github.io/runbooks/posts/linux/debug-systemd-service-units/

$ sudo nano /etc/systemd/system/YOUR_NAME_OF_THE_APP.service
[Unit]
Description=<a description of your application>
After=network.target

[Service]
User=<username>
WorkingDirectory=<path to your app>
ExecStart=<app start command>
Restart=always

[Install]
WantedBy=multi-user.target

Here is an example:

 [Unit]
 Description=Bla Bla Bla made in Flask
 After=network.target
 
 [Service]
 User=YOUR_USERNAME
 WorkingDirectory=/YOUR/PATH/
 ExecStart=/YOUR/PATH/.venv/bin/flask --app NAME_OF_YOUR_PY_FILE run --port 51?? #DO NOT PUT .py AT THE END + #PUT YOUR_PORT
 Restart=always
 
 [Install]
 WantedBy=multi-user.target

1st thing to do when your .service file is new or changed

$ sudo systemctl daemon-reload

Then in order:

$ sudo systemctl start YOUR_NAME_OF_THE_APP
$ sudo systemctl status YOUR_NAME_OF_THE_APP

When you see that it's working (checking its status, that it actually is running, etc...)

$ sudo systemctl enable YOUR_NAME_OF_THE_APP

Will make the "service" auto start when the pi restarts.

Additional commands:

$ sudo systemctl restart YOUR_NAME_OF_THE_APP
$ sudo systemctl stop YOUR_NAME_OF_THE_APP

To view the log file (errors):

$ sudo journalctl -u YOUR_NAME_OF_THE_APP -f

You can find documentation here:

https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html

Running Flask applications on the collective hub.xpub.nl servers

There have been different attempts in the past:

PrefixMiddleware

To install your flask application on the sandbox server (sergio, cerealbox, etc), you need to take a few steps:

  1. upload your code to the server (i would recommend to work with a git repository!)
  2. add prefix.py to your flask applications
  3. configure nginx
  4. install your flask application as a systemd background process

add prefix.py

Add these two lines to your flask application script (there is an example here):

# there should be no slash at the end of this line!!
app.wsgi_app = PrefixMiddleware(app.wsgi_app, '/sergio/SI30/fieldwork-tools/YOURFOLDER')

Add the following script to your flask folder and save it as prefix.py:

class PrefixMiddleware(object):

    def __init__(self, app, prefix=''):
        self.app = app
        self.prefix = prefix

    def __call__(self, environ, start_response):

        if environ['PATH_INFO'].startswith(self.prefix):
            environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
            environ['SCRIPT_NAME'] = self.prefix
            return self.app(environ, start_response)
        else:
            start_response('404', [('Content-Type', 'text/plain')])
            return ["This url does not belong to the app.".encode()]

configure nginx

Edit the nginx hub.xpub.nl config file:

sudo nano /etc/nginx/sites-enabled/hub.xpub.nl

Scroll down until you find "Flask applications here".

Copy this example into that section, and edit "YOURFOLDER" to your foldername.

Tip! Give your tool a name. Wouldn't it be cool to not call it "Manetta's observations", but something else, so it describes what it does? And multiple people can use it?

        location /sergio/SI30/fieldwork-tools/YOURFOLDER/ {
                proxy_pass http://localhost:5100/sergio/SI30/fieldwork-tools/YOURFOLDER/;
                include proxy_params;
        }

systemd background process

See also