Flask Essentials


Introduction

Flask is a versatile and lightweight web framework for Python, well-suited for developing web applications and APIs with simplicity and flexibility. Flask plays a crucial role in deploying and serving machine learning models as web services. It allows data scientists and developers to build scalable and interactive applications around their machine learning models.

If you haven't already, I highly recommend you, checking out the first blog in my MLOps series on Git and GitHub essentials here.


Exploring Official Flask Documentation

Before we start coding, it's beneficial to visit the official Flask documentation to understand its core concepts and features, ensuring a solid foundation for our project.

Setting Up the Environment

Creating a Conda Environment

To manage dependencies and isolate our project, we'll create a virtual environment using Conda.

conda create -p venv python==<if_you_want_a_specific_version_like_3.9>

Activate the environment:

conda activate venv

Managing Dependencies with requirements.txt

Create a requirements.txt file to list project dependencies:

echo Flask > requirements.txt

Install dependencies:

pip install -r requirements.txt

Creating a Simple Flask Application

Let's build a basic Flask application to understand its fundamental concepts.

Create a file and name it as 'app.py'

app.py

from flask import Flask

# Create an instance of Flask
app = Flask(__name__)

# Define a route and its handler
@app.route('/')
def hello():
    return "Hello, World!"

if __name__ == "__main__":
    app.run(debug=True)

Flask Instance:

When we initialize a Flask application instance using app = Flask(__name__), we are creating the core of our web application. Here’s what happens in detail:

from flask import Flask

# Create an instance of Flask
app = Flask(__name__)
  • Explanation:

    • Flask(__name__) creates an instance of the Flask class. The __name__ variable is a special Python variable that represents the name of the current module. When using a single module (as in most Flask applications), __name__ is typically set to "__main__".

    • This instance app will be used to configure and run the Flask application.

Route Definition:

Flask uses decorators to bind URL paths to Python functions that handle them. Here’s how we define a route:

@app.route('/')
def hello():
    return "Hello, World!"
  • Explanation:

    • @app.route('/') is a decorator provided by Flask. It binds the URL path '/' to the hello() function.

    • When a user visits the root URL of our web application ('/'), Flask calls the hello() function and returns "Hello, World!" as the response.

    • Decorators in Python are functions that modify the behavior of other functions or methods. In this case, @app.route('/') tells Flask that the hello() function should be executed when someone accesses the root URL.

if __name__ == "__main__":

This conditional statement ensures that the Flask application runs only when the script is executed directly, not when it is imported as a module into another script.

if __name__ == "__main__":
    app.run(debug=True)
  • Explanation:

    • __name__ is a special Python variable that is automatically set to "__main__" when the Python script is executed directly.

    • app.run() starts the Flask development server. The debug=True argument enables debug mode, which helps in troubleshooting by providing detailed error messages and automatic reloading of the server when changes are made to the code.

    • By using if __name__ == "__main__":, we ensure that app.run() is only executed when the script is run directly using pythonapp.py, and not when the module app is imported into another Python script.


Routing in Flask

Handling Different Routes and HTTP Methods

Routing in Flask maps URLs to functions that handle HTTP requests. Let's explore different routes and methods.

Example: Routing and Methods

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Index Page"

@app.route('/login', methods=['POST'])
def login():
    return 'Login Page'

In Flask, the @app.route() decorator is used to associate a URL path with a Python function that handles requests to that path.

@app.route('/')
def index():
    return "Index Page"

@app.route('/login', methods=['POST'])
def login():
    return 'Login Page'
  • Explanation:

    • @app.route('/path') is a decorator that maps a specific URL path ('/', '/login' in these examples) to a corresponding view function (index(), login()).

    • When a client makes an HTTP request to the specified path ('/' or '/login'), Flask invokes the associated view function (index() or login()).

    • Each view function returns a response that will be sent back to the client. Here, "Index Page" and "Login Page" are simple strings, but typically, these functions would render HTML templates or return JSON responses.

methods:

The methods parameter in the @app.route() decorator specifies which HTTP methods are allowed for accessing the route. By default, routes in Flask only respond to GET requests. Here’s how we specify other methods like POST:

@app.route('/login', methods=['POST'])
def login():
    return 'Login Page'
  • Explanation:

    • methods=['GET'] inside the decorator indicates that the route ('/' in this case) will only respond to HTTP GET requests by default.

    • In the second example, methods=['POST'] specifies that the login() function should handle HTTP POST requests to the /login endpoint.

    • Allowed methods can include GET, POST, PUT, DELETE, and more. This parameter helps define how clients interact with the web application and what actions they can perform.


