firebase-functions 0.2.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/PKG-INFO +1 -1
  2. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/setup.py +2 -0
  3. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/__init__.py +1 -1
  4. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/alerts_fn.py +2 -2
  5. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/core.py +45 -0
  6. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/db_fn.py +1 -1
  7. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/eventarc_fn.py +2 -2
  8. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/firestore_fn.py +257 -12
  9. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/https_fn.py +20 -9
  10. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/logger.py +3 -0
  11. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/options.py +15 -0
  12. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/params.py +44 -1
  13. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/_alerts_fn.py +13 -2
  14. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/_identity_fn.py +5 -4
  15. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/manifest.py +8 -3
  16. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/path_pattern.py +4 -1
  17. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/util.py +51 -13
  18. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/pubsub_fn.py +2 -2
  19. firebase_functions-0.4.0/src/firebase_functions/py.typed +0 -0
  20. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/remote_config_fn.py +8 -2
  21. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/scheduler_fn.py +2 -1
  22. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/storage_fn.py +2 -2
  23. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/tasks_fn.py +4 -1
  24. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/test_lab_fn.py +8 -2
  25. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions.egg-info/PKG-INFO +1 -1
  26. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions.egg-info/SOURCES.txt +7 -0
  27. firebase_functions-0.4.0/tests/test_db.py +43 -0
  28. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_eventarc_fn.py +34 -0
  29. firebase_functions-0.4.0/tests/test_firestore_fn.py +123 -0
  30. firebase_functions-0.4.0/tests/test_https_fn.py +72 -0
  31. firebase_functions-0.4.0/tests/test_identity_fn.py +64 -0
  32. firebase_functions-0.4.0/tests/test_init.py +23 -0
  33. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_manifest.py +25 -27
  34. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_params.py +36 -0
  35. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_pubsub_fn.py +35 -0
  36. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_scheduler_fn.py +19 -1
  37. firebase_functions-0.4.0/tests/test_storage_fn.py +43 -0
  38. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_tasks_fn.py +65 -1
  39. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_test_lab_fn.py +47 -1
  40. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_util.py +9 -1
  41. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/LICENSE +0 -0
  42. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/README.md +0 -0
  43. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/pyproject.toml +0 -0
  44. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/setup.cfg +0 -0
  45. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/alerts/__init__.py +0 -0
  46. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/alerts/app_distribution_fn.py +0 -0
  47. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/alerts/billing_fn.py +0 -0
  48. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/alerts/crashlytics_fn.py +0 -0
  49. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/alerts/performance_fn.py +0 -0
  50. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/identity_fn.py +0 -0
  51. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/__init__.py +0 -0
  52. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/serving.py +0 -0
  53. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions/private/token_verifier.py +0 -0
  54. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions.egg-info/dependency_links.txt +0 -0
  55. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions.egg-info/requires.txt +0 -0
  56. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/src/firebase_functions.egg-info/top_level.txt +0 -0
  57. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_logger.py +0 -0
  58. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_options.py +0 -0
  59. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_path_pattern.py +0 -0
  60. {firebase_functions-0.2.0 → firebase_functions-0.4.0}/tests/test_remote_config_fn.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: firebase_functions
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Firebase Functions Python SDK
5
5
  Home-page: https://github.com/firebase/firebase-functions-python
6
6
  Author: Firebase Team
