diff --git a/README.md b/README.md index cf93eb1..38c955b 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,17 @@ by front end without aggregation. ```python -def CityBase(DomainModel): - _collection_name = "cities" - -class City(CityBase): - _schema_cls = CitySchema +from flask_boiler.domain_model import DomainModel +from flask_boiler import attrs + +class City(DomainModel): + + city_name = attrs.bproperty() + country = attrs.bproperty() + capital = attrs.bproperty() + + class Meta: + collection_name = "City" City.new( doc_id='SF', @@ -144,46 +150,6 @@ p.save() See ```examples/relationship_example.py``` - -### Context Management -In `__init__` of your project source root: -```python -import os - -from flask_boiler import context -from flask_boiler import config - -Config = config.Config - -testing_config = Config(app_name="your_app_name", - debug=True, - testing=True, - certificate_path=os.path.curdir + "/../your_project/config_jsons/your_certificate.json") - -CTX = context.Context -CTX.read(testing_config) -``` - -Note that initializing `Config` with `certificate_path` is unstable and -may be changed later. - -In your project code, - -```python -from flask_boiler import context - -CTX = context.Context - -# Retrieves firestore database instance -CTX.db - -# Retrieves firebase app instance -CTX.firebase_app - -``` - - - ### Automatically Generated Swagger Docs You can enable auto-generated swagger docs. See: `examples/view_example.py` diff --git a/context_management.md b/context_management.md new file mode 100644 index 0000000..54a9810 --- /dev/null +++ b/context_management.md @@ -0,0 +1,60 @@ +# Context Management + + +## Auto Configuration with boiler.yaml + +Provide authentication credentials to flask-boiler by moving the json certificate file +to your project directory and specify the path in ```boiler.yaml``` +in your current working directory. + +```yaml +app_name: "" +debug: True +testing: True +certificate_filename: "" +``` + +In ```__init__``` of your project source root: +```python +from flask_boiler.context import Context as CTX + +CTX.load() +``` + +## Manual Configuration +In `__init__` of your project source root: +```python +import os + +from flask_boiler import context +from flask_boiler import config + +Config = config.Config + +testing_config = Config(app_name="your_app_name", + debug=True, + testing=True, + certificate_path=os.path.curdir + "/../your_project/config_jsons/your_certificate.json") + +CTX = context.Context +CTX.read(testing_config) +``` + +Note that initializing `Config` with `certificate_path` is unstable and +may be changed later. + +In your project code, + +```python +from flask_boiler import context + +CTX = context.Context + +# Retrieves firestore database instance +CTX.db + +# Retrieves firebase app instance +CTX.firebase_app + +``` + diff --git a/docs/apidoc/flask_boiler.rst b/docs/apidoc/flask_boiler.rst.backup similarity index 100% rename from docs/apidoc/flask_boiler.rst rename to docs/apidoc/flask_boiler.rst.backup diff --git a/docs/apidoc/modules.rst b/docs/apidoc/modules.rst index c7b8907..ac17d00 100644 --- a/docs/apidoc/modules.rst +++ b/docs/apidoc/modules.rst @@ -54,3 +54,19 @@ Query Delta View Mediator :inherited-members: :undoc-members: :show-inheritance: + +Context +---------------------------- + +.. autoclass:: flask_boiler.context.Context + :members: + :undoc-members: + :show-inheritance: + +Auth +------------------------- + +.. automodule:: flask_boiler.auth + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/context-management.rst b/docs/context-management.rst index e2aacc2..412c3f0 100644 --- a/docs/context-management.rst +++ b/docs/context-management.rst @@ -1,40 +1 @@ -.. _context-management: - -Context Management -================== - -In ```__init__``` of your project source root: - -.. code-block:: python - - import os - - from flask_boiler import context - from flask_boiler import config - - Config = config.Config - - testing_config = Config(app_name="your_app_name", - debug=True, - testing=True, - certificate_path=os.path.curdir + "/../your_project/config_jsons/your_certificate.json") - - CTX = context.Context - CTX.read(testing_config) - -Note that initializing ```Config``` with ```certificate_path``` is unstable and -may be changed later. - -In your project code, - -.. code-block:: python - - from flask_boiler import context - - CTX = context.Context - - # Retrieves firestore database instance - CTX.db - - # Retrieves firebase app instance - CTX.firebase_app +.. mdinclude:: ../context_management.md diff --git a/flask_boiler/collection_mixin.py b/flask_boiler/collection_mixin.py index c8b9a71..4b65a51 100644 --- a/flask_boiler/collection_mixin.py +++ b/flask_boiler/collection_mixin.py @@ -14,9 +14,8 @@ def _doc_ref_from_id(cls, doc_id): def collection_name(self): """ Returns the root collection name of the class of objects. If cls._collection_name is not specified, then the collection - name will be inferred from the class name. - - :return: + name will be inferred from the class name. Note that different + types of objects may share one collection. """ # if type(self) == FirestoreObject: # raise ValueError("collection_name is read from class name, " @@ -32,8 +31,6 @@ def _get_collection_name(cls): @property def collection(self): """ Returns the firestore collection of the current object - - :return: """ return self._get_collection() @@ -46,6 +43,5 @@ def ref_from_id(cls, doc_id): """ Returns a Document Reference from doc_id supplied. :param doc_id: Document ID - :return: """ return cls._get_collection().document(document_id=doc_id) diff --git a/flask_boiler/context.py b/flask_boiler/context.py index cb1c9de..cf29cc1 100644 --- a/flask_boiler/context.py +++ b/flask_boiler/context.py @@ -22,6 +22,13 @@ class Context: + """ Context Singleton for Firestore, Firebase and Celery app. + + Example: + + + """ + firebase_app: firebase_admin.App = None db: firestore.Client = None config: Config = None diff --git a/flask_boiler/firestore_object.py b/flask_boiler/firestore_object.py index cd58df8..2c1feef 100644 --- a/flask_boiler/firestore_object.py +++ b/flask_boiler/firestore_object.py @@ -31,9 +31,8 @@ def doc_ref(self) -> DocumentReference: raise NotImplementedError @property - def doc_ref_str(self): - """ - Used for serializing the object + def doc_ref_str(self) -> str: + """ Serializes to doc_ref field. """ return self.doc_ref.path @@ -43,8 +42,7 @@ def get(cls, *, doc_ref=None, transaction=None, **kwargs): :param doc_ref: :param transaction: - :param kwargs: - :return: + :param kwargs: Keyword arguments to be forwarded to from_dict """ if transaction is None: snapshot = doc_ref.get() @@ -61,7 +59,6 @@ def from_snapshot(cls, snapshot=None): """ Deserializes an object from a Document Snapshot. :param snapshot: Firestore Snapshot - :return: """ obj = snapshot_to_obj(snapshot=snapshot, super_cls=cls) return obj @@ -73,7 +70,6 @@ def save(self, transaction: Transaction = None, doc_ref=None, save_rel=True): :param doc_ref: override save with this doc_ref :param save_rel: If true, objects nested in this object will be saved to the Firestore. - :return: """ if doc_ref is None: doc_ref = self.doc_ref @@ -86,11 +82,10 @@ def save(self, transaction: Transaction = None, doc_ref=None, save_rel=True): transaction.set(reference=doc_ref, document_data=d) - def delete(self, transaction: Transaction = None): + def delete(self, transaction: Transaction = None) -> None: """ Deletes and object from Firestore. - :param transaction: - :return: + :param transaction: Firestore Transaction """ if transaction is None: self.doc_ref.delete() @@ -182,15 +177,20 @@ def nest_relationship(val: RelationshipReference): val, to_get=to_get, transaction=transaction) @classmethod - def from_dict(cls, d, to_get=True, must_get=False, transaction=None, **kwargs): + def from_dict( + cls, + d, + to_get=True, + must_get=False, + transaction=None, + **kwargs): """ Deserializes an object from a dictionary. :param d: a dictionary representation of an object generated by `to_dict` method. :param transaction: Firestore transaction for retrieving related documents, and for saving this object. - :param kwargs: - :return: + :param kwargs: Keyword arguments to be forwarded to new """ super_cls, obj_cls = cls, cls diff --git a/flask_boiler/primary_object.py b/flask_boiler/primary_object.py index 0f76df6..1e71a3d 100644 --- a/flask_boiler/primary_object.py +++ b/flask_boiler/primary_object.py @@ -27,25 +27,16 @@ class PrimaryObject(FirestoreObject, QueryMixin, CollectionMixin, the document will be stored in and accessed from self.collection.document(doc_id) - Attributes - ---------- - _collection_name : str - the name of the collection for the object. Note that different - types of objects may share one collection. - """ - # Abstract property: MUST OVERRIDE - # TODO: add abstract property decorator _collection_name = None @classmethod def get_schema_cls(cls): - """ Returns schema_cls or the union of all schemas - of subclasses. Should only be used on the root - DomainModel. Does not cache the result. + """ Returns schema_cls or the union of all schemas of subclasses. + Should only be used on the root DomainModel. Does not + cache the result. - :return: """ d = dict() if super().get_schema_cls() is None: @@ -69,17 +60,13 @@ def __init__(self, doc_id=None, doc_ref=None, **kwargs): @property def doc_id(self): - """ - - :return: Document ID + """ Returns Document ID """ return self.doc_ref.id @property def doc_ref(self): - """ - - :return: Document Reference + """ Returns Document Reference """ if self._doc_ref is None: self._doc_ref = self.collection.document(random_id()) @@ -89,10 +76,20 @@ def doc_ref(self): @classmethod def new(cls, doc_id=None, doc_ref=None, **kwargs): - """ - Creates an instance of object and assign a firestore - reference with random id to the instance. - :return: + """ Creates an instance of object and assign a firestore reference + with random id to the instance. This is similar to the use + of "new" in Java. It is recommended that you use "new" to + initialize an object, rather than the native initializer. + Values are initialized based on the order that they are + declared in the schema. + + :param: doc_id: Document ID + :param: doc_ref: Document Reference + :param allow_default: if set to False, an error will be + raised if value is not provided for a field. + :param kwargs: keyword arguments to pass to the class + initializer. + :return: the instance created """ if doc_ref is None: if doc_id is None: @@ -110,7 +107,6 @@ def get(cls, *, doc_ref_str=None, doc_ref=None, doc_id=None, :param doc_ref: DocumentReference :param doc_id: gets the instance from self.collection.document(doc_id) :param transaction: firestore transaction - :return: """ if doc_ref_str is not None: diff --git a/flask_boiler/query_mixin.py b/flask_boiler/query_mixin.py index 955f917..04ffc68 100644 --- a/flask_boiler/query_mixin.py +++ b/flask_boiler/query_mixin.py @@ -4,7 +4,7 @@ from flask_boiler.utils import snapshot_to_obj -def is_fb_snapshot(snapshot: DocumentSnapshot): +def is_fb_snapshot(snapshot: DocumentSnapshot) -> bool: """ Returns true if a snapshot is generated by flask-boiler (checks if obj_type exists) @@ -25,7 +25,6 @@ def convert_query_ref(func): of objects. :param super_cls: - :return: """ def call(cls, *args, **kwargs): query_ref = func(cls, *args, **kwargs) @@ -40,11 +39,11 @@ def call(cls, *args, **kwargs): class QueryMixin: @classmethod def all(cls): - """ Gets object that is a subclass of the current cls in the - collection. + """ Gets object that is a subclass of the current cls in + the collection. - :return: """ + q = cls.get_query() docs = q.stream() for doc in docs: @@ -132,11 +131,8 @@ def _where_query(cls, *args, cur_where=None, **kwargs): @classmethod def get_query(cls): - """ - Returns a query with parent=cls._get_collection(), and + """ Returns a query with parent=cls._get_collection(), and limits to obj_type of subclass of cls. - - :return: """ cur_where = Query(parent=cls._get_collection(), # TODO: pass caught kwargs to Query constructor @@ -157,8 +153,7 @@ def where(cls, *args, start_after=None, start_at=None, **kwargs,): - """ - Queries the datastore. Note that indexes may need to be added + """ Queries the datastore. Note that indexes may need to be added from the link provided by firestore in the error messages. TODO: add error handling and argument checking @@ -166,7 +161,6 @@ def where(cls, *args, :param args: :param kwargs: - :return: """ cur_where = cls._where_query(*args, **kwargs, diff --git a/flask_boiler/serializable.py b/flask_boiler/serializable.py index f7d1b78..63c44a7 100644 --- a/flask_boiler/serializable.py +++ b/flask_boiler/serializable.py @@ -130,7 +130,7 @@ def embed_element(val: EmbeddedElement): def update_vals(self, with_dict: Optional[Dict] = None, with_raw: Optional[Dict] = None, - **kwargs): + **kwargs) -> None: """ Update values of the object in batch. :param with_dict: Update object with keys and values in the @@ -138,7 +138,6 @@ def update_vals(self, :param with_raw: Do field deserialization before setting value of attributes. :param kwargs: - :return: """ if with_dict is None: @@ -209,7 +208,6 @@ def _export_val(self, val, to_save=False): a relationship field will be saved. Otherwise, changes are not saved, and the TODO: Add support for atomicity - :return: """ def embed_element(val: EmbeddedElement): @@ -236,8 +234,7 @@ def embed_element(val: EmbeddedElement): def _export_as_dict(self, to_save=False, **kwargs) -> dict: """ Map/dict is only supported at root level for now - TODO: implement iterable support - :return: + """ d = self.schema_obj.dump(self) @@ -275,11 +272,9 @@ def embed_element(val: EmbeddedElement): return val def _export_as_view_dict(self) -> dict: - """ - TODO: implement iterable support - :return: - """ + """ Export as dictionary representable to front end. + """ d = self.schema_obj.dump(self) res = dict() diff --git a/flask_boiler/view_mediator_dav.py b/flask_boiler/view_mediator_dav.py index 506b1ed..7f57bf4 100644 --- a/flask_boiler/view_mediator_dav.py +++ b/flask_boiler/view_mediator_dav.py @@ -33,11 +33,10 @@ def __init__(self, @classmethod def notify(cls, obj): - """ - Specifies what to do with the view model newly generated from an update. + """ Specifies what to do with the view model newly generated from + an update. - :param obj: - :return: + :param obj: view model newly generated """ obj.save() @@ -78,6 +77,7 @@ def on_snapshot(snapshots, changes, timestamp): def start(self): """ Generates view models and listen to changes proposed to the view model. + """ self.instances = self.generate_entries() # time.sleep(3) # TODO: delete after implementing sync @@ -117,7 +117,6 @@ def __init__(self, *args, query, **kwargs): def _get_on_snapshot(self): """ Implement in subclass - :return: """ def on_snapshot(snapshots, changes, timestamp): """ @@ -127,7 +126,6 @@ def on_snapshot(snapshots, changes, timestamp): :param snapshots: :param changes: :param timestamp: - :return: """ for change in changes: snapshot = change.document @@ -163,7 +161,6 @@ def on_snapshot(snapshots, changes, timestamp): def start(self): """ Starts a listener to the query. - :return: """ query, on_snapshot = self.query, self._get_on_snapshot() @@ -179,37 +176,34 @@ def start(self): class ProtocolBase: """ Protocol to specify actions when a document is 'ADDED', 'MODIFIED', and 'REMOVED'. + """ @staticmethod def on_create(snapshot: DocumentSnapshot, mediator: ViewMediatorBase): """ - Called when a document is 'ADDED' to the result of a query + Called when a document is 'ADDED' to the result of a query. - :param snapshot: - :param mediator: - :return: + :param snapshot: Firestore Snapshot added + :param mediator: ViewMediator object """ pass @staticmethod def on_update(snapshot: DocumentSnapshot, mediator: ViewMediatorBase): - """ - Called when a document is 'MODIFIED' in the result of a query + """ Called when a document is 'MODIFIED' in the result of a query. - :param snapshot: - :param mediator: - :return: + :param snapshot: Firestore Snapshot modified + :param mediator: ViewMediator object """ pass @staticmethod def on_delete(snapshot: DocumentSnapshot, mediator: ViewMediatorBase): - """ - Called when a document is 'REMOVED' in the result of a query + """ Called when a document is 'REMOVED' in the result of a query. - :param snapshot: - :param mediator: + :param snapshot: Firestore Snapshot deleted + :param mediator: ViewMediator object :return: """ pass diff --git a/flask_boiler/view_model.py b/flask_boiler/view_model.py index 94ea76d..e377fa9 100644 --- a/flask_boiler/view_model.py +++ b/flask_boiler/view_model.py @@ -336,7 +336,7 @@ def default_to_do_nothing(vm: ViewModel, dm: DomainModel): def bind_to(self, key, obj_type, doc_id): """ Binds to a domain model so that this view model changes - when such domain model changes. + when such domain model changes. :param key: :param obj_type: @@ -357,12 +357,11 @@ def to_dict(self): class ViewModel(ViewModelMixin, ReferencedObject): - """ - View model are generated and refreshed automatically - as domain model changes. Note that states stored in ViewModel + """ View model are generated and refreshed automatically as domain + model changes. Note that states stored in ViewModel are unreliable and should not be used to evaluate other states. - Note that since ViewModel is designed to store in a database that is not - strongly consistent, fields may be inconsistent. + Note that since ViewModel is designed to store in a database + that is not strongly consistent, fields may be inconsistent. """ diff --git a/quickstart.md b/quickstart.md index a3ab05d..1bfa96f 100644 --- a/quickstart.md +++ b/quickstart.md @@ -63,17 +63,13 @@ app_name: "" debug: True testing: True certificate_filename: "" - ``` In ```__init__``` of your project source root: ```python -import os - -from flask_boiler import context +from flask_boiler.context import Context as CTX -CTX = context.Context CTX.load() ``` @@ -127,5 +123,6 @@ use the data viewer in the [Firebase console](https://console.firebase.google.co You can get all documents that is a subclass of City where country equals USA: ```python for city in City.where(country="USA"): + ... ```