URL Parameters and Variable Rules

URL parameters allow dynamic URLs with variable values. Let's handle user-specific routes.

@app.route('/user/<username>')
def show_user_profile(username):
    return f'User {username}'

Explanation:

  • Variable Rules: <username> in the route captures URL segments as variables (username in show_user_profile(username)).


    In Flask, variable rules allow us to create dynamic URLs by capturing parts of the URL and passing them as parameters to view functions. This is achieved using <variable_name> syntax within the route definition.

      @app.route('/user/<username>')
      def show_user_profile(username):
          return f'User {username}'
    

    Explanation:

    • <username> in the route path ('/user/<username>') is a variable rule. It captures the part of the URL after /user/ and passes it as an argument to the show_user_profile() function.

    • When a request is made to a URL like /user/kanishk, Flask extracts kanishk from the URL and passes it as the username argument to the show_user_profile() function.

    • The view function show_user_profile(username) then uses this captured value (username) to customize the response. In this case, it returns "User kanishk", but typically it would fetch data related to the username from a database or another source.

Benefits of Variable Rules:

  • Dynamic URLs: Variable rules allow for the creation of dynamic and user-friendly URLs where different users or resources can be accessed using a single route definition.

  • Flexibility: They provide flexibility in handling varying URL patterns without the need to define multiple routes for each possible combination.

  • Parameter Passing: By capturing URL segments as variables, Flask simplifies the process of passing data between the client and the server, enhancing the interactivity and functionality of web applications.


Templates and Rendering

Using Templates with Flask

Flask uses the Jinja2 templating engine to render dynamic HTML templates. Let's create a form using templates.

First, make a folder named 'templates' and create a file named 'form.html'.

templates/form.html

<html>
<head>
    <title>Student Marks Form</title>
</head>
<body>
    <h2>Enter Marks</h2>
    <form action="{{ url_for('form') }}" method="POST">
        <label for="maths">Maths:</label>
        <input type="number" id="maths" name="maths" required><br><br>

        <label for="science">Science:</label>
        <input type="number" id="science" name="science" required><br><br>

        <label for="sanskrit">Sanskrit:</label>
        <input type="number" id="sanskrit" name="sanskrit" required><br><br>

        <button type="submit">Submit</button>
    </form>
</body>
</html>

Explanation:

  • action="{{ url_for('form') }}": Generates the URL for the form endpoint defined in app.py.

  • method="POST": Specifies the HTTP method to send form data.


    action="{{ url_for('form') }}":

    In HTML forms used with Flask applications, the action attribute specifies the URL where the form data should be submitted. The url_for() function in Flask generates URLs for endpoint functions defined in your app.py file.

      <form action="{{ url_for('form') }}" method="POST">
          <!-- Form fields -->
          <button type="submit">Submit</button>
      </form>
    
    • Explanation:

      • action="{{ url_for('form') }}" dynamically generates the URL for the form's action attribute. The url_for() function takes the name of the endpoint ('form' in this case) and returns the URL that corresponds to the form() function in app.py.

      • This approach ensures that if the URL pattern for the form endpoint changes in app.py, the form HTML automatically uses the updated URL without manual editing.

      • Using url_for() enhances maintainability by centralizing URL definitions and reducing the risk of broken links when endpoint URLs are modified.

Benefits of url_for() in Flask:

  • URL Flexibility: Allows developers to change URL routes in Flask applications without needing to update every HTML file manually.

  • Route Centralization: Consolidates URL routing logic within Python code (app.py), promoting cleaner separation of concerns between application logic and presentation.

  • Enhanced Maintenance: Simplifies URL management and maintenance, especially in larger applications with multiple routes and templates.


"POST":

The method attribute in HTML forms specifies the HTTP method to be used when submitting form data to the server. In this case, "POST" is used to send data to the server in the body of the HTTP request.

    <form action="{{ url_for('form') }}" method="POST">
        <!-- Form fields go here -->
        <button type="submit">Submit</button>
    </form>
  • Explanation:

    • method="POST" indicates that the form data should be sent to the server using the HTTP POST method. This method is commonly used for submitting data that modifies server state, such as adding or updating resources.

    • When the form is submitted (<button type="submit">Submit</button>), the browser sends an HTTP POST request to the URL specified in the action attribute ({{ url_for('form') }}), along with the data entered in the form fields.

    • POST requests are suitable for sensitive data or operations that modify server-side data, as they do not expose form data in the URL and can handle larger data payloads compared to GET requests.

