Skip to content
bobby_dreamer

Hello Firebase from Python

notes, python, GCP, firebase3 min read

This is the first time, i am testing Firebase Admin SDK using Python. I have written programs using NodeJS. Since, i am into learning Python recently wanted to try Firebase and Python. What really interested me is

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

This seriously reduces my code a lot. Earlier i used to write a ton of code to make things synchronous, yeah, NodeJS/Javascript is not for that but at that time, it was the only option i had for things i was working on. I am very much happy reading this.

Lets divide this post into two parts,

  1. Adding/Updating data to firebase

  2. Querying data

Adding/Updating data to firebase

This is a simple Python Firebase Program to add data to realtime database, its taken from the sample Google Firebase documentation only.

1import json
2import firebase_admin
3from firebase_admin import credentials
4from firebase_admin import db
5
6# Fetch the service account key JSON file contents
7cred = credentials.Certificate("D:/BigData/13. Firebase/FB-Authentication3/functions/secrets/api-project-testing.json")
8
9# Initialize the app with a service account, granting admin privileges
10firebase_admin.initialize_app(cred, {
11 'databaseURL': "https://api-project-testing.firebaseio.com"
12})
13
14# As an admin, the app has access to read and write all data, regradless of Security Rules
15ref = db.reference('python-testing')
16# print(ref.get())
17
18# Pretty Print
19print(json.dumps(ref.get(), indent=4, sort_keys=True))
20
21# Set Data
22print('1. Setting Data')
23ref = db.reference('python-testing')
24users_ref = ref.child('users')
25users_ref.set({
26 'alanisawesome': {
27 'date_of_birth': 'June 23, 1912',
28 'full_name': 'Alan Turing'
29 },
30 'gracehop': {
31 'date_of_birth': 'December 9, 1906',
32 'full_name': 'Grace Hopper'
33 }
34})
35
36# Update Data
37print('2. Update Data')
38hopper_ref = users_ref.child('gracehop')
39hopper_ref.update({
40 'nickname': 'Amazing Grace'
41})
42
43# Multi-path updates
44print('3. Multi-path updates')
45users_ref.update({
46 'alanisawesome/nickname': 'Alan The Machine',
47 'gracehop/nickname': 'Amazing Grace'
48})
49
50# Generating push keys
51posts_ref = ref.child('posts')
52
53new_post_ref = posts_ref.push()
54print('Key = {}'.format(new_post_ref.key))
55new_post_ref.set({
56 'author': 'gracehop',
57 'title': 'Announcing COBOL, a New Programming Language'
58})
59
60# We can also chain above two calls together ie., posts_ref + new_post_ref
61posts_ref.push().set({
62 'author': 'alanisawesome',
63 'title': 'The Turing Machine'
64})
65
66# This is equivalent to the calls to push().set(...) above
67posts_ref.push({
68 'author': 'gracehop',
69 'title': 'Announcing COBOL, a New Programming Language'
70})
71
72# Delete an entire node
73ref = db.reference('python-testing')
74ref.update({'posts':None})

Console Output

1>py -m firebase-test1
2null
31. Setting Data
42. Update Data
53. Multi-path updates
6Key = -MleNEJkc_ONcyBlac0B

Below is the output when you run it.

Snap taken while firebase is getting updated

Things to notice in the above program,

  1. Service Account Specification, there are two ways

    • First method is as mentioned in the program pointing to the secret json file
    • Second is updating the environment variable GOOGLE_APPLICATION_CREDENTIALS ( more details on this after the below program )
  2. .set() will replace the data in that node path, if data already exists. So, you need to be careful with that.

  3. When adding data, either you can add it individually like below or combine them as in the program,

1users_ref.child('alanisawesome').set({
2 'date_of_birth': 'June 23, 1912',
3 'full_name': 'Alan Turing'
4})
5users_ref.child('gracehop').set({
6 'date_of_birth': 'December 9, 1906',
7 'full_name': 'Grace Hopper'
8})
  1. Multi-path updates is the one i prefer, but you got to do a bit of planning for it.

  2. Push keys new_post_ref = posts_ref.push(). They generate a unique key which contains an encoded timestamp value. So when you are generating it and add to firebase database, they are sort of chronologically sorted as these push-keys contain current time. The keys are also designed to be unguessable (they contain 72 random bits of entropy).

  3. Deletes an entire node

Application Default Credentials (ADC)

Google Cloud Client Libraries use a library called Application Default Credentials (ADC) to automatically find your service account credentials. ADC looks for service account credentials in the following order:

If the environment variable GOOGLE_APPLICATION_CREDENTIALS is set, ADC uses the service account key or configuration file that the variable points to.

If the environment variable GOOGLE_APPLICATION_CREDENTIALS isn't set, ADC uses the service account that is attached to the resource that is running your code.

This service account might be a default service account provided by Compute Engine, Google Kubernetes Engine, App Engine, Cloud Run, or Cloud Functions. It might also be a user-managed service account that you created.

