Using ORM Patterns With Google Cloud Firestore In Datastore Mode

Using ORM Patterns With Google Cloud Firestore In Datastore Mode

The following example will demonstrate how to use Google Cloud Firestore in Datastore mode as a backend for Flask-Login.

Basic familiarity with the following is assumed:

  • Flask and Flask-Login.
  • App Engine and Firestore in Datastore mode.

Introduction

Flask-Login requires the user(ie application user) to be represented using a Python class with specific properties and methods provided.

The above requirement of Flask-Login is straightforward when using a relational database such as MySQL or Postgres.
Using an ORM toolkit like SQL-Alchemy, you can easily create a user model/class to represent a user in a relational database.
Methods and properties required by Flask-Login can then be added to the user model.

However, for a NoSQL database like Firestore in Datastore mode, this Flask-Login requirement poses a challenge.
This is because the pattern of using a model/class to represent a database record does not directly apply.

This tutorial will demonstrate how to model your Firestore in Datastore mode entity as a Python class.
This will let you conveniently use popular Python libraries like Flask-Login, WTForms etc.

To model our Datastore entity as a Python class, we will use Datastore-Entity library.
Think of Datastore-Entity as an ORM-like library for Firestore in Datastore mode.

Set up your local environment

NOTE: Projects that use the Datastore mode API require an active App Engine application. So you should already have App Engine and Datastore mode API enabled in your GCP project.

As usual, to connect to your Google Cloud service from your local machine you need the appropriate credentials(service account key).

Create and download service account key

  • On the GCP console, go to Main menu -> IAM & Admin -> click Service Account on the left pane -> click CREATE SERVICE ACCOUNT at the top.
  • Give the service account an appropriate name (eg. datastore-service-account). Enter an optional description below and click CREATE
  • Under Select a role dropdown menu, type Cloud Datastore owner in the filter field, select it and click CONTINUE and click DONE on the next page.
  • On the Service Account page, look for the service account you just created and click the overflow icon (vertical ellipsis) at the far right end.
  • Click Create Key from the dropdown menu and make sure JSON is selected as the Key type.
  • Click CREATE. This automatically downloads the service account JSON key to your local machine. Take note of the file name on the popup screen.

Configure your service account on your local machine
Point the environment variable GOOGLE_APPLICATION_CREDENTIALS to the location of the service account key you downloaded.
Linux/macOS

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-acount-key.json"  

Windows
With Powershell

$env:GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-acount-key.json"  

With Command Prompt

set GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-acount-key.json"  

You are now ready to connect to your Firestore in Datastore mode.

Install required libraries

pip install flask flask-login datastore-entity

NOTE: Installing datastore-entity installs the required/official client library for Firestore in Datastore mode.

Create your Flask user model

Model your user entity.

from flask_login import UserMixin
from datastore_entity import DatastoreEntity, EntityValue
import datetime


class User(DatastoreEntity, UserMixin):
    username = EntityValue(None)
    password = EntityValue(None)
    status = EntityValue(1)
    date_created = EntityValue(datetime.datetime.utcnow())

    # name of entity's kind
    __kind__ = "User"

    # other fields or optional methods go here...
    # def authenticated(self, password):
    #    ...

    # def update_activity(): 
    # ...

Implement the Flask ‘login’ view

Once the entity is modelled, you can fetch your user entity using familiar ORM pattern.
Use the methods provided on your model to fetch the user record.

An example flask login view.

#from flask import redirect ...

from flask_login import login_user

# import your model 
from models import User

@page.route("/login", methods=['GET','POST'])
def login():
    form = LoginForm(next=request.args.get('next'))

    if form.validate_on_submit():
        identity = request.form.get('username')
        password = request.form.get('password')

        # fetch user using the 'username' property
        # refer to the datastore-entity documentation
        # for more on simplifying this further
        user = User().get_object('username',identity)

        if user and user.authenticated(password):

            if login_user(user, remember=True):
                user.update_activity()

                #handle optionally redirecting to the next URL safely
                next_url = form.next.data
                if next_url:
                    return redirect(safe_next_url(next_url))

                return redirect(url_for('page/dashboard.html'))
            else:
                flash('This account is not active','error')

        else: 
            flash('Login or password is incorrect','error')

    return render_template("page/login.html", form=form)

More Examples…

Exclude entity properties from indexing.

class User(DatastoreEntity, UserMixin):
    username = EntityValue(None)
    password = EntityValue(None)
    status = EntityValue(1)
    date_last_login = EntityValue(None)
    login_attempts = EntityValue(0)
    date_created = EntityValue(datetime.datetime.utcnow())

    __kind__ = "User"

    # add excluded properties here
    __exclude_from_index__ = ['date_last_login', 'login_attempts']

Dynamically add new entity properties on the fly.
You can add additional properties to an instance of entity on the fly.
Apart from using the extra_props dictionary as keyword argument to the save() method, you can create additional entity properties with the following:

user = User()
user.type = EntityValue(3) # assign attribute value as a type of EntityValue
user.save()

This specific instance of the entity will now have type as a property.

Conclusion

You can employ same approach to model any other Firestore in Datastore mode entity to persist/update data. This technique can let Firestore in Datastore mode be a drop-in replacement for your relational database with little to no code changes.

If you find this guide useful, kindly share it with your friends via email, social media so that they can also benefit.
If you need assistance with any of the following steps contact us "email"

Check out Datastore Entity documentation on readthedocs.

- SKS

Check out our other guides

  1. Common Google Cloud CLI Commands
  2. How To Create Your Own Online Portfolio
  3. How To Make Your Resume Stand Out
  4. How To Create A Simple Website

Comments

comments powered by Disqus