Usage Considerations:

  • Data Handling: POST requests are appropriate when dealing with form submissions or any operation that requires sending data to the server for processing or storage.

Handling Form Data and Redirects

In Flask, handling form submissions involves capturing user input, processing it, and redirecting users based on the processed data. Let's explore the example provided in app.py with a focus on each key component.

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

@app.route('/form', methods=['GET', 'POST'])
def form():
    if request.method == 'POST':
        maths = float(request.form['maths'])
        science = float(request.form['science'])
        sanskrit = float(request.form['sanskrit'])
        average_marks = (maths + science + sanskrit) / 3
        result = "Pass" if average_marks >= 35 else "Fail"
        return redirect(url_for(result, score=average_marks))
    return render_template('form.html')

if average marks >= 35, the student will pass:

if the average marks <= 35, then the student will fail:

Explanation:

  • Form Submission Handling (@app.route('/form', methods=['GET', 'POST'])):

    • Defines a route /form that handles both GET and POST requests. This route is responsible for processing form submissions and rendering the initial form.
  • Processing Form Data (if request.method == 'POST':):

    • Checks if the request method is POST, indicating that the form has been submitted.

    • Retrieves form data (maths, science, sanskrit) from request.form and converts them to floats to perform calculations.

  • Calculating Average Marks and Result:

    • Computes average_marks by summing up the scores and dividing by the number of subjects.

    • Uses a conditional statement (result = "Pass" if average_marks >= 35 else "Fail") to determine whether the student passed or failed based on the calculated average marks.

  • Redirecting Based on Result (redirect(url_for(result, score=average_marks))):

    • Uses redirect() and url_for() functions to redirect the user dynamically based on the result variable.

    • url_for(result, score=average_marks) generates the URL for either the Pass or Fail endpoint, passing score=average_marks as a query parameter.

  • Rendering Form Template (return render_template('form.html')):

    • Renders the form.html template when the request method is GET or after processing a POST request.
@app.route('/Pass')
def Pass():
    return f"The student has passed with an average score of {request.args.get('score')}."

@app.route('/Fail')
def Fail():
    return f"The student has failed with an average score of {request.args.get('score')}."

Explanation:

  • Dynamic Routes (@app.route('/Pass') and @app.route('/Fail')):

    • Defines two routes /Pass and /Fail that handle redirections based on whether the student passed or failed.

    • These routes receive the score query parameter passed from the form() function via redirect(url_for(result, score=average_marks)).

  • Displaying Result Messages:

    • Pass() and Fail() functions retrieve the score query parameter using request.args.get('score').

    • They dynamically generate and return messages indicating whether the student passed or failed, along with their average score.

  • Usage ofrequest.args.get('score'):

    • request.args.get('score') retrieves the value of the score query parameter passed from the form() function via redirect(url_for(result, score=average_marks)).

    • This parameter allows the Pass() and Fail() functions to display personalized messages based on the calculated average score.


Creating an API Endpoint

Building a Simple API with Flask

Flask allows us to create API endpoints easily. Let's build an API endpoint that calculates the product of two numbers from a JSON payload.

app.py

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api', methods=['POST'])
def product():
    data = request.get_json()
    value_of_a = float(data['a'])
    value_of_b = float(data['b'])
    product_value = value_of_a * value_of_b
    return jsonify({"product": product_value})

if __name__ == "__main__":
    app.run(debug=True)

Explanation:

  • request.get_json(): Retrieves JSON data from the request.

  • JSON Handling: Converts JSON data ({'a': 13, 'b': 42}) into Python dictionary and performs calculations.

Testing the API with Postman

Now, we'll test our API with the help of Postman, by sending a POST request with a JSON payload.

Testing API Endpoint with Postman

  • Open Postman and select the POST method.

  • Enter the URL (http://localhost:5000/api).

  • Go to the Body tab, select raw, choose JSON format, and paste the JSON object ({"a": 10, "b": 20}).

  • Click Send to see the response.

Conclusion

In this blog, we explored essential aspects of Flask, giving you foundational knowledge to build robust web applications and APIs. From setting up Flask environments to defining routes, handling form data, rendering templates, and creating APIs.

In the next blog, we will set up our project and explore best practices for its development. Read the next blog here.

Did you find this article valuable?

Support Kanishk's Kaleidoscope by becoming a sponsor. Any amount is appreciated!