Skip to content

Commit

Permalink
Decouple callback logics for after view model "states" are updated
Browse files Browse the repository at this point in the history
  • Loading branch information
billyrrr committed Nov 22, 2019
1 parent 47580c9 commit 442a4e8
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 110 deletions.
88 changes: 23 additions & 65 deletions examples/meeting_room/tests/test_dav.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time

import pytest
from google.cloud.firestore_v1 import Watch, DocumentSnapshot, \
DocumentReference, Query

Expand All @@ -11,6 +12,7 @@
from flask_boiler.mutation import Mutation, PatchMutation
from flask_boiler.view import DocumentAsView
from flask_boiler.view_mediator_dav import ViewMediatorDAV
from flask_boiler.view_model import ViewModel
from ..views import meeting_session_ops
from flask_boiler import view_mediator
# Import the fixtures used by fixtures
Expand All @@ -26,10 +28,10 @@ def new(cls, *args, **kwargs):
return cls.get_from_meeting_id(*args, **kwargs)

@classmethod
def get_from_meeting_id(cls, meeting_id, once=False, user=None):
def get_from_meeting_id(cls, meeting_id, once=False, user=None, **kwargs):
doc_ref = user.doc_ref.collection(cls.__name__).document(meeting_id)
return super().get_from_meeting_id(
meeting_id, once=once, doc_ref=doc_ref)
meeting_id, once=once, doc_ref=doc_ref, **kwargs)


class MeetingSessionViewMediatorDAV(ViewMediatorDAV):
Expand All @@ -38,30 +40,6 @@ def __init__(self, *args, meeting_cls=None, **kwargs):
super().__init__(*args, **kwargs)
self.meeting_cls = meeting_cls

def _listen_to_view_model(self, meeting_id, user_id):
obj: MeetingSessionDAV = self.instances[(meeting_id, user_id)]

def on_snapshot(snapshots, changes, timestamp):
if len(snapshots) != 1:
raise ValueError
doc = snapshots[0]
data = obj.diff(doc.to_dict())
if data:
self.mutation_cls.mutate_patch_one(obj=obj, data=data)

watch = Watch.for_document(
document_ref=obj.doc_ref,
snapshot_callback=on_snapshot,
snapshot_class_instance=DocumentSnapshot,
reference_class_instance=DocumentReference)

def start(self):
self.generate_entries()
for meeting_id, user_id in self.instances:
self._listen_to_view_model(
meeting_id=meeting_id,
user_id=user_id)

def generate_entries(self):

meetings = self.meeting_cls.all()
Expand All @@ -72,9 +50,10 @@ def generate_entries(self):
obj = self.view_model_cls.new(
meeting_id=meeting.doc_id,
once=False,
user=user
user=user,
f_notify=self.notify
)
self.instances[(meeting.doc_id, user.doc_id)] = obj
self.instances[obj.doc_ref._document_path] = obj


def test_start(users, tickets, location, meeting):
Expand Down Expand Up @@ -252,17 +231,18 @@ def test_view_model(users, tickets, location, meeting):
'numHearingAidRequested': 2}


class UserViewDAV(UserViewMixin, DocumentAsView):
class UserViewDAV(UserViewMixin, ViewModel):

@classmethod
def new(cls, *args, **kwargs):
return cls.get_from_user_id(*args, **kwargs)

@classmethod
def get_from_user_id(cls, user_id, once=False):
doc_ref = Context.db.collection(cls._get_collection_name()) \
def get_from_user_id(cls, user_id, once=False, **kwargs):
doc_ref = Context.db.collection(cls.__name__) \
.document(user_id)
return super().get_from_user_id(user_id, once=once, doc_ref=doc_ref)
return super().get_from_user_id(user_id, once=once, doc_ref=doc_ref,
**kwargs)

def propagate_change(self):
self.user.save()
Expand All @@ -274,47 +254,23 @@ def __init__(self, *args, user_cls=None, **kwargs):
super().__init__(*args, **kwargs)
self.user_cls = user_cls

def _listen_to_patch(self):
# NOTE: index for subcollection group may need to be created
# See: https://firebase.google.com/docs/firestore/query-data/queries#top_of_page

