Skip to content

Commit 264b649

Browse files
committed
feat(firebase): validate data before updating the data
- add inheritance checks to FirebasePush
1 parent 2c4dc88 commit 264b649

File tree

3 files changed

+80
-23
lines changed

3 files changed

+80
-23
lines changed

apps/common/firebase.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import typing
44

55
from firebase_admin.db import Reference as FbReference
6+
from pydantic import BaseModel, ConfigDict
67

78
from apps.common.models import FirebasePushResource, FirebasePushStatusEnum
89
from main.celery import app
@@ -14,41 +15,74 @@
1415
class InvalidObjectPushException(Exception): ...
1516

1617

17-
class FirebasePush[T: FirebasePushResource](abc.ABC):
18-
model: type[T]
18+
# FIXME(tnagorra): Add inheritance_checks for model and firebase_model
19+
class FirebasePush[T: FirebasePushResource, K: BaseModel](abc.ABC):
20+
model_class: type[T]
21+
firebase_model_class: type[K]
1922

2023
def __init__(
2124
self,
2225
obj_id: int,
2326
):
2427
self.obj_id = obj_id
2528

29+
@classmethod
30+
def _inheritance_checks(cls):
31+
# FIXME(tnagorra): Find a better way to skip for base classes
32+
if cls.__name__ == "FirebasePush":
33+
# Skip check for the abstract class
34+
return
35+
36+
missing_fields = []
37+
for attr_name in [
38+
"model_class",
39+
"firebase_model_class",
40+
]:
41+
if getattr(cls, attr_name, None) is None:
42+
missing_fields.append(attr_name)
43+
44+
if missing_fields:
45+
raise NotImplementedError(f"Please define {','.join(missing_fields)} for {cls}")
46+
47+
def __init_subclass__(cls, **kwargs):
48+
super().__init_subclass__(**kwargs)
49+
cls._inheritance_checks()
50+
2651
@abc.abstractmethod
2752
def handle_new_object_on_firebase(self, model_obj: T, fb_reference: FbReference): ...
2853

2954
@abc.abstractmethod
30-
def handle_object_update_on_firebase(self, model_obj: T, fb_reference: FbReference): ...
55+
def handle_object_update_on_firebase(self, model_obj: T, fb_obj: K, fb_reference: FbReference): ...
3156

3257
@abc.abstractmethod
3358
def get_firebase_path(self, firebase_id: str, model: type[T]) -> str: ...
3459

3560
def push(self) -> None:
36-
model_obj = self.model.objects.get(id=self.obj_id)
61+
model_obj = self.model_class.objects.get(id=self.obj_id)
62+
63+
# FIXME(tnagorra): The following fails because set firebase_push_status is not set on create/update
64+
# if model_obj.firebase_push_status_enum != FirebasePushStatusEnum.PENDING:
65+
# logger.warning(
66+
# "Firebase push error: push is not required for %s",
67+
# model_obj._meta.label,
68+
# extra={"id": model_obj.pk},
69+
# )
70+
# return
3771

3872
model_obj.update_firebase_push_status(FirebasePushStatusEnum.PROCESSING)
3973

4074
try:
4175
model_ref = Config.FIREBASE_HELPER.ref(
42-
self.get_firebase_path(model_obj.firebase_id, self.model),
76+
self.get_firebase_path(model_obj.firebase_id, self.model_class),
4377
)
4478
fb_model: typing.Any = model_ref.get()
4579

4680
if not model_obj.firebase_last_pushed:
4781
if fb_model is not None:
4882
logger.error(
49-
"Firebase creation error: existing %s found",
83+
"Firebase create error: %s already exists in Firebase",
5084
model_obj._meta.label,
51-
extra={"model_obj_id": model_obj.pk},
85+
extra={"id": model_obj.pk},
5286
)
5387
raise InvalidObjectPushException
5488
self.handle_new_object_on_firebase(model_obj, model_ref)
@@ -57,17 +91,24 @@ def push(self) -> None:
5791
logger.error(
5892
"Firebase update error: missing %s in Firebase",
5993
model_obj._meta.label,
60-
extra={"model_obj_id": model_obj.pk},
94+
extra={"id": model_obj.pk},
6195
)
6296
raise InvalidObjectPushException
63-
self.handle_object_update_on_firebase(model_obj, model_ref)
6497

