Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Flask Web and API Development

You're reading from   Mastering Flask Web and API Development Build and deploy production-ready Flask apps seamlessly across web, APIs, and mobile platforms

Arrow left icon
Product type Paperback
Published in Aug 2024
Publisher Packt
ISBN-13 9781837633227
Length 494 pages
Edition 1st Edition
Languages
Tools
Concepts
Arrow right icon
Author (1):
Arrow left icon
Sherwin John C. Tragura Sherwin John C. Tragura
Author Profile Icon Sherwin John C. Tragura
Sherwin John C. Tragura
Arrow right icon
View More author details
Toc

Table of Contents (18) Chapters Close

Preface 1. Part 1:Learning the Flask 3.x Framework
2. Chapter 1: A Deep Dive into the Flask Framework FREE CHAPTER 3. Chapter 2: Adding Advanced Core Features 4. Chapter 3: Creating REST Web Services 5. Chapter 4: Utilizing Flask Extensions 6. Part 2:Building Advanced Flask 3.x Applications
7. Chapter 5: Building Asynchronous Transactions 8. Chapter 6: Developing Computational and Scientific Applications 9. Chapter 7: Using Non-Relational Data Storage 10. Chapter 8: Building Workflows with Flask 11. Chapter 9: Securing Flask Applications 12. Part 3:Testing, Deploying, and Building Enterprise-Grade Applications
13. Chapter 10: Creating Test Cases for Flask 14. Chapter 11: Deploying Flask Applications 15. Chapter 12: Integrating Flask with Other Tools and Frameworks 16. Index 17. Other Books You May Enjoy

Building the data layer with PostgreSQL

PostgreSQL is an object-relational database system, and Flask can utilize it as a data storage platform if the activated virtual environment has the psycopg2-binary extension module. To install this extension module into the venv, run the following command:

pip install psycopg2-binary

Now, we can write an approach to establish a connection to the PostgreSQL database.

Setting up database connectivity

There are multiple ways to create a connection to a database, but this chapter will showcase a Pythonic way to extract that connection using a custom decorator. In the project’s /config directory, there is a connect_db decorator that uses psycopgy2.connect() to establish connectivity to the opcs database of our prototype. Here is the implementation of this custom decorator:

import psycopg2
import functools
from os import environ
def connect_db(func):
    @functools.wraps(func)
    def repo_function(*args, **kwargs):
        conn = psycopg2.connect(
            host=environ.get('DB_HOST'),
            database=environ.get('DB_NAME'),
            port=environ.get('DB_PORT'),
            user = environ.get('DB_USER'),
            password = environ.get('DB_PASS'))
        resp = func(conn, *args, **kwargs)
        conn.commit()
        conn.close()
        return resp
    return repo_function

The given decorator provides the connection instance, conn, to a repository function and commits all the changes to the database after a transaction’s successful execution. Also, it will close the database connection at the end of the process. All the database details, such as DB_HOST, DB_NAME, and DB_PORT, are stored as environment variables inside a .env file. To retrieve them using the environ dictionary of the os module, run the following command to install the required extension:

pip install python-dotenv

However, there are other ways to manage these custom and built-in configuration variables instead of storing them as .env variables. The next topic will expound on this, but first, let’s apply @connect_db to our repository layer.

Implementing the repository layer

The following insert_signup() transaction adds a new user signup record to the database. It gets the conn instance from the @connect_db decorator. Our application has no object-relational mapper yet and solely depends on the psycopg2 driver to perform the CRUD operation. The cursor instance created by conn executes the INSERT statement of the following transaction with form data provided by its view function:

from config.db import connect_db
from typing import Dict, Any, List
@connect_db
def insert_signup(conn, user:str, passw:str, utype:str, fname:str, lname:str, cid:str) -> bool:
    try:
        cur = conn.cursor()
        sql = 'INSERT INTO signup (username, password, user_type, firstname, lastname, cid) VALUES (%s, %s, %s, %s, %s, %s)'
        values = (user, passw, utype, fname, lname, cid)
        cur.execute(sql, values)
        cur.close()
        return True
    except Exception as e:
        cur.close()
        print(e)
    return False

cursor is an object derived from conn that uses a database session to perform insert, update, delete, and fetch operations. So, just like insert_signup(), the following transaction uses cursor again to execute the UPDATE statement:

@connect_db
def update_signup(conn, id:int, details:Dict[str, Any]) -> bool:
    try:
        cur = conn.cursor()
        params = ['{} = %s'.format(key) for key in details.keys()]
        values = tuple(details.values())
        sql = 'UPDATE signup SET {} where id = {}'.format(', '.join(params), id);
        cur.execute(sql, values)
        cur.close()
        return True
    except Exception as e:
        cur.close()
        print(e)
    return False

To complete the CRUD operations for the signup table, here is the DELETE transaction from our application:

@connect_db
def delete_signup(conn, id) -> bool:
    try:
        cur = conn.cursor()
        sql = 'DELETE FROM signup WHERE id = %s'
        values = (id, )
        cur.execute(sql, values)
        cur.close()
        return True
    except Exception as e:
        cur.close()
        print(e)
    return False

The use of an ORM to build the model layer will be part of Chapter 2’s discussions. For now, the views and services of our application rely on a repository layer that manages PostgreSQL data directly through the psycopg2 driver.

After creating the repository layer, many applications can build a service layer to provide loose coupling between the CRUD operations and the views.

Creating the service layer

The service layer of the application builds the business logic of the view functions and the repository. Instead of loading the view functions with transaction-related and business processes, we place all these implementations in the service layer by creating lists of all the counselor and patient IDs, validating where to persist the newly approved user, and creating a list of patients who excelled in the examinations. The following service function evaluates and records patients’ exam scores:

def record_patient_exam(formdata:Dict[str, Any]) -> bool:
    try:
        pct = round((formdata['score']formdata['total']) * 100, 2)
        status = None
        if (pct >= 70):
            status = 'passed'
        elif (pct < 70) and (pct >= 55):
            status = 'conditional'
        else:
            status = 'failed'
        insert_patient_score(pid=formdata['pid'], qid=formdata['qid'], score=formdata['score'], total=formdata['total'], status=status, percentage=pct)
        return True
    except Exception as e:
        print(e)
    return False

Instead of directly accessing insert_patient_score() to save patient exam scores, record_score() accesses the record_patient_exam() service to compute some formulas before invoking insert_patient_score() from the repository layer for record insertion. The service lessens some friction between the database transactions and the view layer. The following snippet is the view function that accesses the record_patient_exam() service for record exam record insertion:

@app.route('/exam/score', methods=['GET', 'POST'])
def record_score():
    if request.method == 'GET':
        pids = list_pid()
        qids = list_qid()
        return render_template( 'exam/add_patient_score_form.html', pids=pids, qids=qids), 200
    else:
        params = dict()
        params['pid'] = int(request.form['pid'])
        params['qid'] = int(request.form['qid'])
        params['score'] = float(request.form['score'])
        params['total'] = float(request.form['total'])
        result = record_patient_exam(params)
        … … … … … … …
        else:
            return redirect('/exam/task/error')

Aside from calling record_patient_exam(), it also utilizes the list_pid() and list_qid() services to retrieve the IDs. The use of services can help separate the abstraction and use cases from the route functions, which has a beneficial impact on the scope, clean coding, and runtime performance of the routes. Moreover, the project structure can also contribute to clear business flow, maintainability, flexibility, and adaptability.

You have been reading a chapter from
Mastering Flask Web and API Development
Published in: Aug 2024
Publisher: Packt
ISBN-13: 9781837633227
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime