Rakib

Firebase Realtime Database using Python SDK

Firebase Realtime Database using Python SDK

Firebase Realtime Database is a NoSQL cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. Using Firebase Admin SDK, we can read and write Realtime Database data with full admin privileges, or limited privileges.

In my recent project, I had the opportunity to work with Firebase Realtime Database. So, I decided to write a basic guide to using Firebase Realtime Database using Python SDK.

Download source code from Github.

Add Firebase to the App

To use the Firebase Admin SDK, we’ll need a Firebase project, a service account to communicate with the Firebase service, and a configuration file with your service account’s credentials. Follow Add Firebase to your app to get the configuration file, which is a JSON file.

Install Firebase Python SDK

The Firebase Admin Python SDK enables server-side (backend) Python developers to integrate Firebase into their services and applications. To install Firebase Admin Python SDK, simply execute the following command in a terminal:

pip install firebase-admin

Initialize the SDK

Initialize the SDK with this code snippet:

import firebase_admin
from firebase_admin import credentials
from firebase_admin import db

# Fetch the service account key JSON file contents
cred = credentials.Certificate('firebase-adminsdk.json')
# Initialize the app with a service account, granting admin privileges
firebase_admin.initialize_app(cred, {
    'databaseURL': 'https://<app_name>.firebaseio.com/'
})

Structure the Database

In Firebase Realtime Database, data is structured as a JSON tree. Unlike a SQL database, there are no tables or records. When we add data to the JSON tree, it becomes a node in the existing JSON structure with an associated key.

Best practice for a data structure is to avoid nesting data and to keep the data structure as flat as possible. Follow Structure Your Database for a detailed guideline.

Save Data

Create a database reference and then pass it an object using set method:

ref = db.reference('/')
ref.set({
        'boxes': 
            {
                'box001': {
                    'color': 'red',
                    'width': 1,
                    'height': 3,
                    'length': 2
                },
                'box002': {
                    'color': 'green',
                    'width': 1,
                    'height': 2,
                    'length': 3
                },
                'box003': {
                    'color': 'yellow',
                    'width': 3,
                    'height': 2,
                    'length': 1
                }
            }
        })

Follow Saving Data for more information.

Update Data

To update a node of a database location without overwriting other child nodes, use the update method as shown below:

ref = db.reference('boxes')
box_ref = ref.child('box001')
box_ref.update({
    'color': 'blue'
})

This will update box001 color to blue.

The Firebase Realtime Database also supports multi-path updates. Using it, we can update color to both box001 and box002 at the same time:

ref = db.reference('boxes')
ref.update({
    'box001/color': 'red',
    'box002/color': 'blue'
})

In Python Admin SDK all write methods are blocking. That is, the write methods do not return until the writes are committed to the database.

Save Lists of Data

If we want to store a list of boxes, then we might want to use a unique key for each new box instead of box001, box002, box003. To solve this, the Firebase clients provide a push() function that generates a unique key for each new child

ref = db.reference('boxes')
ref.push({
    'color': 'purple',
    'width': 7,
    'height': 8,
    'length': 6
})

The unique key is based on a timestamp, so list items will automatically be ordered chronologically. Our database data now looks like this:

{
  "boxes": {
    "-L5_dtlqoO2K8rgwdxJ3": {
      "color": "purple",
      "width": 7,
      "height": 8,
      "length": 6
    }
  }
}

Get the Unique Key Generated by push()

Calling push() will return a reference to the new data path, which you can use to get the key or set data to it. The following code will result in the same data as the above example, but now we’ll have access to the unique key that was generated:

ref = db.reference('boxes')
# Generate a reference to a new location and add some data using push()
new_box_ref = ref.push({
    'color': 'purple',
    'width': 7,
    'height': 8,
    'length': 6
})
# Get the unique key generated by push()
box_id = new_box_ref.key

Retrieve Data

The Python Admin SDK currently only supports blocking reads. It cannot be used to add event listeners that receive realtime update notifications. In python, data stored in a Firebase Realtime Database is retrieved by invoking a blocking method on a database reference, which returns the data stored at the reference. Each method call is a onetime operation.

The get() method in Python returns a Python representation of the data directly:

ref = db.reference('boxes')
print(ref.get())

Query Data

Firebase Realtime Database supports some queries to selectively retrieve data based on various factors.

Data Ordering

We can order data in three ways: by child key, by key, or by value. A basic database query starts with one of these ordering functions, each of which is explained below.

Indexing on Values

If we want to use order query, we need to add the values/keys to the .indexOn rule.

{
  "rules": {
    "boxes": {
      ".indexOn": ["color", "width", "height", "length"]
    },
    "weights": {
      ".indexOn": [".value"]
    },
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

Read Index your data on the .indexOn rule for more information.

Order by a Specified Child Key

To get data of all boxes ordered by height, we can do the following:

ref = db.reference('boxes')
snapshot = ref.order_by_child('height').get()
for key, val in snapshot.items():
    print('{0} => {1}'.format(key, val))

Order by Key

We can also order nodes by their keys using the orderByKey() method:

ref = db.reference('boxes')
snapshot = ref.order_by_key().get()
for key, val in snapshot.items():
    print('{0} => {1}'.format(key, val))

Order by Value

We can order nodes by the value of their child keys using the orderByValue() method:

{
  "weights": {
    "person001" : 60,
    "person002" : 65,
    "person003" : 80,
    "person004" : 55,
    "person005" : 72
  }
}
ref = db.reference('weights')
snapshot = ref.order_by_value().get()
for key, val in snapshot.items():
    print('{0} => {1}'.format(key, val))

Limit Queries

The limitToFirst() and limitToLast() queries are used to set a maximum number of children to be retrieved:

ref = db.reference('boxes')
snapshot = ref.order_by_child('weight').limit_to_last(2).get()
for key in snapshot:
    print(key)
ref = db.reference('boxes')
snapshot = ref.order_by_child('height').limit_to_first(2).get()
for key in snapshot:
    print(key)
weights_ref = db.reference('weights')
snapshot = weights_ref.order_by_value().limit_to_last(3).get()
for key, val in snapshot.items():
    print('{0} => {1}'.format(key, val))

Range Queries

Using startAt(), endAt(), and equalTo() allows to choose arbitrary starting and ending points for our queries:

ref = db.reference('boxes')
snapshot = ref.order_by_child('color').start_at('g').get()
for key in snapshot:
    print(key)
ref = db.reference('boxes')
snapshot = ref.order_by_key().end_at('w').get()
for key in snapshot:
    print(key)
ref = db.reference('boxes')
snapshot = ref.order_by_key().start_at('g').end_at(u'n\uf8ff').get()
for key in snapshot:
    print(key)
ref = db.reference('boxes')
snapshot = ref.order_by_child('length').equal_to(3).get()
for key in snapshot:
    print(key)

Visit Data security to know about security rules.
Visit firebase_admin.db module to get information about the module containing functions and classes that facilitate interacting with the Firebase Realtime Database.