firebase-functions 0.2.0__tar.gz → 0.3.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 (55) hide show
  1. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/PKG-INFO +1 -1
  2. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/setup.py +2 -0
  3. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/__init__.py +1 -1
  4. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/firestore_fn.py +255 -12
  5. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/https_fn.py +18 -7
  6. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/logger.py +3 -0
  7. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/options.py +15 -0
  8. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/params.py +44 -1
  9. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/_alerts_fn.py +13 -2
  10. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/manifest.py +8 -3
  11. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/path_pattern.py +4 -1
  12. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/util.py +51 -13
  13. firebase_functions-0.3.0/src/firebase_functions/py.typed +0 -0
  14. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/remote_config_fn.py +6 -0
  15. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/tasks_fn.py +4 -1
  16. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/test_lab_fn.py +6 -0
  17. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions.egg-info/PKG-INFO +1 -1
  18. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions.egg-info/SOURCES.txt +2 -0
  19. firebase_functions-0.3.0/tests/test_firestore_fn.py +72 -0
  20. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_manifest.py +25 -27
  21. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_params.py +36 -0
  22. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_tasks_fn.py +35 -0
  23. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_util.py +9 -1
  24. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/LICENSE +0 -0
  25. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/README.md +0 -0
  26. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/pyproject.toml +0 -0
  27. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/setup.cfg +0 -0
  28. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/alerts/__init__.py +0 -0
  29. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/alerts/app_distribution_fn.py +0 -0
  30. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/alerts/billing_fn.py +0 -0
  31. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/alerts/crashlytics_fn.py +0 -0
  32. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/alerts/performance_fn.py +0 -0
  33. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/alerts_fn.py +0 -0
  34. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/core.py +0 -0
  35. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/db_fn.py +0 -0
  36. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/eventarc_fn.py +0 -0
  37. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/identity_fn.py +0 -0
  38. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/__init__.py +0 -0
  39. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/_identity_fn.py +0 -0
  40. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/serving.py +0 -0
  41. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/private/token_verifier.py +0 -0
  42. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/pubsub_fn.py +0 -0
  43. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/scheduler_fn.py +0 -0
  44. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions/storage_fn.py +0 -0
  45. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions.egg-info/dependency_links.txt +0 -0
  46. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions.egg-info/requires.txt +0 -0
  47. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/src/firebase_functions.egg-info/top_level.txt +0 -0
  48. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_eventarc_fn.py +0 -0
  49. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_logger.py +0 -0
  50. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_options.py +0 -0
  51. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_path_pattern.py +0 -0
  52. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_pubsub_fn.py +0 -0
  53. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_remote_config_fn.py +0 -0
  54. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_scheduler_fn.py +0 -0
  55. {firebase_functions-0.2.0 → firebase_functions-0.3.0}/tests/test_test_lab_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.3.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.3.0"
@@ -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,15 @@ def _firestore_endpoint_handler(
173
204
  subject=event_attributes["subject"],
174
205
  params=params,
175
206
  )
176
- func(database_event)
207
+
208
+ if event_type.endswith(".withAuthContext"):
209
+ database_event_with_auth_context = AuthEvent(**vars(database_event),
210
+ auth_type=event_auth_type,
211
+ auth_id=event_auth_id)
212
+ func(database_event_with_auth_context)
213
+ else:
214
+ # mypy cannot infer that the event type is correct, hence the cast
215
+ _typing.cast(_C1 | _C2, func)(database_event)
177
216
 
178
217
 
179
218
  @_util.copy_func_kwargs(FirestoreOptions)
@@ -224,6 +263,57 @@ def on_document_written(**kwargs) -> _typing.Callable[[_C1], _C1]:
224
263
  return on_document_written_inner_decorator
225
264
 
226
265
 
266
+ @_util.copy_func_kwargs(FirestoreOptions)
267
+ def on_document_written_with_auth_context(**kwargs
268
+ ) -> _typing.Callable[[_C1], _C1]:
269
+ """
270
+ Event handler that triggers when a document is created, updated, or deleted in Firestore.
271
+ This trigger will also provide the authentication context of the principal who triggered
272
+ the event.
273
+
274
+ Example:
275
+
276
+ .. code-block:: python
277
+
278
+ @on_document_written_with_auth_context(document="*")
279
+ def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None:
280
+ pass
281
+
282
+ :param \\*\\*kwargs: Firestore options.
283
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
284
+ :rtype: :exc:`typing.Callable`
285
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
286
+ :exc:`firebase_functions.db.Change` \\] \\], `None` \\]
287
+ A function that takes a Firestore event and returns ``None``.
288
+ """
289
+ options = FirestoreOptions(**kwargs)
290
+
291
+ def on_document_written_with_auth_context_inner_decorator(func: _C1):
292
+ document_pattern = _path_pattern.PathPattern(
293
+ _util.normalize_path(options.document))
294
+
295
+ @_functools.wraps(func)
296
+ def on_document_written_with_auth_context_wrapped(raw: _ce.CloudEvent):
297
+ return _firestore_endpoint_handler(
298
+ func,
299
+ _event_type_written_with_auth_context,
300
+ document_pattern,
301
+ raw,
302
+ )
303
+
304
+ _util.set_func_endpoint_attr(
305
+ on_document_written_with_auth_context_wrapped,
306
+ options._endpoint(
307
+ event_type=_event_type_written,
308
+ func_name=func.__name__,
309
+ document_pattern=document_pattern,
310
+ ),
311
+ )
312
+ return on_document_written_with_auth_context_wrapped
313
+
314
+ return on_document_written_with_auth_context_inner_decorator
315
+
316
+
227
317
  @_util.copy_func_kwargs(FirestoreOptions)
228
318
  def on_document_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
229
319
  """
@@ -272,6 +362,57 @@ def on_document_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
272
362
  return on_document_updated_inner_decorator
273
363
 
274
364
 
365
+ @_util.copy_func_kwargs(FirestoreOptions)
366
+ def on_document_updated_with_auth_context(**kwargs
367
+ ) -> _typing.Callable[[_C1], _C1]:
368
+ """
369
+ Event handler that triggers when a document is updated in Firestore.
370
+ This trigger will also provide the authentication context of the principal who triggered
371
+ the event.
372
+
373
+ Example:
374
+
375
+ .. code-block:: python
376
+
377
+ @on_document_updated_with_auth_context(document="*")
378
+ def example(event: AuthEvent[Change[DocumentSnapshot]]) -> None:
379
+ pass
380
+
381
+ :param \\*\\*kwargs: Firestore options.
382
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
383
+ :rtype: :exc:`typing.Callable`
384
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
385
+ :exc:`firebase_functions.db.Change` \\] \\], `None` \\]
386
+ A function that takes a Firestore event and returns ``None``.
387
+ """
388
+ options = FirestoreOptions(**kwargs)
389
+
390
+ def on_document_updated_with_auth_context_inner_decorator(func: _C1):
391
+ document_pattern = _path_pattern.PathPattern(
392
+ _util.normalize_path(options.document))
393
+
394
+ @_functools.wraps(func)
395
+ def on_document_updated_with_auth_context_wrapped(raw: _ce.CloudEvent):
396
+ return _firestore_endpoint_handler(
397
+ func,
398
+ _event_type_updated_with_auth_context,
399
+ document_pattern,
400
+ raw,
401
+ )
402
+
403
+ _util.set_func_endpoint_attr(
404
+ on_document_updated_with_auth_context_wrapped,
405
+ options._endpoint(
406
+ event_type=_event_type_updated_with_auth_context,
407
+ func_name=func.__name__,
408
+ document_pattern=document_pattern,
409
+ ),
410
+ )
411
+ return on_document_updated_with_auth_context_wrapped
412
+
413
+ return on_document_updated_with_auth_context_inner_decorator
414
+
415
+
275
416
  @_util.copy_func_kwargs(FirestoreOptions)
276
417
  def on_document_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
277
418
  """
