Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firebase Notes (NoSQL Database) #82

Open
s2t2 opened this issue May 30, 2021 · 1 comment
Open

Firebase Notes (NoSQL Database) #82

s2t2 opened this issue May 30, 2021 · 1 comment

Comments

@s2t2
Copy link
Member

s2t2 commented May 30, 2021

We already have some mongodb notes for basic NoSQL, but let's also add notes about firebase.

Setup

https://firebase.google.com/docs/firestore/quickstart

Usage

Something like this:

import os
from firebase_admin import credentials, initialize_app, firestore #, auth

CREDENTIALS_FILEPATH = os.path.join(os.path.dirname(__file__), "..", "google-credentials.json")

creds = credentials.Certificate(CREDENTIALS_FILEPATH)
app = initialize_app(creds) # or set FIREBASE_CONFIG variable and initialize without creds
db = firestore.client()

cities_ref = db.collection('cities')

#ADD 
data = {...}
cities_ref.add(data) # auto id?

# DOC REF 
cities_ref.document() # auto id
city_ref = cities_ref.document('LA') # specify id

# 3) SET / MERGE DATA
# city_ref.set({...})
# city_ref.set({...}, merge=True)

# 4) UPDATE DATA (JUST SOME FIELDS)
city_ref.update({"toplevel": 4, "favorites.color": "blue"})

# 6) ARRAY APPEND / REMOVE
city_ref.update({'regions': firestore.ArrayUnion(['greater_virginia'])})

city_ref.update({'regions': firestore.ArrayRemove(['east_coast'])})

See: Managing Data - Firebase Python Docs

Also, for higher-level data modeling decisions (whether to store data in a nested document or in a separate collection), see videos like Data Modeling in Firebase.

https://firebase.google.com/docs/firestore/manage-data/structure-data

@s2t2 s2t2 changed the title Firebase Notes Firebase Notes (NoSQL Database) May 30, 2021
@s2t2
Copy link
Member Author

s2t2 commented May 25, 2022

This is an implementation example, with collections called "calendars" and "events" :

import os

from dateutil.parser import parse as to_dt
from dateutil.tz import gettz as get_tz
from firebase_admin import credentials, initialize_app, firestore #, auth
from dotenv import load_dotenv

load_dotenv()

DEFAULT_FILEPATH = os.path.join(os.path.dirname(__file__), "..", "google-credentials.json")
CREDENTIALS_FILEPATH = os.getenv("GOOGLE_APPLICATION_CREDENTIALS", default=DEFAULT_FILEPATH)

class FirebaseService:
    def __init__(self):
        self.creds = credentials.Certificate(CREDENTIALS_FILEPATH)
        self.app = initialize_app(self.creds) # or set FIREBASE_CONFIG variable and initialize without creds
        self.db = firestore.client()

    def fetch_authorized_calendars(self, email_address):
        # see: https://firebase.google.com/docs/firestore/query-data/queries#query_operators
        # ... https://firebase.google.com/docs/firestore/query-data/queries#query_limitations
        # there is no logical OR query in firestore, so we have to fetch both and de-dup...
        authorizedQuery = self.calendars_ref.where("authorizedUsers", "array_contains", email_address)
        #adminQuery = self.calendars_ref.where("adminUsers", "array_contains", email_address)
        #calendar_docs = list(authorizedQuery.stream()) + list(adminQuery.stream())
        # but actually just add the admin into the authorized list for now
        calendar_docs = list(authorizedQuery.stream())

        # let's return the dictionaries, so these are serializable (and can be stored in the session)
        calendars = []
        for calendar_doc in calendar_docs:
            calendar = calendar_doc.to_dict()
            calendar["id"] = calendar_doc.id
            calendars.append(calendar)

        return calendars

    @property
    def calendars_ref(self):
        """Returns a (google.cloud.firestore_v1.collection.CollectionReference) """
        return self.db.collection("calendars")

    #def add_nested_events(self, events, calendar_id=None, calendar_ref=None):
    #    """
    #    Add event records to the given calendar document.
    #
    #    Pass a calendar ref if possible, otherwise pass a calendar id and a new ref will be created.
    #
    #    Params:
    #        calendar_ref (google.cloud.firestore_v1.document.DocumentReference) : the document reference object
    #
    #        calendar_id (str) : the document id
    #
    #        events (list of dict) : list of events to add, like [{
    #            "event_start": datetime.datetime object,
    #            "event_end": datetime.datetime object,
    #            "reservation": None
    #        }]
    #
    #    Note: passing datetime objects gives us a timestamp object in firebase!
    #
    #    Returns : (google.cloud.firestore_v1.types.write.WriteResult)
    #    """
    #    calendar_ref = calendar_ref or self.calendars_ref.document(calendar_id)
    #    return calendar_ref.update({"events": firestore.ArrayUnion(events)})

    @property
    def events_ref(self):
        """Returns a (google.cloud.firestore_v1.collection.CollectionReference) """
        return self.db.collection("events")

    def create_events(self, events):
        """
        Creates new event documents from event data.

        Params:
            events (list of dict) : list of events to add, like [{
                "calendar_id": (str), # referencing the calendar document id
                "event_start": datetime.datetime object,
                "event_end": datetime.datetime object,
                "reservation": None
            }]

        Note: passing datetime objects gives us a timestamp object in firebase!

        See: https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes
        """
        batch = self.db.batch()
        for event_data in events:
            event_ref = self.events_ref.document() # give each event its own unique id (so we can more easily look it up later!!)
            batch.set(event_ref, event_data)
        return batch.commit()

    def fetch_daily_events(self, calendar_id, date, timezone="US/Eastern"):
        """Params: date (str) like '2021-01-01'"""
        tz = get_tz(timezone)
        day_start = to_dt(f"{date} 00:00").astimezone(tz)
        day_end = to_dt(f"{date} 11:59").astimezone(tz)

        query_ref = self.events_ref
        query_ref = query_ref.where("calendar_id", "==", calendar_id)
        query_ref = query_ref.where("event_start", ">=", day_start)
        query_ref = query_ref.where("event_start", "<=", day_end)
        event_docs = list(query_ref.stream()) #> list of DocSnapshot
        events = []
        for event_doc in event_docs:
            event = event_doc.to_dict()
            event["id"] = event_doc.id
            event["event_start"] = event["event_start"].astimezone(tz) # otherwise this will show up as UTC in the browser.
            event["event_end"] = event["event_end"].astimezone(tz) # otherwise this will show up as UTC in the browser
            events.append(event)
        return events


if __name__ == "__main__":

    from pprint import pprint

    service = FirebaseService()

    print("CALENDARS...")
    calendars_ref = service.db.collection("calendars")
    calendars = [doc.to_dict() for doc in calendars_ref.stream()]
    pprint(calendars)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant