Firebase is a noSQL cloud database that is highly robust and allows for rapid prototyping of apps or scripts. There are 2 important parts to firebase, Cloud Firestore, Firebase Storage.
First thing you have to do after creating a firebase project is to navigate to **project overview ** > **settings ** > service accounts > Firebase Admin SDK. Then generate a new private key. You require this private key to access the firebase project server, so keep it well and never have it lying around in a public repo.
You can direct the script to your private key using the following:
import firebase_admin
from firebase_admin import credentials
cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)
This gives the script access to the server so that you can access it.
- Open the Firebase Console and create a new project
- In the Database section, click the Create Database button for Cloud Firestore.
- Select a starting mode for your Cloud Firestore Security Rules:
- Test mode: For Web, IOS, or Android SDK
- Locked mode: For C#, Go, Java, Node.js, PHP, Python, or Ruby server client library
Open Anaconda prompt (or use whatever IDE console you use) and enter the following:
pip install --upgrade firebase-admin
To initialize a firebase server, locate the JSON key as previously mentioned and add the following snippet to your python script:
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
# Use a service account
cred = credentials.Certificate('path/to/serviceAccount.json')
firebase_admin.initialize_app(cred)
db = firestore.client()
Cloud Firestore stores data in Documents, which are stored in Collections.
Create a collection and document with the following coding:
doc_ref = db.collection(u'users').document(u'degojix')
Set the document data with the following:
doc_ref.set({
u'first': u'dego',
u'last': u'jix',
u'born': 1897
})
This creates a collection called users with one document, degojix. The document degojix contains the information for the first and last names of degojix and the date of birth.
We can now add another document in the users collection called fasermaler. This time, we'll add another key-pair for fasermaler for a middle name.
doc_ref = db.collection(u'users').document(u'fasermaler')
doc_ref.set({
u'first': u'fa',
u'middle': u'serma',
u'last': u'ler',
u'born': 1904
})
Notice that the key-pair for middle does not appear in degojix. You can think of collections as merely a folder, the documents themselves can store differing types of data with no concern for each other.
Now when you appended the code and ran the whole script a second time, you might have discovered that firebase provided the following error:
ValueError: The default Firebase app already exists. This means you called initialize_app() more than once without providing an app name as the second argument. In most cases you only need to call initialize_app() once. But if you do want to initialize multiple apps, pass a second argument to initialize_app() to give each app a unique name.
This is because the initial firebase app (cred) is still in effect. You may open a new script and use the following code instead:
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
firebase_admin.get_app()
db = firestore.client()
The .get_app() method will instruct the script to get the currently open firebase app (cred).
ALTERNATIVELY
Append the following line to the end of your code:
firebase_admin.delete_app(firebase_admin.get_app())
This will get the app name and parse it into the delete_app() method to kill the app.
If for some reason you initialized an app without adding this line at the end of your code before running, you have to first run a separate instance of firebase that only consists of this line. Then proceed to append the line to the end of your existing code and run again.
If you merely appended your code with a live firebase app already running, the code will fail before it reaches the .delete_app() method.
You can use the .get() method to retrieve the collection and read the data:
users_ref = db.collection(u'users')
docs = users_ref.get()
for doc in docs:
print(u'{} => {}'.format(doc.id, doc.to_dict()))
You might get something like the following output:
degojix => {'last': 'jix', 'first': 'dego', 'born': 1897}
fasermaler => {'last': 'ler', 'middle': 'serma', 'first': 'fa', 'born': 1904}
You can even use custom classes with firebase documents! Here's an example:
class City(object):
def __init__(self, name, state, country, capital=False, population=0):
self.name = name
self.state = state
self.country = country
self.capital = capital
self.population = population
@staticmethod
def from_dict(source):
# ...
def to_dict(self):
# ...
def __repr__(self):
return u'City(name={}, country={}, population={}, capital={})'.format(
self.name, self.country, self.population, self.capital)
city = City(name=u'Los Angeles', state=u'CA', country=u'USA')
db.collection(u'cities').document(u'LA').set(city.to_dict())
There is a surprising lack of documentation for Firebase Storage, so not all of the following may be best practice, but most importantly it works.
Special thanks to user PhantomInsights for their documentation.
Unlike cloud firestore, which stores mainly strings of information, firebase storage allows for text files and image files to be edited (albeit in a less structured manner). How this is done that firebase storage bundles the file to be uploaded within a blob, which stores the file as well as metadata of the file. The blob contains the metadata in the form of a JSON, which can then be converted into dictionary to be easily manipulated by python.
Before we go on about how to upload files. First, go to your firebase project > storage on the left-hand bar. Then go over to rules. You should see the default rules are something like the following:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write; if request.auth != null;
}
}
}
The if request.auth != null; line basically requires authentication to read and write to the bucket. For the time being, just remove this line. This allows anyone with access to the URL to read/write. Which is good for now since we are developing.
If for some reason, you would like to allow people to view files within the bucket but not write to it then remove write.
The following code can be used for file upload:
def upload_file():
my_file = open("<FILE>", "rb")
my_bytes = my_file.read()
my_url = "https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>"
my_headers = {"Content-Type": "<FILE TYPE"}
my_request = urllib.request.Request(my_url, data=my_bytes, headers=my_headers, method="POST")
try:
loader = urllib.request.urlopen(my_request)
except urllib.error.URLError as e:
message = json.loads(e.read())
print(message["error"]["message"])
image_available = 0
else:
print(loader.read())
The above code will upload a to within your bucket in project . The project ID can be obtained in your project information page within firebase console. Alternatively, you can get the address of the file and folder directly from the page if you have a file manually uploaded before.
After you uploaded the file, you might notice some output in the console (or if you went to the link within my_url), it would most likely appear like this:
{
"name": "<FILE>",
"bucket": "<YOUR-PROJECT-ID>.appspot.com",
"generation": "1537816796033378",
"metageneration": "1",
"contentType": "text/plain",
"timeCreated": "2018-09-24T19:19:56.032Z",
"updated": "2018-09-24T19:19:56.032Z",
"storageClass": "STANDARD",
"size": "10450",
"md5Hash": "7aIjAPS+Sd0DaF5SmGTUYw==",
"contentEncoding": "identity",
"crc32c": "DObTDw==",
"etag": "COLi7f6t1N0CEAE=",
"downloadTokens": "c766b816-7429-4163-9e2d-1193e6f5ac78"
}
You might notice that this is basically a JSON. You might also notice that the URL does not point to the media itself but points to the JSON. This is the metadata for the file.
Updates to the request library has made it possible to upload a file without using URLlib. A sample code is as follows:
def upload_file():
file_url = "https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>"
data = <FILE>
files = {'file':'1.data'}
with open('1.pkl', 'wb') as outfile:
outfile.write(data)
files = {'1.pkl', open('1.pkl', 'wb')}
my_request = requests.post(url=file_url, files=files)
upload_file()
To retrieve metadata, use the following code:
def retrieve_metadata(num):
try:
loader = urllib.request.urlopen("https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>")
except urllib.error.URLError as e:
message = json.loads(e.read())
print(message["error"]["message"])
else:
print(loader.read())
You'll notice that we call the JSON library to read the output of the metadata. Also, notice that we accessed the upload URL but we have not go the file, instead we have only obtained the metadata for the file.
We can also parse the loader data through the .loads() method from the JSON library to get the function to return a python dictionary:
def retrieve_metadata():
try:
loader = urllib.request.urlopen("https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>")
except urllib.error.URLError as e:
message = json.loads(e.read())
print(message["error"]["message"])
else:
json_data = loader.read()
load_d = json.loads(json_data)
return load_d
So now if the output from retrieve_metadata is passed to a variable, you can access the values of the key-pairs easily.
my_python_dict = retrieve_metadata()
print(my_python_dict['name'])
The console should then print out the file name. This makes it very easy to index the names of files into a list for easy reference and access.
The follow function deletes a file:
def delete_file():
my_url = "https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>"
my_request = urllib.request.Request(my_url, method="DELETE")
try:
loader = urllib.request.urlopen(my_request)
except urllib.error.URLError as e:
message = json.loads(e.read())
print(message["error"]["message"])
else:
print(loader.read())
This uses the delete method on the url. Removing the file from the bucket.
You can use the dump method to update the metadata of a file. The following function is how:
def update_metadata():
my_url = "https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>"
my_headers = {"Content-Type": "application/json"}
my_data = {"contentType": "application/binary"}
json_data = json.dumps(my_data).encode()
my_request = urllib.request.Request(my_url, data=json_data, headers=my_headers, method="PATCH")
try:
loader = urllib.request.urlopen(my_request)
except urllib.error.URLError as e:
message = json.loads(e.read())
print(message["error"]["message"])
else:
print(loader.read())
Finally it's important to be able to download a file. The following code is for downloading image files.
def download_file(num):
my_url = "https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>"
try:
r = requests.get(my_url)
with open(str(str(num) + ".jpg"), 'wb') as f:
f.write(r.content)
f.close()
except urllib.error.URLError as e:
message = json.loads(e.read())
print(message["error"]["message"])
else:
pass
Similarly, it is possible to download a file without URLlib, the sample code can be found below:
def download_file(num):
my_url = "https://firebasestorage.googleapis.com/v0/b/<PROJECT ID>appspot.com/o/<FOLDER>%2F<FILE>?alt=media"
r = requests.get(my_url)
with open(str(str(num) + ".jpg"), 'wb') as f:
# In this case we are writing a jpg file
f.write(r.content)
f.close()
download_file(1)
It is important to take note of the ?alt=media
as it is what actually allows you to access the file.
Some sample code for upload and retrieval of images has been included in this directory. They were pulled from an existing project without much modification so use it at your own discretion.
This is a barebones summary of Google's documentation for Firebase Storage and using Blobs.
To use Firebase storage, first you must initialize your firebase app with an additional parameter.
import firebase_admin
from firebase_admin import credentials
from firebase_admin import storage
cred = credentials.Certificate('path/to/serviceAccountKey.json')
firebase_admin.initialize_app(cred, {
'storageBucket': '<BUCKET_NAME>.appspot.com'
})
bucket = storage.bucket()
The bucket used in this case would be the default bucket of your Firebase.
To upload files to Firebase storage, you can upload it in a blob object as such
def upload_blob(bucket_name, source_file_name, destination_blob_name):
"""Uploads a file to the bucket."""
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucket_name)
blob = bucket.blob(destination_blob_name)
blob.upload_from_filename(source_file_name)
print('File {} uploaded to {}.'.format(
source_file_name,
destination_blob_name))
This would store the target file in a folder with destination_blob_name
in the target storage bucket on Firebase.
To list all objects stored in the storage bucket.
def list_blobs(bucket_name):
"""Lists all the blobs in the bucket."""
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucket_name)
blobs = bucket.list_blobs()
for blob in blobs:
print(blob.name)
To download a specific blob from the storage bucket
def download_blob(bucket_name, source_blob_name, destination_file_name):
"""Downloads a blob from the bucket."""
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucket_name)
blob = bucket.blob(source_blob_name)
blob.download_to_filename(destination_file_name)
print('Blob {} downloaded to {}.'.format(
source_blob_name,
destination_file_name))
To retrieve the metadata of a specific blob.
def blob_metadata(bucket_name, blob_name):
"""Prints out a blob's metadata."""
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucket_name)
blob = bucket.get_blob(blob_name)
print('Blob: {}'.format(blob.name))
print('Bucket: {}'.format(blob.bucket.name))
print('Storage class: {}'.format(blob.storage_class))
print('ID: {}'.format(blob.id))
print('Size: {} bytes'.format(blob.size))
print('Updated: {}'.format(blob.updated))
print('Generation: {}'.format(blob.generation))
print('Metageneration: {}'.format(blob.metageneration))
print('Etag: {}'.format(blob.etag))
print('Owner: {}'.format(blob.owner))
print('Component count: {}'.format(blob.component_count))
print('Crc32c: {}'.format(blob.crc32c))
print('md5_hash: {}'.format(blob.md5_hash))
print('Cache-control: {}'.format(blob.cache_control))
print('Content-type: {}'.format(blob.content_type))
print('Content-disposition: {}'.format(blob.content_disposition))
print('Content-encoding: {}'.format(blob.content_encoding))
print('Content-language: {}'.format(blob.content_language))
print('Metadata: {}'.format(blob.metadata))
print("Temporary hold: ",
'enabled' if blob.temporary_hold else 'disabled')
print("Event based hold: ",
'enabled' if blob.event_based_hold else 'disabled')
if blob.retention_expiration_time:
print("retentionExpirationTime: {}"
.format(blob.retention_expiration_time))
To delete a specific blob on Firebase Storage
def delete_blob(bucket_name, blob_name):
"""Deletes a blob from the bucket."""
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucket_name)
blob = bucket.blob(blob_name)
blob.delete()
print('Blob {} deleted.'.format(blob_name))