@@ -320,6 +461,57 @@ def on_document_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
320
461
  return on_document_created_inner_decorator
321
462
 
322
463
 
464
+ @_util.copy_func_kwargs(FirestoreOptions)
465
+ def on_document_created_with_auth_context(**kwargs
466
+ ) -> _typing.Callable[[_C2], _C2]:
467
+ """
468
+ Event handler that triggers when a document is created in Firestore.
469
+ This trigger will also provide the authentication context of the principal who triggered
470
+ the event.
471
+
472
+ Example:
473
+
474
+ .. code-block:: python
475
+
476
+ @on_document_created_with_auth_context(document="*")
477
+ def example(event: AuthEvent[DocumentSnapshot]):
478
+ pass
479
+
480
+ :param \\*\\*kwargs: Firestore options.
481
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
482
+ :rtype: :exc:`typing.Callable`
483
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
484
+ :exc:`object` \\] \\], `None` \\]
485
+ A function that takes a Firestore event and returns ``None``.
486
+ """
487
+ options = FirestoreOptions(**kwargs)
488
+
489
+ def on_document_created_with_auth_context_inner_decorator(func: _C2):
490
+ document_pattern = _path_pattern.PathPattern(
491
+ _util.normalize_path(options.document))
492
+
493
+ @_functools.wraps(func)
494
+ def on_document_created_with_auth_context_wrapped(raw: _ce.CloudEvent):
495
+ return _firestore_endpoint_handler(
496
+ func,
497
+ _event_type_created_with_auth_context,
498
+ document_pattern,
499
+ raw,
500
+ )
501
+
502
+ _util.set_func_endpoint_attr(
503
+ on_document_created_with_auth_context_wrapped,
504
+ options._endpoint(
505
+ event_type=_event_type_created_with_auth_context,
506
+ func_name=func.__name__,
507
+ document_pattern=document_pattern,
508
+ ),
509
+ )
510
+ return on_document_created_with_auth_context_wrapped
511
+
512
+ return on_document_created_with_auth_context_inner_decorator
513
+
514
+
323
515
  @_util.copy_func_kwargs(FirestoreOptions)
324
516
  def on_document_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]:
325
517
  """
@@ -366,3 +558,54 @@ def on_document_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]:
366
558
  return on_document_deleted_wrapped
367
559
 
368
560
  return on_document_deleted_inner_decorator
561
+
562
+
563
+ @_util.copy_func_kwargs(FirestoreOptions)
564
+ def on_document_deleted_with_auth_context(**kwargs
565
+ ) -> _typing.Callable[[_C2], _C2]:
566
+ """
567
+ Event handler that triggers when a document is deleted in Firestore.
568
+ This trigger will also provide the authentication context of the principal who triggered
569
+ the event.
570
+
571
+ Example:
572
+
573
+ .. code-block:: python
574
+
575
+ @on_document_deleted_with_auth_context(document="*")
576
+ def example(event: AuthEvent[DocumentSnapshot]) -> None:
577
+ pass
578
+
579
+ :param \\*\\*kwargs: Firestore options.
580
+ :type \\*\\*kwargs: as :exc:`firebase_functions.options.FirestoreOptions`
581
+ :rtype: :exc:`typing.Callable`
582
+ \\[ \\[ :exc:`firebase_functions.firestore_fn.AuthEvent` \\[
583
+ :exc:`object` \\] \\], `None` \\]
584
+ A function that takes a Firestore event and returns ``None``.
585
+ """
586
+ options = FirestoreOptions(**kwargs)
587
+
588
+ def on_document_deleted_with_auth_context_inner_decorator(func: _C2):
589
+ document_pattern = _path_pattern.PathPattern(
590
+ _util.normalize_path(options.document))
591
+
592
+ @_functools.wraps(func)
593
+ def on_document_deleted_with_auth_context_wrapped(raw: _ce.CloudEvent):
594
+ return _firestore_endpoint_handler(
595
+ func,
596
+ _event_type_deleted_with_auth_context,
597
+ document_pattern,
598
+ raw,
599
+ )
600
+
601
+ _util.set_func_endpoint_attr(
602
+ on_document_deleted_with_auth_context_wrapped,
603
+ options._endpoint(
604
+ event_type=_event_type_deleted_with_auth_context,
605
+ func_name=func.__name__,
606
+ document_pattern=document_pattern,
607
+ ),
608
+ )
609
+ return on_document_deleted_with_auth_context_wrapped
610
+
611
+ 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")
@@ -399,7 +410,7 @@ def _on_call_handler(func: _C2, request: Request,
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)
@@ -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):
@@ -14,6 +14,7 @@
14
14
  """Module for params that can make Cloud Functions codebases generic."""
15
15
 
16
16
  import abc as _abc
17
+ import json as _json
17
18
  import dataclasses as _dataclasses
18
19
  import os as _os
19
20
  import re as _re
@@ -139,6 +140,18 @@ class SelectInput(_typing.Generic[_T]):
139
140
  """A list of user selectable options."""
140
141
 
141
142
 
143
+ @_dataclasses.dataclass(frozen=True)
144
+ class MultiSelectInput():
145
+ """
146
+ Specifies that a Param's value should be determined by having the user select
147
+ a subset from a list of pre-canned options interactively at deploy-time.
148
+ Will result in errors if used on Params of type other than string[].
149
+ """
150
+
151
+ options: list[SelectOption[str]]
152
+ """A list of user selectable options."""
153
+
154
+
142
155
  @_dataclasses.dataclass(frozen=True)
143
156
  class TextInput:
144
157
  """
@@ -168,6 +181,9 @@ class ResourceType(str, _enum.Enum):
168
181
  """The type of resource that a picker should pick."""
169
182
  STORAGE_BUCKET = "storage.googleapis.com/Bucket"
170
183
 
184
+ def __str__(self) -> str:
185
+ return self.value
186
+
171
187
 
172
188
  @_dataclasses.dataclass(frozen=True)
173
189
  class ResourceInput:
@@ -215,7 +231,8 @@ class Param(Expression[_T]):
215
231
  deployments.
216
232
  """
217
233
 
218
- input: TextInput | ResourceInput | SelectInput[_T] | None = None
234
+ input: TextInput | ResourceInput | SelectInput[
235
+ _T] | MultiSelectInput | None = None
219
236
  """
220
237
  The type of input that is required for this param, e.g. TextInput.
221
238
  """
@@ -355,6 +372,32 @@ class BoolParam(Param[bool]):
355
372
  return False
356
373
 
357
374
 
375
+ @_dataclasses.dataclass(frozen=True)
376
+ class ListParam(Param[list]):
377
+ """A parameter as a list of strings."""
378
+
379
+ @property
380
+ def value(self) -> list[str]:
381
+ if _os.environ.get(self.name) is not None:
382
+ # If the environment variable starts with "[" and ends with "]",
383
+ # then assume it is a JSON array and try to parse it.
384
+ # (This is for Cloud Run (v2 Functions), the environment variable is a JSON array.)
385
+ if _os.environ[self.name].startswith("[") and _os.environ[
386
+ self.name].endswith("]"):
387
+ try:
388
+ return _json.loads(_os.environ[self.name])
389
+ except _json.JSONDecodeError:
390
+ return []
391
+ # Otherwise, split the string by commas.
392
+ # (This is for emulator & the Firebase CLI generated .env file, the environment
393
+ # variable is a comma-separated list.)
394
+ return list(filter(len, _os.environ[self.name].split(",")))
395
+ if self.default is not None:
396
+ return self.default.value if isinstance(
397
+ self.default, Expression) else self.default
398
+ return []
399
+
400
+
358
401
  @_dataclasses.dataclass(frozen=True)
359
402
  class _DefaultStringParam(StringParam):
360
403
  """
@@ -16,7 +16,7 @@
16
16
  # pylint: disable=protected-access,cyclic-import
17
17
  import typing as _typing
18
18
  import cloudevents.http as _ce
19
- import util as _util
19
+ import firebase_functions.private.util as _util
20
20
  from firebase_functions.alerts import FirebaseAlertData
21
21
 
22
22
  from functions_framework import logging as _logging
@@ -110,6 +110,7 @@ def regression_alert_payload_from_ce_payload(payload: dict):
110
110
 
111
111
  def trending_issue_details_from_ce_payload(payload: dict):
112
112
  from firebase_functions.alerts.crashlytics_fn import TrendingIssueDetails
113
+
113
114
  return TrendingIssueDetails(
114
115
  type=payload["type"],
115
116
  issue=issue_from_ce_payload(payload["issue"]),
@@ -120,16 +121,19 @@ def trending_issue_details_from_ce_payload(payload: dict):
120
121
 
121
122
  def stability_digest_payload_from_ce_payload(payload: dict):
122
123
  from firebase_functions.alerts.crashlytics_fn import StabilityDigestPayload
124
+
123
125
  return StabilityDigestPayload(
124
126
  digest_date=_util.timestamp_conversion(payload["digestDate"]),
125
127
  trending_issues=[
126
128
  trending_issue_details_from_ce_payload(issue)
127
129
  for issue in payload["trendingIssues"]
128
- ])
130
+ ],
131
+ )
129
132
 
130
133
 
131
134
  def velocity_alert_payload_from_ce_payload(payload: dict):
132
135
  from firebase_functions.alerts.crashlytics_fn import VelocityAlertPayload
136
+
133
137
  return VelocityAlertPayload(
134
138
  issue=issue_from_ce_payload(payload["issue"]),
135
139
  create_time=_util.timestamp_conversion(payload["createTime"]),
@@ -141,11 +145,13 @@ def velocity_alert_payload_from_ce_payload(payload: dict):
141
145
 
142
146
  def new_anr_issue_payload_from_ce_payload(payload: dict):
143
147
  from firebase_functions.alerts.crashlytics_fn import NewAnrIssuePayload
148
+
144
149
  return NewAnrIssuePayload(issue=issue_from_ce_payload(payload["issue"]))
145
150
 
146
151
 
147
152
  def firebase_alert_data_from_ce(event_dict: dict,) -> FirebaseAlertData:
148
153
  from firebase_functions.options import AlertType
154
+
149
155
  alert_type: str = event_dict["alerttype"]
150
156
  alert_payload = event_dict["payload"]
151
157
  if alert_type == AlertType.CRASHLYTICS_NEW_FATAL_ISSUE.value:
@@ -205,24 +211,29 @@ def event_from_ce_helper(raw: _ce.CloudEvent, cls, app_id=True):
205
211
 
206
212
  def billing_event_from_ce(raw: _ce.CloudEvent):
207
213
  from firebase_functions.alerts.billing_fn import BillingEvent
214
+
208
215
  return event_from_ce_helper(raw, BillingEvent, app_id=False)
209
216
 
210
217
 
211
218
  def performance_event_from_ce(raw: _ce.CloudEvent):
212
219
  from firebase_functions.alerts.performance_fn import PerformanceEvent
220
+
213
221
  return event_from_ce_helper(raw, PerformanceEvent)
214
222
 
215
223
 
216
224
  def app_distribution_event_from_ce(raw: _ce.CloudEvent):
217
225
  from firebase_functions.alerts.app_distribution_fn import AppDistributionEvent
226
+
218
227
  return event_from_ce_helper(raw, AppDistributionEvent)
219
228
 
220
229
 
221
230
  def crashlytics_event_from_ce(raw: _ce.CloudEvent):
222
231
  from firebase_functions.alerts.crashlytics_fn import CrashlyticsEvent
232
+
223
233
  return event_from_ce_helper(raw, CrashlyticsEvent)
224
234
 
225
235
 
226
236
  def alerts_event_from_ce(raw: _ce.CloudEvent):
227
237
  from firebase_functions.alerts_fn import AlertEvent
238
+
228
239
  return event_from_ce_helper(raw, AlertEvent)