@@ -56,6 +56,8 @@ setup(
56
56
  extras_require={'dev': dev_requires},
57
57
  packages=find_packages(where='src'),
58
58
  package_dir={'': 'src'},
59
+ include_package_data=True,
60
+ package_data={'firebase_functions': ['py.typed']},
59
61
  python_requires='>=3.10',
60
62
  classifiers=[
61
63
  'Development Status :: 4 - Beta',
@@ -15,4 +15,4 @@
15
15
  Firebase Functions for Python.
16
16
  """
17
17
 
18
- __version__ = "0.2.0"
18
+ __version__ = "0.4.0"
@@ -24,7 +24,7 @@ from firebase_functions.alerts import FirebaseAlertData
24
24
 
25
25
  import firebase_functions.private.util as _util
26
26
 
27
- from firebase_functions.core import T, CloudEvent as _CloudEvent
27
+ from firebase_functions.core import T, CloudEvent as _CloudEvent, _with_init
28
28
  from firebase_functions.options import FirebaseAlertOptions
29
29
 
30
30
  # Explicitly import AlertType to make it available in the public API.
@@ -95,7 +95,7 @@ def on_alert_published(
95
95
  @_functools.wraps(func)
96
96
  def on_alert_published_wrapped(raw: _ce.CloudEvent):
97
97
  from firebase_functions.private._alerts_fn import alerts_event_from_ce
98
- func(alerts_event_from_ce(raw))
98
+ _with_init(func)(alerts_event_from_ce(raw))
99
99
 
100
100
  _util.set_func_endpoint_attr(
101
101
  on_alert_published_wrapped,
@@ -18,6 +18,8 @@ import dataclasses as _dataclass
18
18
  import datetime as _datetime
19
19
  import typing as _typing
20
20
 
21
+ from . import logger as _logger
22
+
21
23
  T = _typing.TypeVar("T")
22
24
 
23
25
 
@@ -80,3 +82,46 @@ class Change(_typing.Generic[T]):
80
82
  """
81
83
  The state of data after the change.
82
84
  """
85
+
86
+
87
+ _did_init = False
88
+ _init_callback: _typing.Callable[[], _typing.Any] | None = None
89
+
90
+
91
+ def init(callback: _typing.Callable[[], _typing.Any]) -> None:
92
+ """
93
+ Registers a function that should be run when in a production environment
94
+ before executing any functions code.
95
+ Calling this decorator more than once leads to undefined behavior.
96
+ """
97
+
98
+ global _did_init
99
+ global _init_callback
100
+
101
+ if _did_init:
102
+ _logger.warn(
103
+ "Setting init callback more than once. Only the most recent callback will be called"
104
+ )
105
+
106
+ _init_callback = callback
107
+ _did_init = False
108
+
109
+
110
+ def _with_init(
111
+ fn: _typing.Callable[...,
112
+ _typing.Any]) -> _typing.Callable[..., _typing.Any]:
113
+ """
114
+ A decorator that runs the init callback before running the decorated function.
115
+ """
116
+
117
+ def wrapper(*args, **kwargs):
118
+ global _did_init
119
+
120
+ if not _did_init:
121
+ if _init_callback is not None:
122
+ _init_callback()
123
+ _did_init = True
124
+
125
+ return fn(*args, **kwargs)
126
+
127
+ return wrapper
@@ -119,7 +119,7 @@ def _db_endpoint_handler(
119
119
  subject=event_attributes["subject"],
120
120
  params=params,
121
121
  )
122
- func(database_event)
122
+ _core._with_init(func)(database_event)
123
123
 
124
124
 
125
125
  @_util.copy_func_kwargs(DatabaseOptions)
@@ -21,7 +21,7 @@ import cloudevents.http as _ce
21
21
 
22
22
  import firebase_functions.options as _options
23
23
  import firebase_functions.private.util as _util
24
- from firebase_functions.core import CloudEvent
24
+ from firebase_functions.core import CloudEvent, _with_init
25
25
 
26
26
 
27
27
  @_util.copy_func_kwargs(_options.EventarcTriggerOptions)
@@ -73,7 +73,7 @@ def on_custom_event_published(
73
73
  ),
74
74
  type=event_dict["type"],
75
75
  )
76
- func(event)
76
+ _with_init(func)(event)
77
77
 
78
78
  _util.set_func_endpoint_attr(
79
79
  on_custom_event_published_wrapped,
@@ -38,6 +38,11 @@ _event_type_created = "google.cloud.firestore.document.v1.created"
38
38
  _event_type_updated = "google.cloud.firestore.document.v1.updated"
39
39
  _event_type_deleted = "google.cloud.firestore.document.v1.deleted"
40
40
 
41
+ _event_type_written_with_auth_context = "google.cloud.firestore.document.v1.written.withAuthContext"
42
+ _event_type_created_with_auth_context = "google.cloud.firestore.document.v1.created.withAuthContext"
43
+ _event_type_updated_with_auth_context = "google.cloud.firestore.document.v1.updated.withAuthContext"
44
+ _event_type_deleted_with_auth_context = "google.cloud.firestore.document.v1.deleted.withAuthContext"
45
+
41
46
 
42
47
  @_dataclass.dataclass(frozen=True)
43
48
  class Event(_core.CloudEvent[_core.T]):
@@ -82,9 +87,26 @@ _E2 = Event[DocumentSnapshot | None]
82
87
  _C1 = _typing.Callable[[_E1], None]
83
88
  _C2 = _typing.Callable[[_E2], None]
84
89
 
90
+ AuthType = _typing.Literal["service_account", "api_key", "system",
91
+ "unauthenticated", "unknown"]
92
+
93
+
94
+ @_dataclass.dataclass(frozen=True)
95
+ class AuthEvent(Event[_core.T]):
96
+ auth_type: AuthType
97
+ """The type of principal that triggered the event"""
98
+ auth_id: str | None
99
+ """The unique identifier for the principal"""
100
+
101
+
102
+ _E3 = AuthEvent[Change[DocumentSnapshot | None]]
103
+ _E4 = AuthEvent[DocumentSnapshot | None]
104
+ _C3 = _typing.Callable[[_E3], None]
105
+ _C4 = _typing.Callable[[_E4], None]
106
+
85
107
 
86
108
  def _firestore_endpoint_handler(
87
- func: _C1 | _C2,
109
+ func: _C1 | _C2 | _C3 | _C4,
88
110
  event_type: str,
89
111
  document_pattern: _path_pattern.PathPattern,
90
112
  raw: _ce.CloudEvent,
@@ -94,12 +116,14 @@ def _firestore_endpoint_handler(
94
116
  firestore_event_data: _firestore.DocumentEventData
95
117
  content_type: str = event_attributes["datacontenttype"]
96
118
  if "application/json" in content_type or isinstance(event_data, dict):
97
- firestore_event_data = _firestore.DocumentEventData.from_json(
98
- event_data)
119
+ firestore_event_data = _typing.cast(
120
+ _firestore.DocumentEventData,
121
+ _firestore.DocumentEventData.from_json(event_data))
99
122
  elif "application/protobuf" in content_type or isinstance(
100
123
  event_data, bytes):
101
- firestore_event_data = _firestore.DocumentEventData.deserialize(
102
- event_data)
124
+ firestore_event_data = _typing.cast(
125
+ _firestore.DocumentEventData,
126
+ _firestore.DocumentEventData.deserialize(event_data))
103
127
  else:
104
128
  actual_type = type(event_data)
105
129
  raise TypeError(f"Firestore: Cannot parse event payload of data type "
@@ -110,6 +134,8 @@ def _firestore_endpoint_handler(
110
134
  event_namespace = event_attributes["namespace"]
111
135
  event_document = event_attributes["document"]
112
136
  event_database = event_attributes["database"]
137
+ event_auth_type = event_attributes["authtype"]
138
+ event_auth_id = event_attributes["authid"]
113
139
 
114
140
  time = event_attributes["time"]
115
141
  event_time = _util.timestamp_conversion(time)
@@ -146,18 +172,23 @@ def _firestore_endpoint_handler(
146
172
  firestore_event_data.old_value.update_time,
147
173
  )
148
174
  if event_type == _event_type_deleted:
149
- firestore_event_data = old_value_snapshot
175
+ firestore_event_data = _typing.cast(_firestore.DocumentEventData,
176
+ old_value_snapshot)
150
177
  if event_type == _event_type_created:
151
- firestore_event_data = value_snapshot
178
+ firestore_event_data = _typing.cast(_firestore.DocumentEventData,
179
+ value_snapshot)
152
180
  if event_type in (_event_type_written, _event_type_updated):
153
- firestore_event_data = Change(
154
- before=old_value_snapshot,
155
- after=value_snapshot,
156
- )
181
+ firestore_event_data = _typing.cast(
182
+ _firestore.DocumentEventData,
183
+ Change(
184
+ before=old_value_snapshot,
185
+ after=value_snapshot,
186
+ ))
157
187
 
158
188
  params: dict[str, str] = {
159
189
  **document_pattern.extract_matches(event_document),
160
190
  }
191
+
161
192
  database_event = Event(
162
193
  project=event_project,
163
194
  namespace=event_namespace,
@@ -173,7 +204,17 @@ def _firestore_endpoint_handler(
173
204
  subject=event_attributes["subject"],
174
205
  params=params,
175
206
  )
176
- func(database_event)
207
+
208
+ func = _core._with_init(func)
209
+
210
+ if event_type.endswith(".withAuthContext"):
211
+ database_event_with_auth_context = AuthEvent(**vars(database_event),
212
+ auth_type=event_auth_type,
213
+ auth_id=event_auth_id)
214
+ func(database_event_with_auth_context)
215
+ else:
216
+ # mypy cannot infer that the event type is correct, hence the cast
217
+ _typing.cast(_C1 | _C2, func)(database_event)
177
218
 
178
219
 
179
220
  @_util.copy_func_kwargs(FirestoreOptions)
@@ -224,6 +265,57 @@ def on_document_written(**kwargs) -> _typing.Callable[[_C1], _C1]:
224
265
  return on_document_written_inner_decorator
225
266
 
226
267
 
268
+ @_util.copy_func_kwargs(FirestoreOptions)
269
+ def on_document_written_with_auth_context(**kwargs
270
+ ) -> _typing.Callable[[_C1], _C1]:
271
+ """
272
+ Event handler that triggers when a document is created, updated, or deleted in Firestore.
273
+ This trigger will also provide the authentication context of the principal who triggered
274
+ the event.
275
+
276
+ Example:
277
+
278
+ .. code-block:: python
279
+
280
+ @on_document_written_with_auth_context(document="*")
281
+ def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None:
282
+ pass
283
+
284
+ :param \\*\\*kwargs: Firestore options.
285
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
286
+ :rtype: :exc:`typing.Callable`
287
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
288
+ :exc:`firebase_functions.db.Change` \\] \\], `None` \\]
289
+ A function that takes a Firestore event and returns ``None``.
290
+ """
291
+ options = FirestoreOptions(**kwargs)
292
+
293
+ def on_document_written_with_auth_context_inner_decorator(func: _C1):
294
+ document_pattern = _path_pattern.PathPattern(
295
+ _util.normalize_path(options.document))
296
+
297
+ @_functools.wraps(func)
298
+ def on_document_written_with_auth_context_wrapped(raw: _ce.CloudEvent):
299
+ return _firestore_endpoint_handler(
300
+ func,
301
+ _event_type_written_with_auth_context,
302
+ document_pattern,
303
+ raw,
304
+ )
305
+
306
+ _util.set_func_endpoint_attr(
307
+ on_document_written_with_auth_context_wrapped,
308
+ options._endpoint(
309
+ event_type=_event_type_written,
310
+ func_name=func.__name__,
311
+ document_pattern=document_pattern,
312
+ ),
313
+ )
314
+ return on_document_written_with_auth_context_wrapped
315
+
316
+ return on_document_written_with_auth_context_inner_decorator
317
+
318
+
227
319
  @_util.copy_func_kwargs(FirestoreOptions)
228
320
  def on_document_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
229
321
  """
@@ -272,6 +364,57 @@ def on_document_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
272
364
  return on_document_updated_inner_decorator
273
365
 
274
366
 
367
+ @_util.copy_func_kwargs(FirestoreOptions)
368
+ def on_document_updated_with_auth_context(**kwargs
369
+ ) -> _typing.Callable[[_C1], _C1]:
370
+ """
371
+ Event handler that triggers when a document is updated in Firestore.
372
+ This trigger will also provide the authentication context of the principal who triggered
373
+ the event.
374
+
375
+ Example:
376
+
377
+ .. code-block:: python
378
+
379
+ @on_document_updated_with_auth_context(document="*")
380
+ def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None:
381
+ pass
382
+
383
+ :param \\*\\*kwargs: Firestore options.
384
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
385
+ :rtype: :exc:`typing.Callable`
386
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
387
+ :exc:`firebase_functions.db.Change` \\] \\], `None` \\]
388
+ A function that takes a Firestore event and returns ``None``.
389
+ """
390
+ options = FirestoreOptions(**kwargs)
391
+
392
+ def on_document_updated_with_auth_context_inner_decorator(func: _C1):
393
+ document_pattern = _path_pattern.PathPattern(
394
+ _util.normalize_path(options.document))
395
+
396
+ @_functools.wraps(func)
397
+ def on_document_updated_with_auth_context_wrapped(raw: _ce.CloudEvent):
398
+ return _firestore_endpoint_handler(
399
+ func,
400
+ _event_type_updated_with_auth_context,
401
+ document_pattern,
402
+ raw,
403
+ )
404
+
405
+ _util.set_func_endpoint_attr(
406
+ on_document_updated_with_auth_context_wrapped,
407
+ options._endpoint(
408
+ event_type=_event_type_updated_with_auth_context,
409
+ func_name=func.__name__,
410
+ document_pattern=document_pattern,
411
+ ),
412
+ )
413
+ return on_document_updated_with_auth_context_wrapped
414
+
415
+ return on_document_updated_with_auth_context_inner_decorator
416
+
417
+
275
418
  @_util.copy_func_kwargs(FirestoreOptions)
276
419
  def on_document_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
277
420
  """
@@ -320,6 +463,57 @@ def on_document_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
320
463
  return on_document_created_inner_decorator
321
464
 
322
465
 
466
+ @_util.copy_func_kwargs(FirestoreOptions)
467
+ def on_document_created_with_auth_context(**kwargs
468
+ ) -> _typing.Callable[[_C2], _C2]:
469
+ """
470
+ Event handler that triggers when a document is created in Firestore.
471
+ This trigger will also provide the authentication context of the principal who triggered
472
+ the event.
473
+
474
+ Example:
475
+
476
+ .. code-block:: python
477
+
478
+ @on_document_created_with_auth_context(document="*")
479
+ def example(event: AuthEvent[DocumentSnapshot]):
480
+ pass
481
+
482
+ :param \\*\\*kwargs: Firestore options.
483
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
484
+ :rtype: :exc:`typing.Callable`
485
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
486
+ :exc:`object` \\] \\], `None` \\]
487
+ A function that takes a Firestore event and returns ``None``.
488
+ """
489
+ options = FirestoreOptions(**kwargs)
490
+
491
+ def on_document_created_with_auth_context_inner_decorator(func: _C2):
492
+ document_pattern = _path_pattern.PathPattern(
493
+ _util.normalize_path(options.document))
494
+
495
+ @_functools.wraps(func)
496
+ def on_document_created_with_auth_context_wrapped(raw: _ce.CloudEvent):
497
+ return _firestore_endpoint_handler(
498
+ func,
499
+ _event_type_created_with_auth_context,
500
+ document_pattern,
501
+ raw,
502
+ )
503
+
504
+ _util.set_func_endpoint_attr(
505
+ on_document_created_with_auth_context_wrapped,
506
+ options._endpoint(
507
+ event_type=_event_type_created_with_auth_context,
508
+ func_name=func.__name__,
509
+ document_pattern=document_pattern,
510
+ ),
511
+ )
512
+ return on_document_created_with_auth_context_wrapped
513
+
514
+ return on_document_created_with_auth_context_inner_decorator
515
+
516
+
323
517
  @_util.copy_func_kwargs(FirestoreOptions)
324
518
  def on_document_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]:
325
519
  """
