From 093575494132ccb708595c4f9dafc2b74269e21a Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 18 Mar 2024 14:46:44 +0530 Subject: [PATCH 01/14] feat: initial implementation of new triggers --- src/firebase_functions/firestore_fn.py | 301 ++++++++++++++++++++++--- 1 file changed, 271 insertions(+), 30 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 4b66f15..c077831 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -38,6 +38,11 @@ _event_type_updated = "google.cloud.firestore.document.v1.updated" _event_type_deleted = "google.cloud.firestore.document.v1.deleted" +_event_type_written_with_auth_context = "google.cloud.firestore.document.v1.written.withAuthContext" +_event_type_created_with_auth_context = "google.cloud.firestore.document.v1.created.withAuthContext" +_event_type_updated_with_auth_context = "google.cloud.firestore.document.v1.updated.withAuthContext" +_event_type_deleted_with_auth_context = "google.cloud.firestore.document.v1.deleted.withAuthContext" + @_dataclass.dataclass(frozen=True) class Event(_core.CloudEvent[_core.T]): @@ -82,24 +87,43 @@ class Event(_core.CloudEvent[_core.T]): _C1 = _typing.Callable[[_E1], None] _C2 = _typing.Callable[[_E2], None] +AuthType = _typing.Literal["service_account", "api_key", "system", +"unauthenticated", "unknown"] + + +@_dataclass.dataclass(frozen=True) +class EventWithAuthContext(Event[_core.T]): + auth_type: AuthType + """The type of principal that triggered the event""" + auth_id: str + """The unique identifier for the principal""" + + +_E3 = EventWithAuthContext[Change[DocumentSnapshot | None]] +_E4 = EventWithAuthContext[DocumentSnapshot | None] +_C3 = _typing.Callable[[_E3], None] +_C4 = _typing.Callable[[_E4], None] + def _firestore_endpoint_handler( - func: _C1 | _C2, - event_type: str, - document_pattern: _path_pattern.PathPattern, - raw: _ce.CloudEvent, + func: _C1 | _C2 | _C3 | _C4, + event_type: str, + document_pattern: _path_pattern.PathPattern, + raw: _ce.CloudEvent, ) -> None: event_attributes = raw._get_attributes() event_data: _typing.Any = raw.get_data() firestore_event_data: _firestore.DocumentEventData content_type: str = event_attributes["datacontenttype"] if "application/json" in content_type or isinstance(event_data, dict): - firestore_event_data = _firestore.DocumentEventData.from_json( - event_data) + firestore_event_data = _typing.cast( + _firestore.DocumentEventData, + _firestore.DocumentEventData.from_json(event_data)) elif "application/protobuf" in content_type or isinstance( event_data, bytes): - firestore_event_data = _firestore.DocumentEventData.deserialize( - event_data) + firestore_event_data = _typing.cast( + _firestore.DocumentEventData, + _firestore.DocumentEventData.deserialize(event_data)) else: actual_type = type(event_data) raise TypeError(f"Firestore: Cannot parse event payload of data type " @@ -110,6 +134,8 @@ def _firestore_endpoint_handler( event_namespace = event_attributes["namespace"] event_document = event_attributes["document"] event_database = event_attributes["database"] + event_auth_type = event_attributes["authType"] + event_auth_id = event_attributes["authId"] time = event_attributes["time"] event_time = _util.timestamp_conversion(time) @@ -146,34 +172,49 @@ def _firestore_endpoint_handler( firestore_event_data.old_value.update_time, ) if event_type == _event_type_deleted: - firestore_event_data = old_value_snapshot + firestore_event_data = _typing.cast(_firestore.DocumentEventData, + old_value_snapshot) if event_type == _event_type_created: - firestore_event_data = value_snapshot + firestore_event_data = _typing.cast(_firestore.DocumentEventData, + value_snapshot) if event_type in (_event_type_written, _event_type_updated): - firestore_event_data = Change( - before=old_value_snapshot, - after=value_snapshot, - ) + firestore_event_data = _typing.cast( + _firestore.DocumentEventData, + Change( + before=old_value_snapshot, + after=value_snapshot, + )) params: dict[str, str] = { **document_pattern.extract_matches(event_document), } - database_event = Event( - project=event_project, - namespace=event_namespace, - database=event_database, - location=event_location, - document=event_document, - specversion=event_attributes["specversion"], - id=event_attributes["id"], - source=event_attributes["source"], - type=event_attributes["type"], - time=event_time, - data=firestore_event_data, - subject=event_attributes["subject"], - params=params, - ) - func(database_event) + + common_event_kwargs = { + 'project': event_project, + 'namespace': event_namespace, + 'database': event_database, + 'location': event_location, + 'document': event_document, + 'specversion': event_attributes["specversion"], + 'id': event_attributes["id"], + 'source': event_attributes["source"], + 'type': event_attributes["type"], + 'time': event_time, + 'data': firestore_event_data, + 'subject': event_attributes["subject"], + 'params': params, + } + + if event_type.endswith('.withAuthContext'): + database_event_with_auth_context = EventWithAuthContext( + **common_event_kwargs, + auth_type=event_auth_type, + auth_id=event_auth_id) + func(database_event_with_auth_context) + else: + database_event = Event(**common_event_kwargs) + # mypy cannot infer that the event type is correct, hence the cast + _typing.cast(_C1 | _C2, func)(database_event) @_util.copy_func_kwargs(FirestoreOptions) @@ -224,6 +265,56 @@ def on_document_written_wrapped(raw: _ce.CloudEvent): return on_document_written_inner_decorator +@_util.copy_func_kwargs(FirestoreOptions) +def on_document_written_with_auth_context(**kwargs + ) -> _typing.Callable[[_C1], _C1]: + """ + Event handler that triggers when a document is created, updated, or deleted in Firestore. + This trigger will also provide the authentication context of the principal who triggered the event. + + Example: + + .. code-block:: python + + @on_document_written_with_auth_context(document="*") + def example(event: Event[Change[DocumentSnapshot]]) -> None: + pass + + :param \\*\\*kwargs: Firestore options. + :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` + :rtype: :exc:`typing.Callable` + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + :exc:`firebase_functions.db.Change` \\] \\], `None` \\] + A function that takes a Firestore event and returns ``None``. + """ + options = FirestoreOptions(**kwargs) + + def on_document_written_with_auth_context_inner_decorator(func: _C1): + document_pattern = _path_pattern.PathPattern( + _util.normalize_path(options.document)) + + @_functools.wraps(func) + def on_document_written_with_auth_context_wrapped(raw: _ce.CloudEvent): + return _firestore_endpoint_handler( + func, + _event_type_written_with_auth_context, + document_pattern, + raw, + ) + + _util.set_func_endpoint_attr( + on_document_written_with_auth_context_wrapped, + options._endpoint( + event_type=_event_type_written, + func_name=func.__name__, + document_pattern=document_pattern, + ), + ) + return on_document_written_with_auth_context_wrapped + + return on_document_written_with_auth_context_inner_decorator + + @_util.copy_func_kwargs(FirestoreOptions) def on_document_updated(**kwargs) -> _typing.Callable[[_C1], _C1]: """ @@ -272,6 +363,56 @@ def on_document_updated_wrapped(raw: _ce.CloudEvent): return on_document_updated_inner_decorator +@_util.copy_func_kwargs(FirestoreOptions) +def on_document_updated_with_auth_context(**kwargs + ) -> _typing.Callable[[_C1], _C1]: + """ + Event handler that triggers when a document is updated in Firestore. + This trigger will also provide the authentication context of the principal who triggered the event. + + Example: + + .. code-block:: python + + @on_document_updated_with_auth_context(document="*") + def example(event: Event[Change[DocumentSnapshot]]) -> None: + pass + + :param \\*\\*kwargs: Firestore options. + :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` + :rtype: :exc:`typing.Callable` + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + :exc:`firebase_functions.db.Change` \\] \\], `None` \\] + A function that takes a Firestore event and returns ``None``. + """ + options = FirestoreOptions(**kwargs) + + def on_document_updated_with_auth_context_inner_decorator(func: _C1): + document_pattern = _path_pattern.PathPattern( + _util.normalize_path(options.document)) + + @_functools.wraps(func) + def on_document_updated_with_auth_context_wrapped(raw: _ce.CloudEvent): + return _firestore_endpoint_handler( + func, + _event_type_updated_with_auth_context, + document_pattern, + raw, + ) + + _util.set_func_endpoint_attr( + on_document_updated_with_auth_context_wrapped, + options._endpoint( + event_type=_event_type_updated_with_auth_context, + func_name=func.__name__, + document_pattern=document_pattern, + ), + ) + return on_document_updated_with_auth_context_wrapped + + return on_document_updated_with_auth_context_inner_decorator + + @_util.copy_func_kwargs(FirestoreOptions) def on_document_created(**kwargs) -> _typing.Callable[[_C2], _C2]: """ @@ -320,6 +461,56 @@ def on_document_created_wrapped(raw: _ce.CloudEvent): return on_document_created_inner_decorator +@_util.copy_func_kwargs(FirestoreOptions) +def on_document_created_with_auth_context(**kwargs + ) -> _typing.Callable[[_C2], _C2]: + """ + Event handler that triggers when a document is created in Firestore. + This trigger will also provide the authentication context of the principal who triggered the event. + + Example: + + .. code-block:: python + + @on_document_created_with_auth_context(document="*") + def example(event: Event[DocumentSnapshot]): + pass + + :param \\*\\*kwargs: Firestore options. + :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` + :rtype: :exc:`typing.Callable` + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + :exc:`object` \\] \\], `None` \\] + A function that takes a Firestore event and returns ``None``. + """ + options = FirestoreOptions(**kwargs) + + def on_document_created_with_auth_context_inner_decorator(func: _C2): + document_pattern = _path_pattern.PathPattern( + _util.normalize_path(options.document)) + + @_functools.wraps(func) + def on_document_created_with_auth_context_wrapped(raw: _ce.CloudEvent): + return _firestore_endpoint_handler( + func, + _event_type_created_with_auth_context, + document_pattern, + raw, + ) + + _util.set_func_endpoint_attr( + on_document_created_with_auth_context_wrapped, + options._endpoint( + event_type=_event_type_created_with_auth_context, + func_name=func.__name__, + document_pattern=document_pattern, + ), + ) + return on_document_created_with_auth_context_wrapped + + return on_document_created_with_auth_context_inner_decorator + + @_util.copy_func_kwargs(FirestoreOptions) def on_document_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]: """ @@ -366,3 +557,53 @@ def on_document_deleted_wrapped(raw: _ce.CloudEvent): return on_document_deleted_wrapped return on_document_deleted_inner_decorator + + +@_util.copy_func_kwargs(FirestoreOptions) +def on_document_deleted_with_auth_context(**kwargs + ) -> _typing.Callable[[_C2], _C2]: + """ + Event handler that triggers when a document is deleted in Firestore. + This trigger will also provide the authentication context of the principal who triggered the event. + + Example: + + .. code-block:: python + + @on_document_deleted_with_auth_context(document="*") + def example(event: Event[DocumentSnapshot]) -> None: + pass + + :param \\*\\*kwargs: Firestore options. + :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` + :rtype: :exc:`typing.Callable` + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + :exc:`object` \\] \\], `None` \\] + A function that takes a Firestore event and returns ``None``. + """ + options = FirestoreOptions(**kwargs) + + def on_document_deleted_with_auth_context_inner_decorator(func: _C2): + document_pattern = _path_pattern.PathPattern( + _util.normalize_path(options.document)) + + @_functools.wraps(func) + def on_document_deleted_with_auth_context_wrapped(raw: _ce.CloudEvent): + return _firestore_endpoint_handler( + func, + _event_type_deleted_with_auth_context, + document_pattern, + raw, + ) + + _util.set_func_endpoint_attr( + on_document_deleted_with_auth_context_wrapped, + options._endpoint( + event_type=_event_type_deleted_with_auth_context, + func_name=func.__name__, + document_pattern=document_pattern, + ), + ) + return on_document_deleted_with_auth_context_wrapped + + return on_document_deleted_with_auth_context_inner_decorator From c5b1dcf2c6a95aac4661d0627dd0e5419b4a2cd2 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 18 Mar 2024 17:53:59 +0530 Subject: [PATCH 02/14] feat: lowercase authtype and authid accessors and add debug logs --- src/firebase_functions/firestore_fn.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index c077831..3a60501 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -33,6 +33,8 @@ from firebase_functions.options import FirestoreOptions from firebase_functions.core import Change +from firebase_functions import logger + _event_type_written = "google.cloud.firestore.document.v1.written" _event_type_created = "google.cloud.firestore.document.v1.created" _event_type_updated = "google.cloud.firestore.document.v1.updated" @@ -129,13 +131,15 @@ def _firestore_endpoint_handler( raise TypeError(f"Firestore: Cannot parse event payload of data type " f"'{actual_type}' and content type '{content_type}'.") + logger.debug("Event Attributes", event_attributes=event_attributes) + event_location = event_attributes["location"] event_project = event_attributes["project"] event_namespace = event_attributes["namespace"] event_document = event_attributes["document"] event_database = event_attributes["database"] - event_auth_type = event_attributes["authType"] - event_auth_id = event_attributes["authId"] + event_auth_type = event_attributes["authtype"] + event_auth_id = event_attributes["authid"] time = event_attributes["time"] event_time = _util.timestamp_conversion(time) From fb396ebe9099a6a8834a22b3153ef512fdaa2ef4 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 18 Mar 2024 18:22:59 +0530 Subject: [PATCH 03/14] feat: remove debug logs --- src/firebase_functions/firestore_fn.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 3a60501..0d65c99 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -131,8 +131,6 @@ def _firestore_endpoint_handler( raise TypeError(f"Firestore: Cannot parse event payload of data type " f"'{actual_type}' and content type '{content_type}'.") - logger.debug("Event Attributes", event_attributes=event_attributes) - event_location = event_attributes["location"] event_project = event_attributes["project"] event_namespace = event_attributes["namespace"] From 9a80483c652afff8ad40f38e575be278af04494f Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 18 Mar 2024 19:59:07 +0530 Subject: [PATCH 04/14] feat: remove logger import --- src/firebase_functions/firestore_fn.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 0d65c99..433c1ed 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -33,8 +33,6 @@ from firebase_functions.options import FirestoreOptions from firebase_functions.core import Change -from firebase_functions import logger - _event_type_written = "google.cloud.firestore.document.v1.written" _event_type_created = "google.cloud.firestore.document.v1.created" _event_type_updated = "google.cloud.firestore.document.v1.updated" From 97b19384d1caa7b61871db6c7952ca06d43a3591 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Wed, 20 Mar 2024 04:44:55 +0530 Subject: [PATCH 05/14] feat: update docs to reflect new EventWithAuthContext type --- src/firebase_functions/firestore_fn.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 433c1ed..e7497a6 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -277,13 +277,13 @@ def on_document_written_with_auth_context(**kwargs .. code-block:: python @on_document_written_with_auth_context(document="*") - def example(event: Event[Change[DocumentSnapshot]]) -> None: + def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -375,13 +375,13 @@ def on_document_updated_with_auth_context(**kwargs .. code-block:: python @on_document_updated_with_auth_context(document="*") - def example(event: Event[Change[DocumentSnapshot]]) -> None: + def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -473,13 +473,13 @@ def on_document_created_with_auth_context(**kwargs .. code-block:: python @on_document_created_with_auth_context(document="*") - def example(event: Event[DocumentSnapshot]): + def example(event: EventWithAuthContext[DocumentSnapshot]): pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -571,13 +571,13 @@ def on_document_deleted_with_auth_context(**kwargs .. code-block:: python @on_document_deleted_with_auth_context(document="*") - def example(event: Event[DocumentSnapshot]) -> None: + def example(event: EventWithAuthContext[DocumentSnapshot]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ From c8413518a543e523bc62c7a634cc857b9a52afd4 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Wed, 20 Mar 2024 04:48:53 +0530 Subject: [PATCH 06/14] feat: linting and formatting changes --- src/firebase_functions/firestore_fn.py | 58 ++++++++++++++------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index e7497a6..2a40b4d 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -88,7 +88,7 @@ class Event(_core.CloudEvent[_core.T]): _C2 = _typing.Callable[[_E2], None] AuthType = _typing.Literal["service_account", "api_key", "system", -"unauthenticated", "unknown"] + "unauthenticated", "unknown"] @_dataclass.dataclass(frozen=True) @@ -106,10 +106,10 @@ class EventWithAuthContext(Event[_core.T]): def _firestore_endpoint_handler( - func: _C1 | _C2 | _C3 | _C4, - event_type: str, - document_pattern: _path_pattern.PathPattern, - raw: _ce.CloudEvent, + func: _C1 | _C2 | _C3 | _C4, + event_type: str, + document_pattern: _path_pattern.PathPattern, + raw: _ce.CloudEvent, ) -> None: event_attributes = raw._get_attributes() event_data: _typing.Any = raw.get_data() @@ -190,22 +190,22 @@ def _firestore_endpoint_handler( } common_event_kwargs = { - 'project': event_project, - 'namespace': event_namespace, - 'database': event_database, - 'location': event_location, - 'document': event_document, - 'specversion': event_attributes["specversion"], - 'id': event_attributes["id"], - 'source': event_attributes["source"], - 'type': event_attributes["type"], - 'time': event_time, - 'data': firestore_event_data, - 'subject': event_attributes["subject"], - 'params': params, + "project": event_project, + "namespace": event_namespace, + "database": event_database, + "location": event_location, + "document": event_document, + "specversion": event_attributes["specversion"], + "id": event_attributes["id"], + "source": event_attributes["source"], + "type": event_attributes["type"], + "time": event_time, + "data": firestore_event_data, + "subject": event_attributes["subject"], + "params": params, } - if event_type.endswith('.withAuthContext'): + if event_type.endswith(".withAuthContext"): database_event_with_auth_context = EventWithAuthContext( **common_event_kwargs, auth_type=event_auth_type, @@ -267,10 +267,11 @@ def on_document_written_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_written_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: + ) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is created, updated, or deleted in Firestore. - This trigger will also provide the authentication context of the principal who triggered the event. + This trigger will also provide the authentication context of the principal who triggered + the event. Example: @@ -365,10 +366,11 @@ def on_document_updated_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_updated_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: + ) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is updated in Firestore. - This trigger will also provide the authentication context of the principal who triggered the event. + This trigger will also provide the authentication context of the principal who triggered + the event. Example: @@ -463,10 +465,11 @@ def on_document_created_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_created_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: + ) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is created in Firestore. - This trigger will also provide the authentication context of the principal who triggered the event. + This trigger will also provide the authentication context of the principal who triggered + the event. Example: @@ -561,10 +564,11 @@ def on_document_deleted_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_deleted_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: + ) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is deleted in Firestore. - This trigger will also provide the authentication context of the principal who triggered the event. + This trigger will also provide the authentication context of the principal who triggered + the event. Example: From 8788ebc0a200298f1e1699b85ce7da3cb05c11aa Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 25 Mar 2024 18:04:38 +0530 Subject: [PATCH 07/14] feat: Make Event common object, using __dict__ to spread into AuthContext --- src/firebase_functions/firestore_fn.py | 51 +++++++++++++------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 2a40b4d..933275d 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -88,7 +88,7 @@ class Event(_core.CloudEvent[_core.T]): _C2 = _typing.Callable[[_E2], None] AuthType = _typing.Literal["service_account", "api_key", "system", - "unauthenticated", "unknown"] +"unauthenticated", "unknown"] @_dataclass.dataclass(frozen=True) @@ -106,10 +106,10 @@ class EventWithAuthContext(Event[_core.T]): def _firestore_endpoint_handler( - func: _C1 | _C2 | _C3 | _C4, - event_type: str, - document_pattern: _path_pattern.PathPattern, - raw: _ce.CloudEvent, + func: _C1 | _C2 | _C3 | _C4, + event_type: str, + document_pattern: _path_pattern.PathPattern, + raw: _ce.CloudEvent, ) -> None: event_attributes = raw._get_attributes() event_data: _typing.Any = raw.get_data() @@ -189,30 +189,29 @@ def _firestore_endpoint_handler( **document_pattern.extract_matches(event_document), } - common_event_kwargs = { - "project": event_project, - "namespace": event_namespace, - "database": event_database, - "location": event_location, - "document": event_document, - "specversion": event_attributes["specversion"], - "id": event_attributes["id"], - "source": event_attributes["source"], - "type": event_attributes["type"], - "time": event_time, - "data": firestore_event_data, - "subject": event_attributes["subject"], - "params": params, - } + database_event = Event( + project=event_project, + namespace=event_namespace, + database=event_database, + location=event_location, + document=event_document, + specversion=event_attributes["specversion"], + id=event_attributes["id"], + source=event_attributes["source"], + type=event_attributes["type"], + time=event_time, + data=firestore_event_data, + subject=event_attributes["subject"], + params=params, + ) if event_type.endswith(".withAuthContext"): database_event_with_auth_context = EventWithAuthContext( - **common_event_kwargs, + **vars(database_event), auth_type=event_auth_type, auth_id=event_auth_id) func(database_event_with_auth_context) else: - database_event = Event(**common_event_kwargs) # mypy cannot infer that the event type is correct, hence the cast _typing.cast(_C1 | _C2, func)(database_event) @@ -267,7 +266,7 @@ def on_document_written_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_written_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: + ) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is created, updated, or deleted in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -366,7 +365,7 @@ def on_document_updated_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_updated_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: + ) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is updated in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -465,7 +464,7 @@ def on_document_created_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_created_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: + ) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is created in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -564,7 +563,7 @@ def on_document_deleted_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_deleted_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: + ) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is deleted in Firestore. This trigger will also provide the authentication context of the principal who triggered From cff357e061d77a40b72aacb98de0aac906a6e26c Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 25 Mar 2024 18:46:43 +0530 Subject: [PATCH 08/14] feat: add test --- tests/test_firestore_fn.py | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/test_firestore_fn.py diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py new file mode 100644 index 0000000..ccb6da7 --- /dev/null +++ b/tests/test_firestore_fn.py @@ -0,0 +1,53 @@ +import json +from unittest import TestCase +from unittest.mock import MagicMock, Mock, patch + +mocked_modules = { + "google.cloud.firestore": MagicMock(), + "google.cloud.firestore_v1": MagicMock(), + "firebase_admin": MagicMock() +} + + +class TestFirestore(TestCase): + def test_firestore_endpoint_handler_calls_function_with_correct_args(self): + with patch.dict('sys.modules', mocked_modules): + from cloudevents.http import CloudEvent + from firebase_functions import firestore_fn + from firebase_functions.private import path_pattern + + func = Mock(__name__="example_func") + + event_type = firestore_fn._event_type_created_with_auth_context + document_pattern = path_pattern.PathPattern("foo/{bar}") + raw_event = CloudEvent( + attributes={ + "specversion": "1.0", + "type": event_type, + "source": "https://example.com/testevent", + "time": "2023-03-11T13:25:37.403Z", + "subject": "test_subject", + "datacontenttype": "application/json", + "location": "projects/project-id/databases/(default)/documents/foo/{bar}", + "project": "project-id", + "namespace": "(default)", + "document": "foo/{bar}", + "database": "projects/project-id/databases/(default)", + "authtype": "unauthenticated", + "authid": "foo" + }, + data=json.dumps({}) + ) + + firestore_fn._firestore_endpoint_handler(func=func, + event_type=event_type, + document_pattern=document_pattern, + raw=raw_event) + + func.assert_called_once() + + event = func.call_args.args[0] + self.assertIsNotNone(event) + self.assertIsInstance(event, firestore_fn.EventWithAuthContext) + self.assertEqual(event.auth_type, "unauthenticated") + self.assertEqual(event.auth_id, "foo") From 7481ddf8ebcd753ad449f41695f4689da578f815 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 25 Mar 2024 20:53:22 +0530 Subject: [PATCH 09/14] fix: lints --- src/firebase_functions/firestore_fn.py | 18 +++---- tests/test_firestore_fn.py | 73 ++++++++++++++++---------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 933275d..b337402 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -88,7 +88,7 @@ class Event(_core.CloudEvent[_core.T]): _C2 = _typing.Callable[[_E2], None] AuthType = _typing.Literal["service_account", "api_key", "system", -"unauthenticated", "unknown"] + "unauthenticated", "unknown"] @_dataclass.dataclass(frozen=True) @@ -106,10 +106,10 @@ class EventWithAuthContext(Event[_core.T]): def _firestore_endpoint_handler( - func: _C1 | _C2 | _C3 | _C4, - event_type: str, - document_pattern: _path_pattern.PathPattern, - raw: _ce.CloudEvent, + func: _C1 | _C2 | _C3 | _C4, + event_type: str, + document_pattern: _path_pattern.PathPattern, + raw: _ce.CloudEvent, ) -> None: event_attributes = raw._get_attributes() event_data: _typing.Any = raw.get_data() @@ -266,7 +266,7 @@ def on_document_written_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_written_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: + ) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is created, updated, or deleted in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -365,7 +365,7 @@ def on_document_updated_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_updated_with_auth_context(**kwargs - ) -> _typing.Callable[[_C1], _C1]: + ) -> _typing.Callable[[_C1], _C1]: """ Event handler that triggers when a document is updated in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -464,7 +464,7 @@ def on_document_created_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_created_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: + ) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is created in Firestore. This trigger will also provide the authentication context of the principal who triggered @@ -563,7 +563,7 @@ def on_document_deleted_wrapped(raw: _ce.CloudEvent): @_util.copy_func_kwargs(FirestoreOptions) def on_document_deleted_with_auth_context(**kwargs - ) -> _typing.Callable[[_C2], _C2]: + ) -> _typing.Callable[[_C2], _C2]: """ Event handler that triggers when a document is deleted in Firestore. This trigger will also provide the authentication context of the principal who triggered diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py index ccb6da7..8115488 100644 --- a/tests/test_firestore_fn.py +++ b/tests/test_firestore_fn.py @@ -1,3 +1,7 @@ +""" +This module contains tests for the firestore_fn module. +""" + import json from unittest import TestCase from unittest.mock import MagicMock, Mock, patch @@ -10,44 +14,59 @@ class TestFirestore(TestCase): + """ + firestore_fn tests. + """ + def test_firestore_endpoint_handler_calls_function_with_correct_args(self): - with patch.dict('sys.modules', mocked_modules): + with patch.dict("sys.modules", mocked_modules): from cloudevents.http import CloudEvent - from firebase_functions import firestore_fn + from firebase_functions.firestore_fn import _event_type_created_with_auth_context as event_type, \ + _firestore_endpoint_handler as firestore_endpoint_handler, EventWithAuthContext from firebase_functions.private import path_pattern func = Mock(__name__="example_func") - event_type = firestore_fn._event_type_created_with_auth_context document_pattern = path_pattern.PathPattern("foo/{bar}") - raw_event = CloudEvent( - attributes={ - "specversion": "1.0", - "type": event_type, - "source": "https://example.com/testevent", - "time": "2023-03-11T13:25:37.403Z", - "subject": "test_subject", - "datacontenttype": "application/json", - "location": "projects/project-id/databases/(default)/documents/foo/{bar}", - "project": "project-id", - "namespace": "(default)", - "document": "foo/{bar}", - "database": "projects/project-id/databases/(default)", - "authtype": "unauthenticated", - "authid": "foo" - }, - data=json.dumps({}) - ) - - firestore_fn._firestore_endpoint_handler(func=func, - event_type=event_type, - document_pattern=document_pattern, - raw=raw_event) + attributes = { + "specversion": + "1.0", + "type": + event_type, + "source": + "https://example.com/testevent", + "time": + "2023-03-11T13:25:37.403Z", + "subject": + "test_subject", + "datacontenttype": + "application/json", + "location": + "projects/project-id/databases/(default)/documents/foo/{bar}", + "project": + "project-id", + "namespace": + "(default)", + "document": + "foo/{bar}", + "database": + "projects/project-id/databases/(default)", + "authtype": + "unauthenticated", + "authid": + "foo" + } + raw_event = CloudEvent(attributes=attributes, data=json.dumps({})) + + firestore_endpoint_handler(func=func, + event_type=event_type, + document_pattern=document_pattern, + raw=raw_event) func.assert_called_once() event = func.call_args.args[0] self.assertIsNotNone(event) - self.assertIsInstance(event, firestore_fn.EventWithAuthContext) + self.assertIsInstance(event, EventWithAuthContext) self.assertEqual(event.auth_type, "unauthenticated") self.assertEqual(event.auth_id, "foo") From 3799007f8a8c80d13c286fd4bccdb90aaa353003 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Fri, 29 Mar 2024 12:10:13 +0530 Subject: [PATCH 10/14] feat: remove EventWithAuthContext and add additional fields to Event --- src/firebase_functions/firestore_fn.py | 62 ++++++++++++-------------- tests/test_firestore_fn.py | 4 +- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index b337402..79ac0df 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -43,6 +43,9 @@ _event_type_updated_with_auth_context = "google.cloud.firestore.document.v1.updated.withAuthContext" _event_type_deleted_with_auth_context = "google.cloud.firestore.document.v1.deleted.withAuthContext" +AuthType = _typing.Literal["service_account", "api_key", "system", + "unauthenticated", "unknown"] + @_dataclass.dataclass(frozen=True) class Event(_core.CloudEvent[_core.T]): @@ -75,6 +78,12 @@ class Event(_core.CloudEvent[_core.T]): The document path. """ + auth_type: _typing.Optional[AuthType] + """The type of principal that triggered the event""" + + auth_id: _typing.Optional[str] + """The unique identifier for the principal""" + params: dict[str, str] """ An dict containing the values of the path patterns. @@ -87,26 +96,9 @@ class Event(_core.CloudEvent[_core.T]): _C1 = _typing.Callable[[_E1], None] _C2 = _typing.Callable[[_E2], None] -AuthType = _typing.Literal["service_account", "api_key", "system", - "unauthenticated", "unknown"] - - -@_dataclass.dataclass(frozen=True) -class EventWithAuthContext(Event[_core.T]): - auth_type: AuthType - """The type of principal that triggered the event""" - auth_id: str - """The unique identifier for the principal""" - - -_E3 = EventWithAuthContext[Change[DocumentSnapshot | None]] -_E4 = EventWithAuthContext[DocumentSnapshot | None] -_C3 = _typing.Callable[[_E3], None] -_C4 = _typing.Callable[[_E4], None] - def _firestore_endpoint_handler( - func: _C1 | _C2 | _C3 | _C4, + func: _C1 | _C2, event_type: str, document_pattern: _path_pattern.PathPattern, raw: _ce.CloudEvent, @@ -203,17 +195,19 @@ def _firestore_endpoint_handler( data=firestore_event_data, subject=event_attributes["subject"], params=params, + auth_type=None, + auth_id=None, ) if event_type.endswith(".withAuthContext"): - database_event_with_auth_context = EventWithAuthContext( - **vars(database_event), - auth_type=event_auth_type, - auth_id=event_auth_id) - func(database_event_with_auth_context) - else: - # mypy cannot infer that the event type is correct, hence the cast - _typing.cast(_C1 | _C2, func)(database_event) + event_attrs = vars(database_event).copy() + event_attrs.update({ + "auth_type": event_auth_type, + "auth_id": event_auth_id, + }) + database_event = Event(**event_attrs) + + func(database_event) @_util.copy_func_kwargs(FirestoreOptions) @@ -277,13 +271,13 @@ def on_document_written_with_auth_context(**kwargs .. code-block:: python @on_document_written_with_auth_context(document="*") - def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: + def example(event: Event[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -376,13 +370,13 @@ def on_document_updated_with_auth_context(**kwargs .. code-block:: python @on_document_updated_with_auth_context(document="*") - def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: + def example(event: Event[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -475,13 +469,13 @@ def on_document_created_with_auth_context(**kwargs .. code-block:: python @on_document_created_with_auth_context(document="*") - def example(event: EventWithAuthContext[DocumentSnapshot]): + def example(event: Event[DocumentSnapshot]): pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -574,13 +568,13 @@ def on_document_deleted_with_auth_context(**kwargs .. code-block:: python @on_document_deleted_with_auth_context(document="*") - def example(event: EventWithAuthContext[DocumentSnapshot]) -> None: + def example(event: Event[DocumentSnapshot]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py index 8115488..0fc17ca 100644 --- a/tests/test_firestore_fn.py +++ b/tests/test_firestore_fn.py @@ -22,7 +22,7 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): with patch.dict("sys.modules", mocked_modules): from cloudevents.http import CloudEvent from firebase_functions.firestore_fn import _event_type_created_with_auth_context as event_type, \ - _firestore_endpoint_handler as firestore_endpoint_handler, EventWithAuthContext + _firestore_endpoint_handler as firestore_endpoint_handler, Event from firebase_functions.private import path_pattern func = Mock(__name__="example_func") @@ -67,6 +67,6 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): event = func.call_args.args[0] self.assertIsNotNone(event) - self.assertIsInstance(event, EventWithAuthContext) + self.assertIsInstance(event, Event) self.assertEqual(event.auth_type, "unauthenticated") self.assertEqual(event.auth_id, "foo") From 0f981e0c49d490405b33b2e914c4668e838eb50d Mon Sep 17 00:00:00 2001 From: exaby73 Date: Wed, 3 Apr 2024 09:18:53 +0530 Subject: [PATCH 11/14] Revert "feat: remove EventWithAuthContext and add additional fields to Event" This reverts commit 3799007f8a8c80d13c286fd4bccdb90aaa353003. --- src/firebase_functions/firestore_fn.py | 62 ++++++++++++++------------ tests/test_firestore_fn.py | 4 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 79ac0df..b337402 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -43,9 +43,6 @@ _event_type_updated_with_auth_context = "google.cloud.firestore.document.v1.updated.withAuthContext" _event_type_deleted_with_auth_context = "google.cloud.firestore.document.v1.deleted.withAuthContext" -AuthType = _typing.Literal["service_account", "api_key", "system", - "unauthenticated", "unknown"] - @_dataclass.dataclass(frozen=True) class Event(_core.CloudEvent[_core.T]): @@ -78,12 +75,6 @@ class Event(_core.CloudEvent[_core.T]): The document path. """ - auth_type: _typing.Optional[AuthType] - """The type of principal that triggered the event""" - - auth_id: _typing.Optional[str] - """The unique identifier for the principal""" - params: dict[str, str] """ An dict containing the values of the path patterns. @@ -96,9 +87,26 @@ class Event(_core.CloudEvent[_core.T]): _C1 = _typing.Callable[[_E1], None] _C2 = _typing.Callable[[_E2], None] +AuthType = _typing.Literal["service_account", "api_key", "system", + "unauthenticated", "unknown"] + + +@_dataclass.dataclass(frozen=True) +class EventWithAuthContext(Event[_core.T]): + auth_type: AuthType + """The type of principal that triggered the event""" + auth_id: str + """The unique identifier for the principal""" + + +_E3 = EventWithAuthContext[Change[DocumentSnapshot | None]] +_E4 = EventWithAuthContext[DocumentSnapshot | None] +_C3 = _typing.Callable[[_E3], None] +_C4 = _typing.Callable[[_E4], None] + def _firestore_endpoint_handler( - func: _C1 | _C2, + func: _C1 | _C2 | _C3 | _C4, event_type: str, document_pattern: _path_pattern.PathPattern, raw: _ce.CloudEvent, @@ -195,19 +203,17 @@ def _firestore_endpoint_handler( data=firestore_event_data, subject=event_attributes["subject"], params=params, - auth_type=None, - auth_id=None, ) if event_type.endswith(".withAuthContext"): - event_attrs = vars(database_event).copy() - event_attrs.update({ - "auth_type": event_auth_type, - "auth_id": event_auth_id, - }) - database_event = Event(**event_attrs) - - func(database_event) + database_event_with_auth_context = EventWithAuthContext( + **vars(database_event), + auth_type=event_auth_type, + auth_id=event_auth_id) + func(database_event_with_auth_context) + else: + # mypy cannot infer that the event type is correct, hence the cast + _typing.cast(_C1 | _C2, func)(database_event) @_util.copy_func_kwargs(FirestoreOptions) @@ -271,13 +277,13 @@ def on_document_written_with_auth_context(**kwargs .. code-block:: python @on_document_written_with_auth_context(document="*") - def example(event: Event[Change[DocumentSnapshot]]) -> None: + def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -370,13 +376,13 @@ def on_document_updated_with_auth_context(**kwargs .. code-block:: python @on_document_updated_with_auth_context(document="*") - def example(event: Event[Change[DocumentSnapshot]]) -> None: + def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -469,13 +475,13 @@ def on_document_created_with_auth_context(**kwargs .. code-block:: python @on_document_created_with_auth_context(document="*") - def example(event: Event[DocumentSnapshot]): + def example(event: EventWithAuthContext[DocumentSnapshot]): pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -568,13 +574,13 @@ def on_document_deleted_with_auth_context(**kwargs .. code-block:: python @on_document_deleted_with_auth_context(document="*") - def example(event: Event[DocumentSnapshot]) -> None: + def example(event: EventWithAuthContext[DocumentSnapshot]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.Event` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py index 0fc17ca..8115488 100644 --- a/tests/test_firestore_fn.py +++ b/tests/test_firestore_fn.py @@ -22,7 +22,7 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): with patch.dict("sys.modules", mocked_modules): from cloudevents.http import CloudEvent from firebase_functions.firestore_fn import _event_type_created_with_auth_context as event_type, \ - _firestore_endpoint_handler as firestore_endpoint_handler, Event + _firestore_endpoint_handler as firestore_endpoint_handler, EventWithAuthContext from firebase_functions.private import path_pattern func = Mock(__name__="example_func") @@ -67,6 +67,6 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): event = func.call_args.args[0] self.assertIsNotNone(event) - self.assertIsInstance(event, Event) + self.assertIsInstance(event, EventWithAuthContext) self.assertEqual(event.auth_type, "unauthenticated") self.assertEqual(event.auth_id, "foo") From dbf699c1473d04153229d5996d78f3aa375352f9 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Fri, 5 Apr 2024 23:59:18 +0530 Subject: [PATCH 12/14] feat: rename to AuthEvent --- src/firebase_functions/firestore_fn.py | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index b337402..0568bb7 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -92,15 +92,15 @@ class Event(_core.CloudEvent[_core.T]): @_dataclass.dataclass(frozen=True) -class EventWithAuthContext(Event[_core.T]): +class AuthEvent(Event[_core.T]): auth_type: AuthType """The type of principal that triggered the event""" auth_id: str """The unique identifier for the principal""" -_E3 = EventWithAuthContext[Change[DocumentSnapshot | None]] -_E4 = EventWithAuthContext[DocumentSnapshot | None] +_E3 = AuthEvent[Change[DocumentSnapshot | None]] +_E4 = AuthEvent[DocumentSnapshot | None] _C3 = _typing.Callable[[_E3], None] _C4 = _typing.Callable[[_E4], None] @@ -206,10 +206,9 @@ def _firestore_endpoint_handler( ) if event_type.endswith(".withAuthContext"): - database_event_with_auth_context = EventWithAuthContext( - **vars(database_event), - auth_type=event_auth_type, - auth_id=event_auth_id) + database_event_with_auth_context = AuthEvent(**vars(database_event), + auth_type=event_auth_type, + auth_id=event_auth_id) func(database_event_with_auth_context) else: # mypy cannot infer that the event type is correct, hence the cast @@ -277,13 +276,13 @@ def on_document_written_with_auth_context(**kwargs .. code-block:: python @on_document_written_with_auth_context(document="*") - def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: + def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -376,13 +375,13 @@ def on_document_updated_with_auth_context(**kwargs .. code-block:: python @on_document_updated_with_auth_context(document="*") - def example(event: EventWithAuthContext[Change[DocumentSnapshot]]) -> None: + def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[ :exc:`firebase_functions.db.Change` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -475,13 +474,13 @@ def on_document_created_with_auth_context(**kwargs .. code-block:: python @on_document_created_with_auth_context(document="*") - def example(event: EventWithAuthContext[DocumentSnapshot]): + def example(event: AuthEvent[DocumentSnapshot]): pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ @@ -574,13 +573,13 @@ def on_document_deleted_with_auth_context(**kwargs .. code-block:: python @on_document_deleted_with_auth_context(document="*") - def example(event: EventWithAuthContext[DocumentSnapshot]) -> None: + def example(event: AuthEvent[DocumentSnapshot]) -> None: pass :param \\*\\*kwargs: Firestore options. :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions` :rtype: :exc:`typing.Callable` - \\[ \\[ :exc:`firebase_functions.firestore_fn.EventWithAuthContext` \\[ + \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[ :exc:`object` \\] \\], `None` \\] A function that takes a Firestore event and returns ``None``. """ From 0361e85474b0cafb4fbf16d7047a43324f54e753 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Sat, 6 Apr 2024 00:01:10 +0530 Subject: [PATCH 13/14] feat: rename to AuthEvent in tests --- tests/test_firestore_fn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_firestore_fn.py b/tests/test_firestore_fn.py index 8115488..d6a65a5 100644 --- a/tests/test_firestore_fn.py +++ b/tests/test_firestore_fn.py @@ -22,7 +22,7 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): with patch.dict("sys.modules", mocked_modules): from cloudevents.http import CloudEvent from firebase_functions.firestore_fn import _event_type_created_with_auth_context as event_type, \ - _firestore_endpoint_handler as firestore_endpoint_handler, EventWithAuthContext + _firestore_endpoint_handler as firestore_endpoint_handler, AuthEvent from firebase_functions.private import path_pattern func = Mock(__name__="example_func") @@ -67,6 +67,6 @@ def test_firestore_endpoint_handler_calls_function_with_correct_args(self): event = func.call_args.args[0] self.assertIsNotNone(event) - self.assertIsInstance(event, EventWithAuthContext) + self.assertIsInstance(event, AuthEvent) self.assertEqual(event.auth_type, "unauthenticated") self.assertEqual(event.auth_id, "foo") From e9d5140a9106ebe0ce4fd4353085cf58d3efba5b Mon Sep 17 00:00:00 2001 From: exaby73 Date: Mon, 8 Apr 2024 05:40:52 +0530 Subject: [PATCH 14/14] feat: make auth_id nullable to sync it with the Node implementation --- src/firebase_functions/firestore_fn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firebase_functions/firestore_fn.py b/src/firebase_functions/firestore_fn.py index 0568bb7..c75c288 100644 --- a/src/firebase_functions/firestore_fn.py +++ b/src/firebase_functions/firestore_fn.py @@ -95,7 +95,7 @@ class Event(_core.CloudEvent[_core.T]): class AuthEvent(Event[_core.T]): auth_type: AuthType """The type of principal that triggered the event""" - auth_id: str + auth_id: str | None """The unique identifier for the principal"""