Using the above technique, ADC can automatically find your credentials. Its recommended using ADC because it requires less code and your code is portable in different environments.

Basically run the below command to set the environment variable

1set GOOGLE_APPLICATION_CREDENTIALS=D:\BigData\13. Firebase\FB-Authentication3\functions\secrets\api-project-testing.json

Any GCP Client will look for GOOGLE_APPLICATION_CREDENTIALS, if its set it will use it and no need any code related to pointing to the secret json file. See below firebase admin initialization there is no mention of cred which is mentioned in above program. Also take at look below querying example it uses ADC.

1firebase_admin.initialize_app(options={
2 'databaseURL': "https://api-project-testing.firebaseio.com"
3})

Querying data

Below running the below program SET the environment variable set GOOGLE_APPLICATION_CREDENTIALS

1import json
2import firebase_admin
3from firebase_admin import credentials
4from firebase_admin import db
5
6firebase_admin.initialize_app(options={
7 'databaseURL': "https://api-project-testing.firebaseio.com"
8})
9
10ref = db.reference('python-testing')
11
12# Setup Data
13ref = db.reference('python-testing')
14dino_ref = ref.child('dinosaurs')
15dino_ref.set({
16 "lambeosaurus": {
17 "height" : 2.1,
18 "length" : 12.5,
19 "weight": 5000
20 },
21 "stegosaurus": {
22 "height" : 4,
23 "length" : 9,
24 "weight" : 2500
25 }
26})

Sorting the data

First lets see order_by_child(), here length is the child of node dinosaurs

1# .order_by_child
2# .indexOn setup for this to work
3ref = db.reference('python-testing/dinosaurs')
4snapshot = ref.order_by_child('length').get()
5for key, val in snapshot.items():
6 print('{0} stats are {1}'.format(key, val))

If you don't add .indexOn to rules, you will get below error

1firebase_admin.exceptions.InvalidArgumentError: Index not defined, add ".indexOn": "length", for path "/python-testing/dinosaurs", to the rules

Make the changes and publish

Firebase Rules

Now if you run, you will get result like below,

1stegosaurus stats are {'height': 4, 'length': 9, 'weight': 2500}
2lambeosaurus stats are {'height': 2.1, 'length': 12.5, 'weight': 5000}

Next, we will see order_by_value(), setting up data.

Here we are trying to sort by value of the scores. There is nothing nested like sub-nodes, so try to keep things this simple.

1ref = db.reference('python-testing')
2lb_ref = ref.child('leader-board')
3lb_ref.set({
4 "scores": {
5 "bruhathkayosaurus" : 55,
6 "lambeosaurus" : 21,
7 "linhenykus" : 80,
8 "pterodactyl" : 93,
9 "stegosaurus" : 5,
10 "triceratops" : 22
11 }
12})
13
14lb_ref = db.reference('python-testing').child('leader-board/scores')
15snapshot = lb_ref.order_by_value().get()
16for key, val in snapshot.items():
17 print('The {0} dinosaur\'s score is {1}'.format(key, val))

Before running this program, you should update the updates the rules to index the score.

1{
2 "rules": {
3 ".read": "auth.uid != null",
4 ".write": "auth.uid != null",
5 "python-testing":{
6 "dinosaurs": {
7 ".indexOn": ["height", "length"]
8 },
9 "leader-board":{
10 "scores": {
11 ".indexOn": ".value"
12 }
13 }
14 }
15 }
16}

After updating the index, if you execute, you will get output like below,

1The stegosaurus dinosaur's score is 5
2The lambeosaurus dinosaur's score is 21
3The triceratops dinosaur's score is 22
4The bruhathkayosaurus dinosaur's score is 55
5The linhenykus dinosaur's score is 80
6The pterodactyl dinosaur's score is 93

Next, we will see order_by_key() and there is no need to define index explicitly as node's key is indexed automatically

1ref = db.reference('python-testing').child('dinosaurs')
2snapshot = ref.order_by_key().get()
3for key, val in snapshot.items():
4 print('{0} : {1}'.format(key, val))

Output is

1lambeosaurus : {'height': 2.1, 'length': 12.5, 'weight': 5000}
2stegosaurus : {'height': 4, 'length': 9, 'weight': 2500}

Order is always ascending, there is no function to get it descending, you have to do that in client side.

1ref = db.reference('python-testing').child('dinosaurs')
2snapshot = ref.order_by_key().get()
3#for key, val in snapshot.items():
4for key, val in reversed(snapshot.items()):
5# for key, val in sorted(snapshot.items(), reverse=True):
6 print('{0} : {1}'.format(key, val))

Both the above reverse techniques work. Now the output is,

1stegosaurus : {'height': 4, 'length': 9, 'weight': 2500}
2lambeosaurus : {'height': 2.1, 'length': 12.5, 'weight': 5000}

Checking if data exists

This should have been the first in querying section, but here it is

1ref = db.reference('python-testing').child('anime')
2if ref.get() is None:
3 print('None - No Data')
4else:
5 print(ref.get())
6
7# Output
8None - No Data

Thank you for reading