@@ -366,3 +560,54 @@ def on_document_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]:
366
560
  return on_document_deleted_wrapped
367
561
 
368
562
  return on_document_deleted_inner_decorator
563
+
564
+
565
+ @_util.copy_func_kwargs(FirestoreOptions)
566
+ def on_document_deleted_with_auth_context(**kwargs
567
+ ) -> _typing.Callable[[_C2], _C2]:
568
+ """
569
+ Event handler that triggers when a document is deleted in Firestore.
570
+ This trigger will also provide the authentication context of the principal who triggered
571
+ the event.
572
+
573
+ Example:
574
+
575
+ .. code-block:: python
576
+
577
+ @on_document_deleted_with_auth_context(document="*")
578
+ def example(event: AuthEvent[DocumentSnapshot]) -> None:
579
+ pass
580
+
581
+ :param \\*\\*kwargs: Firestore options.
582
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
583
+ :rtype: :exc:`typing.Callable`
584
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
585
+ :exc:`object` \\] \\], `None` \\]
586
+ A function that takes a Firestore event and returns ``None``.
587
+ """
588
+ options = FirestoreOptions(**kwargs)
589
+
590
+ def on_document_deleted_with_auth_context_inner_decorator(func: _C2):
591
+ document_pattern = _path_pattern.PathPattern(
592
+ _util.normalize_path(options.document))
593
+
594
+ @_functools.wraps(func)
595
+ def on_document_deleted_with_auth_context_wrapped(raw: _ce.CloudEvent):
596
+ return _firestore_endpoint_handler(
597
+ func,
598
+ _event_type_deleted_with_auth_context,
599
+ document_pattern,
600
+ raw,
601
+ )
602
+
603
+ _util.set_func_endpoint_attr(
604
+ on_document_deleted_with_auth_context_wrapped,
605
+ options._endpoint(
606
+ event_type=_event_type_deleted_with_auth_context,
607
+ func_name=func.__name__,
608
+ document_pattern=document_pattern,
609
+ ),
610
+ )
611
+ return on_document_deleted_with_auth_context_wrapped
612
+
613
+ return on_document_deleted_with_auth_context_inner_decorator
@@ -135,6 +135,9 @@ class FunctionsErrorCode(str, _enum.Enum):
135
135
  Unrecoverable data loss or corruption.
