Skip to content

Latest commit

 

History

History
536 lines (380 loc) · 17.6 KB

Documentation for Firebase.md

File metadata and controls

536 lines (380 loc) · 17.6 KB

Firebase

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.

Firebase Admin SDK

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.

Initializing the JSON key

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.

Cloud Firestore

Creating a Project

  1. Open the Firebase Console and create a new project
  2. In the Database section, click the Create Database button for Cloud Firestore.
  3. Select a starting mode for your Cloud Firestore Security Rules:
    1. Test mode: For Web, IOS, or Android SDK
    2. Locked mode: For C#, Go, Java, Node.js, PHP, Python, or Ruby server client library

Install Firebase-Admin SDK

Open Anaconda prompt (or use whatever IDE console you use) and enter the following:

pip install --upgrade firebase-admin

Initialize a Firebase server

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()

Adding Data

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.

Reading Data

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}

Classes

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())

Firebase Storage

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.

Editing the Rules

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.

File Upload

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.

Uploading File without urllib

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()   

Retrieving Metadata

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.

Delete a File

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.

Updating Metadata

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())

Download File

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
Downloading File without urllib

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.

Sample Code

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.

Firebase Storage using blobs

This is a barebones summary of Google's documentation for Firebase Storage and using Blobs.

Initializing Firebase for Firebase Storage

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.

Uploading Objects

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.

Listing Objects

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)

Downloading Objects

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))

Retrieving Metadata

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))

Deleting Objects

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))