def on_snapshot(snapshots, changes, timestamp):
for doc in snapshots:
# doc: DocumentSnapshot = snapshots[0]
data = doc.to_dict()
data = {
self.view_model_cls.get_schema_cls().g(key): val
for key, val in data.items()
}

# ie. doc from /UserViewDAV/user_id_a/_PATCH_UserViewDAV/patch_id_1
user_view_ref = doc.reference.parent.parent
user_id = user_view_ref.id
obj = self.instances[user_id]

self.mutation_cls.mutate_patch_one(obj=obj, data=data)

watch = Watch.for_query(
query=self.view_model_cls._get_patch_query(),
snapshot_callback=on_snapshot,
snapshot_class_instance=DocumentSnapshot,
reference_class_instance=DocumentReference)

def start(self):
self.generate_entries()
time.sleep(3) # TODO: delete after implementing sync
self._listen_to_patch()
@classmethod
def notify(cls, obj):
obj.save()

def generate_entries(self):

d = dict()
users = self.user_cls.all()
for user in users:
assert isinstance(user, User)
obj = self.view_model_cls.new(
user_id=user.doc_id,
once=False,
f_notify=self.notify
)
self.instances[user.doc_id] = obj
d[obj.doc_ref._document_path] = obj
return d


def test_user_view(users, tickets, location, meeting):
Expand Down Expand Up @@ -350,6 +306,7 @@ def test_user_view(users, tickets, location, meeting):
'organization': 'UCSD'}


@pytest.mark.skip
def test_user_view_diff(users, tickets, location, meeting):
user_view = UserViewDAV.new(user_id="thomasina", )

Expand Down Expand Up @@ -393,11 +350,12 @@ def test_mutation(users, tickets, location, meeting):

mediator.start()

user_view = mediator.instances[user_id]
ref = Context.db.collection("UserViewDAV").document(user_id)

time.sleep(3) # TODO: delete after implementing sync

ref = Context.db.collection("UserViewDAV").document(user_id)
user_view = mediator.instances[ref._document_path]

ref.collection("_PATCH_UserViewDAV").add({
"lastName": "Manes-Kennedy"
})
Expand Down
3 changes: 3 additions & 0 deletions flask_boiler/serializable.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ def _export_as_dict(self, to_save=False) -> dict:
"""
d = self.schema_obj.dump(self)

# print("----")
# print(d)

res = dict()
for key, val in d.items():
res[key] = self._export_val(val, to_save=to_save)
Expand Down
21 changes: 8 additions & 13 deletions flask_boiler/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ def new(cls, *args, **kwargs):
# on_update([dm_ref.get()], changes=None, readtime=None)


class FlaskAsView(FlaskAsViewMixin,
class FlaskAsView(ViewModel):
pass

class DocumentAsView(ViewModel):
pass

class _FlaskAsView(FlaskAsViewMixin,
ViewModelMixin,
PersistableMixin,
SerializableFO
Expand All @@ -62,17 +68,6 @@ def new(cls, **kwargs):

class DocumentAsViewMixin:

@classmethod
def _get_collection_name(cls):
return cls.__name__

@classmethod
def _get_patch_query(cls) -> firestore.Query:
collection_group_id = "_PATCH_{}" \
.format(cls._get_collection_name())
collection_group_query = CTX.db.collection_group(collection_group_id)
return collection_group_query

@classmethod
def new(cls, *args, **kwargs):
"""
Expand All @@ -96,7 +91,7 @@ def _notify(self):
self.save()