136
136
  """
137
137
 
138
+ def __str__(self) -> str:
139
+ return self.value
140
+
138
141
 
139
142
  class _CanonicalErrorCodeName(str, _enum.Enum):
140
143
  """The canonical error code name for a given error code."""
@@ -157,6 +160,9 @@ class _CanonicalErrorCodeName(str, _enum.Enum):
157
160
  UNAVAILABLE = "UNAVAILABLE"
158
161
  DATA_LOSS = "DATA_LOSS"
159
162
 
163
+ def __str__(self) -> str:
164
+ return self.value
165
+
160
166
 
161
167
  @_dataclasses.dataclass(frozen=True)
162
168
  class _HttpErrorCode:
@@ -280,7 +286,7 @@ class AuthData:
280
286
  The interface for Auth tokens verified in Callable functions
281
287
  """
282
288
 
283
- uid: str
289
+ uid: str | None
284
290
  """
285
291
  User ID of the ID token.
286
292
  """
@@ -346,8 +352,10 @@ _C1 = _typing.Callable[[Request], Response]
346
352
  _C2 = _typing.Callable[[CallableRequest[_typing.Any]], _typing.Any]
347
353
 
348
354
 
349
- def _on_call_handler(func: _C2, request: Request,
350
- enforce_app_check: bool) -> Response:
355
+ def _on_call_handler(func: _C2,
356
+ request: Request,
357
+ enforce_app_check: bool,
358
+ verify_token: bool = True) -> Response:
351
359
  try:
352
360
  if not _util.valid_on_call_request(request):
353
361
  _logging.error("Invalid request, unable to process.")
@@ -357,7 +365,8 @@ def _on_call_handler(func: _C2, request: Request,
357
365
  data=_json.loads(request.data)["data"],
358
366
  )
359
367
 
360
- token_status = _util.on_call_check_tokens(request)
368
+ token_status = _util.on_call_check_tokens(request,
369
+ verify_token=verify_token)
361
370
 
362
371
  if token_status.auth == _util.OnCallTokenState.INVALID:
363
372
  raise HttpsError(FunctionsErrorCode.UNAUTHENTICATED,
@@ -377,8 +386,10 @@ def _on_call_handler(func: _C2, request: Request,
377
386
  if token_status.auth_token is not None:
378
387
  context = _dataclasses.replace(
379
388
  context,
380
- auth=AuthData(token_status.auth_token["uid"],
381
- token_status.auth_token),
389
+ auth=AuthData(
390
+ token_status.auth_token["uid"]
391
+ if "uid" in token_status.auth_token else None,
392
+ token_status.auth_token),
382
393
  )
383
394
 
384
395
  instance_id = request.headers.get("Firebase-Instance-ID-Token")
@@ -392,14 +403,14 @@ def _on_call_handler(func: _C2, request: Request,
392
403
  instance_id_token=request.headers.get(
393
404
  "Firebase-Instance-ID-Token"),
394
405
  )
395
- result = func(context)
406
+ result = _core._with_init(func)(context)
396
407
  return _jsonify(result=result)
397
408
  # Disable broad exceptions lint since we want to handle all exceptions here
398
409
  # and wrap as an HttpsError.
399
410
  # pylint: disable=broad-except
400
411
  except Exception as err:
401
412
  if not isinstance(err, HttpsError):
402
- _logging.error("Unhandled error", err)
413
+ _logging.error("Unhandled error: %s", err)
403
414
  err = HttpsError(FunctionsErrorCode.INTERNAL, "INTERNAL")
404
415
  status = err._http_error_code.status
405
416
  return _make_response(_jsonify(error=err._as_dict()), status)
@@ -436,7 +447,7 @@ def on_request(**kwargs) -> _typing.Callable[[_C1], _C1]:
436
447
  methods=options.cors.cors_methods,
437
448
  origins=options.cors.cors_origins,
438
449
  )(func)(request)
439
- return func(request)
450
+ return _core._with_init(func)(request)
440
451
 
441
452
  _util.set_func_endpoint_attr(
442
453
  on_request_wrapped,
@@ -24,6 +24,9 @@ class LogSeverity(str, _enum.Enum):
24
24
  ALERT = "ALERT"
25
25
  EMERGENCY = "EMERGENCY"
26
26
 
27
+ def __str__(self) -> str:
28
+ return self.value
29
+
27
30
 
28
31
  class LogEntry(_typing.TypedDict):
29
32
  """
@@ -41,6 +41,9 @@ class VpcEgressSetting(str, _enum.Enum):
41
41
  PRIVATE_RANGES_ONLY = "PRIVATE_RANGES_ONLY"
42
42
  ALL_TRAFFIC = "ALL_TRAFFIC"
43
43
 
44
+ def __str__(self) -> str:
45
+ return self.value
46
+
44
47
 
45
48
  class IngressSetting(str, _enum.Enum):
46
49
  """What kind of traffic can access the function."""
@@ -49,6 +52,9 @@ class IngressSetting(str, _enum.Enum):
49
52
  ALLOW_INTERNAL_ONLY = "ALLOW_INTERNAL_ONLY"
50
53
  ALLOW_INTERNAL_AND_GCLB = "ALLOW_INTERNAL_AND_GCLB"
51
54
 
55
+ def __str__(self) -> str:
56
+ return self.value
57
+
52
58
 
53
59
  @_dataclasses.dataclass(frozen=True)
54
60
  class CorsOptions:
@@ -88,6 +94,9 @@ class MemoryOption(int, _enum.Enum):
88
94
  GB_16 = 16 << 10
89
95
  GB_32 = 32 << 10
90
96
 
97
+ def __str__(self) -> str:
98
+ return f"{self.value}MB"
99
+
91
100
 
92
101
  class SupportedRegion(str, _enum.Enum):
93
102
  """
@@ -120,6 +129,9 @@ class SupportedRegion(str, _enum.Enum):
120
129
  US_WEST3 = "us-west3"
121
130
  US_WEST4 = "us-west4"
122
131
 
132
+ def __str__(self) -> str:
133
+ return self.value
134
+
123
135
 
124
136
  @_dataclasses.dataclass(frozen=True)
125
137
  class RateLimits():
@@ -587,6 +599,9 @@ class AlertType(str, _enum.Enum):
587
599
  Performance threshold alerts.
588
600
  """
589
601
 
602
+ def __str__(self) -> str:
603
+ return self.value
604
+
590
605
 
591
606
  @_dataclasses.dataclass(frozen=True, kw_only=True)
592
607
  class FirebaseAlertOptions(EventHandlerOptions):