— notes, python, GCP, firebase — 3 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,
Adding/Updating data to firebase
Querying data
This is a simple Python Firebase Program to add data to realtime database, its taken from the sample Google Firebase documentation only.
1import json2import firebase_admin3from firebase_admin import credentials4from firebase_admin import db5
6# Fetch the service account key JSON file contents7cred = 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 privileges10firebase_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 Rules15ref = db.reference('python-testing')16# print(ref.get())17
18# Pretty Print19print(json.dumps(ref.get(), indent=4, sort_keys=True))20
21# Set Data22print('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 Data37print('2. Update Data')38hopper_ref = users_ref.child('gracehop')39hopper_ref.update({40 'nickname': 'Amazing Grace'41})42
43# Multi-path updates44print('3. Multi-path updates')45users_ref.update({46 'alanisawesome/nickname': 'Alan The Machine',47 'gracehop/nickname': 'Amazing Grace'48})49
50# Generating push keys51posts_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_ref61posts_ref.push().set({62 'author': 'alanisawesome',63 'title': 'The Turing Machine'64})65
66# This is equivalent to the calls to push().set(...) above67posts_ref.push({68 'author': 'gracehop',69 'title': 'Announcing COBOL, a New Programming Language'70})71
72# Delete an entire node73ref = db.reference('python-testing')74ref.update({'posts':None})
Console Output
1>py -m firebase-test1 2null31. Setting Data42. Update Data53. Multi-path updates6Key = -MleNEJkc_ONcyBlac0B
Below is the output when you run it.
Things to notice in the above program,
Service Account Specification, there are two ways
.set()
will replace the data in that node path, if data already exists. So, you need to be careful with that.
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})
Multi-path updates is the one i prefer, but you got to do a bit of planning for it.
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).
Deletes an entire node
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})
Below running the below program SET the environment variable set GOOGLE_APPLICATION_CREDENTIALS
1import json2import firebase_admin3from firebase_admin import credentials4from firebase_admin import db5
6firebase_admin.initialize_app(options={7 'databaseURL': "https://api-project-testing.firebaseio.com"8})9
10ref = db.reference('python-testing')11
12# Setup Data13ref = db.reference('python-testing')14dino_ref = ref.child('dinosaurs')15dino_ref.set({16 "lambeosaurus": {17 "height" : 2.1,18 "length" : 12.5,19 "weight": 500020 },21 "stegosaurus": {22 "height" : 4,23 "length" : 9,24 "weight" : 250025 }26})
First lets see order_by_child()
, here length is the child of node dinosaurs
1# .order_by_child2# .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
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" : 2211 }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 52The lambeosaurus dinosaur's score is 213The triceratops dinosaur's score is 224The bruhathkayosaurus dinosaur's score is 555The linhenykus dinosaur's score is 806The 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}
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# Output8None - No Data
Thank you for reading