class DocumentAsView(DocumentAsViewMixin,
class _DocumentAsView(DocumentAsViewMixin,
ViewModelMixin,
PersistableMixin,
ReferencedObject):
Expand Down
48 changes: 48 additions & 0 deletions flask_boiler/view_mediator_dav.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import time

from flask_boiler.context import Context as CTX
from flasgger import SwaggerView
from flask import request

from google.cloud.firestore import Watch, DocumentSnapshot, \
DocumentReference, Query

from flask_boiler.domain_model import DomainModel


Expand All @@ -28,4 +32,48 @@ def __init__(self,
self.default_tag = self.view_model_cls.__name__
self.instances = dict()

@classmethod
def notify(cls, obj):
obj.save()

def _get_collection_name(self):
return self.view_model_cls.__name__

def _get_patch_query(self) -> Query:
collection_group_id = "_PATCH_{}" \
.format(self._get_collection_name())
collection_group_query = CTX.db.collection_group(collection_group_id)
return collection_group_query

def _listen_to_patch(self):
# NOTE: index for subcollection group may need to be created
# See: https://firebase.google.com/docs/firestore/query-data/queries#top_of_page

def on_snapshot(snapshots, changes, timestamp):
for doc in snapshots:
# doc: DocumentSnapshot = snapshots[0]
data = doc.to_dict()
data = {
self.view_model_cls.get_schema_cls().g(key): val
for key, val in data.items()
}

# ie. doc from /UserViewDAV/user_id_a/_PATCH_UserViewDAV/patch_id_1
parent_view_ref: DocumentReference = doc.reference.parent.parent
obj = self.instances[parent_view_ref._document_path]

self.mutation_cls.mutate_patch_one(obj=obj, data=data)

watch = Watch.for_query(
query=self._get_patch_query(),
snapshot_callback=on_snapshot,
snapshot_class_instance=DocumentSnapshot,
reference_class_instance=DocumentReference)

def start(self):
self.instances = self.generate_entries()
time.sleep(3) # TODO: delete after implementing sync
self._listen_to_patch()

def generate_entries(self):
raise NotImplementedError
65 changes: 38 additions & 27 deletions flask_boiler/view_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,20 @@ def get_many(cls, struct_d_iterable=None, once=False):
return [cls.get(struct_d=struct_d, once=once)
for struct_d in struct_d_iterable]

def __init__(self, *args, **kwargs):
def __init__(self, f_notify=None, *args, **kwargs):
"""
:param f_notify: callback to notify that view model's
business properties have finished updating
:param args:
:param kwargs:
"""
super().__init__(*args, **kwargs)
self.business_properties: Dict[str, DomainModel] = dict()
self.snapshot_container = SnapshotContainer()
self._on_update_funcs: Dict[str, Tuple] = dict()
self.listener = None
self.f_notify = f_notify

def _bind_to_domain_model(self, *, key, obj_type, doc_id):
"""
Expand Down Expand Up @@ -167,30 +175,30 @@ def __subscribe_to(self, *, key, dm_cls,
# doc_watch = dm_ref.on_snapshot(on_update)
self._on_update_funcs[dm_ref._document_path] = on_update

def diff(self, new_state, allowed=None):
prev_state = self.to_view_dict()

if prev_state["lastName"] == "Manes" and new_state["lastName"] == "M.":
return {
"lastName": "M."
}
else:
return dict()

if allowed is not None:
prev_state = {key: val
for key, val in prev_state.items()
if key in allowed}
new_state = {key: val
for key, val in new_state.items()
if key in allowed}

diff_res = diff(prev_state, new_state)
result = patch(diff_result=diff_res,
destination=dict(),
in_place=False
)
return result
# def diff(self, new_state, allowed=None):
# prev_state = self.to_view_dict()
#
# if prev_state["lastName"] == "Manes" and new_state["lastName"] == "M.":
# return {
# "lastName": "M."
# }
# else:
# return dict()
#
# if allowed is not None:
# prev_state = {key: val
# for key, val in prev_state.items()
# if key in allowed}
# new_state = {key: val
# for key, val in new_state.items()
# if key in allowed}
#
# diff_res = diff(prev_state, new_state)
# result = patch(diff_result=diff_res,
# destination=dict(),
# in_place=False
# )
# return result

def listen_once(self):

Expand Down Expand Up @@ -277,7 +285,8 @@ def _notify(self):
:return:
"""
return
if self.f_notify is not None:
self.f_notify(self)

def get_vm_update_callback(self, dm_cls, *args, **kwargs) -> Callable:
""" Returns a function for updating a view
Expand Down Expand Up @@ -309,4 +318,6 @@ def to_dict(self):


class ViewModel(ViewModelMixin, PersistableMixin, ReferencedObject):
pass

def __init__(self, *args, doc_ref=None, **kwargs):
super().__init__(*args, doc_ref=doc_ref, **kwargs)

0 comments on commit 442a4e8

Please sign in to comment.