Package: pyfireconsole
IntroductionInspired by Rails console, PyFireConsole provides a seamless interface to Google's Firestore in Python, simplifying tasks such as connection, ORM, and data associations. It makes managing Firestore a breeze.FeaturesModel Definition and ORM: Define your Firestore data models within Python and use object-relational mapping (ORM) for easier data manipulation and querying.Data Associations: Effortlessly manage relationships between your Firestore data models.Interactive Console: Inspired by the Rails console, PyFireConsole provides a console for interactive data manipulation and querying, making it simple to perform tasks on your Firestore data.InstallationpipinstallpyfireconsoleGetting StartedConnect to FirestoreFirst of all, you need to initializeFirestoreConnectionwith your project id.# Initialize FirestoreConnection using your default credentials of gcloud. (use `gcloud auth application-default login` or set GOOGLE_APPLICATION_CREDENTIALS)FirestoreConnection().initialize(project_id="YOUR-PROJECT-ID")# Or you can specify service_account_key_pathFirestoreConnection().initialize(service_account_key_path="./service-account.json",project_id="YOUR-PROJECT-ID")Find a document by idLike Rails, you can define your model class by inheritingPyfireDocclass.frompyfireconsole.models.pyfire_modelimportPyfireDocclassUser(PyfireDoc):name:stremail:strrole:str="user"# Find a document by iduser=User.find("XXX")#=> User[users/XXX](id='XXX', name='John', email='john@example.com', role='user')# Nested document can be accessed by pathuser=User.find("companies/ZZZ/users/YYY")#=> User[companies/ZZZ/users/YYY](id='YYY', name='John', email='john@example.com', role='user')Where queryYou can usewheremethod to query documents.admin_users=User.where("role","==","admin")#=> PyfireCollection<User>[users]# admin_user is iterableforuserinadmin_users:print(user)#=> User[users/YYYYYYYYYY](id='YYYYYYYYYY', name='Mary', email='mary@example.com', role='admin')Sub collectionYou can define sub collection of a document by usingPyfireCollectionclass.frompyfireconsole.models.pyfire_modelimportPyfireCollection,DocumentRef,PyfireDoc# Tag is sub collection of BookclassTag(PyfireDoc):name:strclassBook(PyfireDoc):title:struser_id:strpublished_at:datetimetags:PyfireCollection[Tag]=PyfireCollection(Tag)# Specify sub collection typebook=Book.find("XXXX")fortaginbook.tags:print(tag)#=> Tag[books/XXXX/tags/YYYY](id='YYYY', name='Python')has_many, belongs_toYou can define data associations by usinghas_manyandbelongs_todecorators.Nowbook.userreturnsUserobject, anduser.booksreturnsPyfireCollection[Book]object.frompyfireconsole.models.associationimportbelongs_to,has_many,resolve_pyfire_model_namesfrompyfireconsole.models.pyfire_modelimportPyfireCollection,DocumentRef,PyfireDoc@has_many('Book',db_field="user_id",attr_name="my_books")classUser(PyfireDoc):name:stremail:strclassTag(PyfireDoc):name:str@belongs_to(User,"user_id")classBook(PyfireDoc):title:struser_id:strpublished_at:datetimetags:PyfireCollection[Tag]=PyfireCollection(Tag)# call this to resolve model names (this is because of python's circular import problem)resolve_pyfire_model_names(globals())user=User.find("YYYY")user.my_books=>PyfireCollection[Book][books]user.my_books.first.user=>User[users/XXXX](id='XXXX',name='John',email="john@example.com")as_jsonYou can convert PyfireDoc object to json serializable dict by usingas_jsonmethod.# dump all admin users, not including sub collectionUser.all().as_json()# dump all admin users, not including sub collectionUser.where("role","==","admin").as_json()# dump all admin users, including sub collectionUser.where("role","==","admin").as_json(recursive=True)# you can also dump custom attributesclassUser(PyfireDoc):name:stremail:strdefemail_domain(self):returnself.email.split("@")[1]User.where("role","==","admin").as_json(recursive=True,include=["email_domain"])Empty documentYou can instantiate empty document by usingallow_emptyoption. The sub collection of empty document can be accessed.# This is empty document which doesn't exist in firestore but sub collection exists.user=User.find("XXXXX",allow_empty=True)# You can't access empty document's attributes# user.name => AttributeError: 'User' object has no attribute 'name'# You can access sub collection of empty documentprint([n.titleforninuser.books])ExampleWe assume that you have a firestore database with the following structure:- users Collection
    - {user_id} Document
        - name: str
        - email: str
- publishers Collection
    - {publisher_id} Document
        - name: str
        - address: str
- books Collection
    - {book_id} Document
        - title: str
        - user_id: str
        - published_at: datetime
        - authors: list[str]
        - tags: Sub Collection
            - {tag_id} Document
                - name: str
        - publisher_ref: ReferencefromdatetimeimportdatetimefromtypingimportOptionalfrompyfireconsole.models.associationimportbelongs_tofrompyfireconsole.models.pyfire_modelimportPyfireCollection,DocumentRef,PyfireDocfrompyfireconsole.db.connectionimportFirestoreConnection# Define models# PyfireDoc is a subclass of Pydantic(2.x) BaseModel. You can use Pydantic's features.@has_many('Book',"user_id")classUser(PyfireDoc):name:stremail:strclassPublisher(PyfireDoc):name:straddress:Optional[str]classTag(PyfireDoc):name:str@belongs_to(User,"user_id")classBook(PyfireDoc):title:struser_id:strpublished_at:datetimeauthors:list[str]tags:PyfireCollection[Tag]=PyfireCollection(Tag)publisher_ref:DocumentRef[Publisher]# Initialize FirestoreConnection using your default credentials of gcloud. (use `gcloud auth application-default login` or set GOOGLE_APPLICATION_CREDENTIALS)FirestoreConnection().initialize(project_id="YOUR-PROJECT-ID")# Or you can specify service_account_key_path# FirestoreConnection().initialize(service_account_key_path="./service-account.json", project_id="YOUR-PROJECT-ID")print("==================== find ====================")book=Book.find("XlvQHeGi3cODbI4MQpI3")# => Bookprint(book.model_dump())# => dictprint(f"ID:{book.id}| Title:{book.title}| Authors:{book.authors}| Published At:{book.published_at.isoformat()}")print("==================== belongs_to ====================")print(book.user)# => Userprint(book.user.name)# => strprint("==================== reference ====================")print(book.publisher_ref)# => DocumentRefprint(book.publisher_ref.path)# => str (So far, we can't access publisher_ref.name directly for ref type)print("==================== where ====================")print(Book.where("title","==","test"))# => Book[] Make sure to create index in firestore for compound queriesprint("==================== where & order ====================")print(Book.where("title","==","test").order("published_at","ASCENDING"))# => Book[] Make sure to create index in firestore for compound queriesprint("==================== has_many ====================")print(book.tags)# => PyfireCollection[Tag]print(book.tags.first)# => TagInteractive ConsolePyFireConsole comes with an interactive console that allows developer to view and manipulate Firestore data in a live and easily.
This feature is inspired by the Rails console.First, you need to define your models.# app/models/models.pyfromdatetimeimportdatetimefromtypingimportOptionalfrompyfireconsole.models.associationimportbelongs_to,has_many,resolve_pyfire_model_namesfrompyfireconsole.models.pyfire_modelimportPyfireCollection,DocumentRef,PyfireDocfrompyfireconsole.db.connectionimportFirestoreConnectionfrompyfireconsoleimportPyFireConsole@has_many('Book',"user_id")classUser(PyfireDoc):name:stremail:strclassPublisher(PyfireDoc):name:straddress:Optional[str]classTag(PyfireDoc):name:str@belongs_to(User,"user_id")classBook(PyfireDoc):title:struser_id:strpublished_at:datetimeauthors:list[str]tags:PyfireCollection[Tag]=PyfireCollection(Tag)publisher_ref:DocumentRef[Publisher]Start interactive console by usingpyfireconsolecommand.pyfireconsole--model-dirapp/modelsThis command loads all python classes inapp/modelsdirectory and starts interactive console.--model-di: model directory path--project-id: project id (optional)--service-account-key-path: service account key path (optional)Invoke console from your codeYou can also callPyFireConsole().run()from your code.fromdatetimeimportdatetimefromtypingimportOptionalfrompyfireconsole.models.associationimportbelongs_to,has_many,resolve_pyfire_model_namesfrompyfireconsole.models.pyfire_modelimportPyfireCollection,DocumentRef,PyfireDocfrompyfireconsole.db.connectionimportFirestoreConnectionfrompyfireconsoleimportPyFireConsole@has_many('Book',"user_id")classUser(PyfireDoc):name:stremail:strclassPublisher(PyfireDoc):name:straddress:Optional[str]classTag(PyfireDoc):name:str@belongs_to(User,"user_id")classBook(PyfireDoc):title:struser_id:strpublished_at:datetimeauthors:list[str]tags:PyfireCollection[Tag]=PyfireCollection(Tag)publisher_ref:DocumentRef[Publisher]# Resolve PyfireModel names# Call this function when you define your models by using str class name. e.g. @has_many('Book', "user_id")resolve_pyfire_model_names(globals())FirestoreConnection().initialize(project_id="YOUR_PROJECT_ID")PyFireConsole().run()Through the interactive console, you can conveniently test and experiment with your Firestore data models.ContributingYour contributions to PyFireConsole are warmly welcomed! Feel free to submit a pull request directly if you have any improvements or features to suggest. For any questions or issues, please create an issue on Github. Thank you for your interest in improving PyFireConsole!LicensePyFireConsole is released under the MIT License.This means you are free to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software. This permission is granted provided that the above copyright notice and this permission notice are included in all copies or substantial portions of the software.