98+
class RelaxedModel(self.firebase_model_class):
99+
model_config = ConfigDict(extra="ignore")
100+
101+
# NOTE: we want to ignore extra fields from firebase
102+
valid_fb_model = RelaxedModel.model_validate(obj=fb_model)
103+
valid_fb_model = self.firebase_model_class.model_validate(valid_fb_model)
104+
105+
self.handle_object_update_on_firebase(model_obj, valid_fb_model, model_ref)
65106
except InvalidObjectPushException:
66107
model_obj.update_firebase_push_status(FirebasePushStatusEnum.FAILED)
67108
except Exception:
68109
logger.error(
69-
"Unexpected error while pushing to Firebase",
70-
extra={"model_obj_id": model_obj.pk},
110+
"Firebase push error: Unexpected error occurred",
111+
extra={"id": model_obj.pk},
71112
exc_info=True,
72113
)
73114
model_obj.update_firebase_push_status(FirebasePushStatusEnum.FAILED)

apps/contributor/firebase.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from celery import shared_task
55
from firebase_admin.db import Reference as FbReference
6+
from pyfirebase_mapswipe import extended_models as firebase_ext_models
67
from pyfirebase_mapswipe import models as firebase_models
78
from pyfirebase_mapswipe import utils as firebase_utils
89

@@ -14,9 +15,9 @@
1415
logger = logging.getLogger(__name__)
1516

1617

17-
class FirebaseContributorTeam(FirebasePush[ContributorTeam]):
18-
model_obj: ContributorTeam
19-
model = ContributorTeam
18+
class FirebaseContributorTeam(FirebasePush[ContributorTeam, firebase_models.FbTeam]):
19+
model_class = ContributorTeam
20+
firebase_model_class = firebase_models.FbTeam
2021

2122
@typing.override
2223
def handle_new_object_on_firebase(self, model_obj: ContributorTeam, fb_reference: FbReference):
@@ -31,7 +32,12 @@ def handle_new_object_on_firebase(self, model_obj: ContributorTeam, fb_reference
3132
)
3233

3334
@typing.override
34-
def handle_object_update_on_firebase(self, model_obj: ContributorTeam, fb_reference: FbReference):
35+
def handle_object_update_on_firebase(
36+
self,
37+
model_obj: ContributorTeam,
38+
fb_obj: firebase_models.FbTeam,
39+
fb_reference: FbReference,
40+
):
3541
fb_reference.update(
3642
value=firebase_utils.serialize(
3743
firebase_models.FbTeam(
@@ -53,17 +59,22 @@ def task(obj_id: int) -> None:
5359
FirebaseContributorTeam(obj_id).push()
5460

5561

56-
class FirebaseContributorUser(FirebasePush[ContributorUser]):
57-
model_obj: ContributorUser
58-
model = ContributorUser
62+
class FirebaseContributorUser(FirebasePush[ContributorUser, firebase_ext_models.FbUser]):
63+
model_class = ContributorUser
64+
firebase_model_class = firebase_ext_models.FbUser
5965

6066
@typing.override
6167
def handle_new_object_on_firebase(self, model_obj: ContributorUser, fb_reference: FbReference):
6268
# FIXME(tnagorra): Use a better exception
6369
raise Exception("User cannot be created from mapswipe-backend")
6470

6571
@typing.override
66-
def handle_object_update_on_firebase(self, model_obj: ContributorUser, fb_reference: FbReference):
72+
def handle_object_update_on_firebase(
73+
self,
74+
model_obj: ContributorUser,
75+
fb_obj: firebase_ext_models.FbUser,
76+
fb_reference: FbReference,
77+
):
6778
fb_reference.update(
6879
value=firebase_utils.serialize(
6980
firebase_models.FbUserUpdateInput(

apps/project/firebase.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
logger = logging.getLogger(__name__)
1414

1515

16-
class FirebaseOrganizationPush(FirebasePush[Organization]):
17-
model_obj: Organization
18-
model = Organization
16+
class FirebaseOrganizationPush(FirebasePush[Organization, firebase_models.FbOrganisation]):
17+
model_class = Organization
18+
firebase_model_class = firebase_models.FbOrganisation
1919

2020
@typing.override
2121
def handle_new_object_on_firebase(self, model_obj: Organization, fb_reference: FbReference):
@@ -31,7 +31,12 @@ def handle_new_object_on_firebase(self, model_obj: Organization, fb_reference: F
3131
)
3232

3333
@typing.override
34-
def handle_object_update_on_firebase(self, model_obj: Organization, fb_reference: FbReference):
34+
def handle_object_update_on_firebase(
35+
self,
36+
model_obj: Organization,
37+
fb_obj: firebase_models.FbOrganisation,
38+
fb_reference: FbReference,
39+
):
3540
fb_reference.update(
3641
value=firebase_utils.serialize(
3742
firebase_models.FbOrganisation(

0 commit comments

Comments
 (0)