firebase-functions 0.4.2__tar.gz → 0.4.3__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 (61) hide show
  1. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/PKG-INFO +1 -1
  2. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/README.md +7 -6
  3. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/setup.py +2 -2
  4. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/__init__.py +2 -2
  5. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/firestore_fn.py +9 -4
  6. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/identity_fn.py +30 -5
  7. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/logger.py +22 -5
  8. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/options.py +1 -1
  9. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/params.py +6 -2
  10. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/_identity_fn.py +6 -2
  11. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/manifest.py +1 -1
  12. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions.egg-info/PKG-INFO +1 -1
  13. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions.egg-info/requires.txt +2 -2
  14. firebase_functions-0.4.3/tests/test_logger.py +226 -0
  15. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_manifest.py +1 -1
  16. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_options.py +16 -2
  17. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_params.py +9 -9
  18. firebase_functions-0.4.2/tests/test_logger.py +0 -81
  19. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/LICENSE +0 -0
  20. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/pyproject.toml +0 -0
  21. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/setup.cfg +0 -0
  22. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/alerts/__init__.py +0 -0
  23. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/alerts/app_distribution_fn.py +0 -0
  24. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/alerts/billing_fn.py +0 -0
  25. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/alerts/crashlytics_fn.py +0 -0
  26. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/alerts/performance_fn.py +0 -0
  27. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/alerts_fn.py +0 -0
  28. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/core.py +0 -0
  29. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/db_fn.py +0 -0
  30. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/eventarc_fn.py +0 -0
  31. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/https_fn.py +0 -0
  32. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/__init__.py +0 -0
  33. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/_alerts_fn.py +0 -0
  34. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/path_pattern.py +0 -0
  35. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/serving.py +0 -0
  36. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/token_verifier.py +0 -0
  37. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/private/util.py +0 -0
  38. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/pubsub_fn.py +0 -0
  39. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/py.typed +0 -0
  40. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/remote_config_fn.py +0 -0
  41. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/scheduler_fn.py +0 -0
  42. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/storage_fn.py +0 -0
  43. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/tasks_fn.py +0 -0
  44. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions/test_lab_fn.py +0 -0
  45. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions.egg-info/SOURCES.txt +0 -0
  46. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions.egg-info/dependency_links.txt +0 -0
  47. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/src/firebase_functions.egg-info/top_level.txt +0 -0
  48. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_db.py +0 -0
  49. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_eventarc_fn.py +0 -0
  50. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_firestore_fn.py +0 -0
  51. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_https_fn.py +0 -0
  52. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_identity_fn.py +0 -0
  53. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_init.py +0 -0
  54. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_path_pattern.py +0 -0
  55. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_pubsub_fn.py +0 -0
  56. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_remote_config_fn.py +0 -0
  57. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_scheduler_fn.py +0 -0
  58. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_storage_fn.py +0 -0
  59. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_tasks_fn.py +0 -0
  60. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_test_lab_fn.py +0 -0
  61. {firebase_functions-0.4.2 → firebase_functions-0.4.3}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: firebase_functions
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Firebase Functions Python SDK
5
5
  Home-page: https://github.com/firebase/firebase-functions-python
6
6
  Author: Firebase Team
@@ -10,12 +10,12 @@ Learn more about the Firebase SDK for Cloud Functions in the [Firebase documenta
10
10
 
11
11
  Here are some resources to get help:
12
12
 
13
- - Start with the quickstart: https://firebase.google.com/docs/functions/get-started
14
- - Go through the guide: https://firebase.google.com/docs/functions/
15
- - Read the full API reference: https://firebase.google.com/docs/reference/functions/2nd-gen/python
16
- - Browse some examples: https://github.com/firebase/functions-samples
13
+ - Start with the quickstart: <https://firebase.google.com/docs/functions/get-started>
14
+ - Go through the guide: <https://firebase.google.com/docs/functions/>
15
+ - Read the full API reference: <https://firebase.google.com/docs/reference/functions/2nd-gen/python>
16
+ - Browse some examples: <https://github.com/firebase/functions-samples>
17
17
 
18
- If the official documentation doesn't help, try asking through our official support channels: https://firebase.google.com/support/
18
+ If the official documentation doesn't help, try asking through our official support channels: <https://firebase.google.com/support/>
19
19
 
20
20
  ## Usage
21
21
 
@@ -36,4 +36,5 @@ To contribute a change, [check out the contributing guide](.github/CONTRIBUTING.
36
36
 
37
37
  ## License
38
38
 
39
- © Google, 2023. Licensed under [Apache License](LICENSE).
39
+ © Google, 2025. Licensed under [Apache License](LICENSE).
40
+
@@ -19,8 +19,8 @@ from setuptools import find_packages, setup
19
19
 
20
20
  install_requires = [
21
21
  'flask>=2.1.2', 'functions-framework>=3.0.0', 'firebase-admin>=6.0.0',
22
- 'pyyaml>=6.0', 'typing-extensions>=4.4.0', 'cloudevents==1.9.0',
23
- 'flask-cors>=3.0.10', 'pyjwt[crypto]>=2.5.0', 'google-events>=0.5.0',
22
+ 'pyyaml>=6.0', 'typing-extensions>=4.4.0', 'cloudevents>=1.2.0,<2.0.0',
23
+ 'flask-cors>=3.0.10', 'pyjwt[crypto]>=2.5.0', 'google-events==0.5.0',
24
24
  'google-cloud-firestore>=2.11.0'
25
25
  ]
26
26
 
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google Inc.
1
+ # Copyright 2025 Google Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -15,4 +15,4 @@
15
15
  Firebase Functions for Python.
16
16
  """
17
17
 
18
- __version__ = "0.4.2"
18
+ __version__ = "0.4.3"
@@ -169,13 +169,18 @@ def _firestore_endpoint_handler(
169
169
  firestore_event_data.old_value.create_time,
170
170
  firestore_event_data.old_value.update_time,
171
171
  )
172
- if event_type == _event_type_deleted:
172
+
173
+ if event_type in (_event_type_deleted,
174
+ _event_type_deleted_with_auth_context):
173
175
  firestore_event_data = _typing.cast(_firestore.DocumentEventData,
174
176
  old_value_snapshot)
175
- if event_type == _event_type_created:
177
+ if event_type in (_event_type_created,
178
+ _event_type_created_with_auth_context):
176
179
  firestore_event_data = _typing.cast(_firestore.DocumentEventData,
177
180
  value_snapshot)
178
- if event_type in (_event_type_written, _event_type_updated):
181
+ if event_type in (_event_type_written, _event_type_updated,
182
+ _event_type_written_with_auth_context,
183
+ _event_type_updated_with_auth_context):
179
184
  firestore_event_data = _typing.cast(
180
185
  _firestore.DocumentEventData,
181
186
  Change(
@@ -306,7 +311,7 @@ def on_document_written_with_auth_context(**kwargs
306
311
  _util.set_func_endpoint_attr(
307
312
  on_document_written_with_auth_context_wrapped,
308
313
  options._endpoint(
309
- event_type=_event_type_written,
314
+ event_type=_event_type_written_with_auth_context,
310
315
  func_name=func.__name__,
311
316
  document_pattern=document_pattern,
312
317
  ),
@@ -18,6 +18,7 @@ import typing as _typing
18
18
  import functools as _functools
19
19
  import datetime as _dt
20
20
  import dataclasses as _dataclasses
21
+ from enum import Enum
21
22
 
22
23
  import firebase_functions.options as _options
23
24
  import firebase_functions.private.util as _util
@@ -238,17 +239,23 @@ class Credential:
238
239
  """The user's sign-in method."""
239
240
 
240
241
 
242
+ class EmailType(str, Enum):
243
+ EMAIL_SIGN_IN = "EMAIL_SIGN_IN"
244
+ PASSWORD_RESET = "PASSWORD_RESET"
245
+
246
+
247
+ class SmsType(str, Enum):
248
+ SIGN_IN_OR_SIGN_UP = "SIGN_IN_OR_SIGN_UP"
249
+ MULTI_FACTOR_SIGN_IN = "MULTI_FACTOR_SIGN_IN"
250
+ MULTI_FACTOR_ENROLLMENT = "MULTI_FACTOR_ENROLLMENT"
251
+
252
+
241
253
  @_dataclasses.dataclass(frozen=True)
242
254
  class AuthBlockingEvent:
243
255
  """
244
256
  Defines an auth event for identitytoolkit v2 auth blocking events.
245
257
  """
246
258
 
247
- data: AuthUserRecord
248
- """
249
- The UserRecord passed to auth blocking functions from the identity platform.
250
- """
251
-
252
259
  locale: str | None
253
260
  """
254
261
  The application locale. You can set the locale using the client SDK,
@@ -262,6 +269,13 @@ class AuthBlockingEvent:
262
269
  Example: 'rWsyPtolplG2TBFoOkkgyg'
263
270
  """
264
271
 
272
+ event_type: str
273
+ """
274
+ The event type. This provides information on the event name, such as
275
+ beforeSignIn or beforeCreate, and the associated sign-in method used,
276
+ like Google or email/password.
277
+ """
278
+
265
279
  ip_address: str
266
280
  """
267
281
  The IP address of the device the end user is registering or signing in from.
@@ -280,10 +294,21 @@ class AuthBlockingEvent:
280
294
  credential: Credential | None
281
295
  """An object containing information about the user's credential."""
282
296
 
297
+ email_type: EmailType | None
298
+ """The type of email event."""
299
+
300
+ sms_type: SmsType | None
301
+ """The type of SMS event."""
302
+
283
303
  timestamp: _dt.datetime
284
304
  """
285
305
  The time the event was triggered."""
286
306
 
307
+ data: AuthUserRecord
308
+ """
309
+ The UserRecord passed to auth blocking functions from the identity platform.
310
+ """
311
+
287
312
 
288
313
  RecaptchaActionOptions = _typing.Literal["ALLOW", "BLOCK"]
289
314
  """
@@ -8,6 +8,12 @@ import sys as _sys
8
8
  import typing as _typing
9
9
  import typing_extensions as _typing_extensions
10
10
 
11
+ # If encoding is not 'utf-8', change it to 'utf-8'.
12
+ if _sys.stdout.encoding != "utf-8":
13
+ _sys.stdout.reconfigure(encoding="utf-8") # type: ignore
14
+ if _sys.stderr.encoding != "utf-8":
15
+ _sys.stderr.reconfigure(encoding="utf-8") # type: ignore
16
+
11
17
 
12
18
  class LogSeverity(str, _enum.Enum):
13
19
  """
@@ -69,21 +75,32 @@ def _remove_circular(obj: _typing.Any,
69
75
  if refs is None:
70
76
  refs = set()
71
77
 
78
+ # Check if the object is already in the current recursion stack
72
79
  if id(obj) in refs:
73
80
  return "[CIRCULAR]"
74
81
 
82
+ # For non-primitive objects, add the current object's id to the recursion stack
75
83
  if not isinstance(obj, (str, int, float, bool, type(None))):
76
84
  refs.add(id(obj))
77
85
 
86
+ # Recursively process the object based on its type
87
+ result: _typing.Any
78
88
  if isinstance(obj, dict):
79
- return {key: _remove_circular(value, refs) for key, value in obj.items()}
89
+ result = {
90
+ key: _remove_circular(value, refs) for key, value in obj.items()
91
+ }
80
92
  elif isinstance(obj, list):
81
- return [_remove_circular(value, refs) for _, value in enumerate(obj)]
93
+ result = [_remove_circular(item, refs) for item in obj]
82
94
  elif isinstance(obj, tuple):
83
- return tuple(
84
- _remove_circular(value, refs) for _, value in enumerate(obj))
95
+ result = tuple(_remove_circular(item, refs) for item in obj)
85
96
  else:
86
- return obj
97
+ result = obj
98
+
99
+ # Remove the object's id from the recursion stack after processing
100
+ if not isinstance(obj, (str, int, float, bool, type(None))):
101
+ refs.remove(id(obj))
102
+
103
+ return result
87
104
 
88
105
 
89
106
  def _get_write_file(severity: LogSeverity) -> _typing.TextIO:
@@ -1134,7 +1134,7 @@ class HttpsOptions(RuntimeOptions):
1134
1134
  invoker = [invoker]
1135
1135
  assert len(
1136
1136
  invoker
1137
- ) > 1, "HttpsOptions: Invalid option for invoker - must be a non-empty list."
1137
+ ) >= 1, "HttpsOptions: Invalid option for invoker - must be a non-empty list."
1138
1138
  assert "" not in invoker, (
1139
1139
  "HttpsOptions: Invalid option for invoker - must be a non-empty string."
1140
1140
  )
@@ -344,8 +344,12 @@ class IntParam(Param[int]):
344
344
 
345
345
 
346
346
  @_dataclasses.dataclass(frozen=True)
347
- class FloatParam(Param[float]):
348
- """A parameter as a float value."""
347
+ class _FloatParam(Param[float]):
348
+ """
349
+ A parameter as a float value.
350
+ Marked as private because it is not supported by firebase-tools yet.
351
+ Unmark when it is supported.
352
+ """
349
353
 
350
354
  @property
351
355
  def value(self) -> float:
@@ -200,17 +200,21 @@ def _credential_from_token_data(token_data: dict[str, _typing.Any],
200
200
  )
201
201
 
202
202
 
203
- def _auth_blocking_event_from_token_data(token_data: dict[str, _typing.Any]):
203
+ def _auth_blocking_event_from_token_data(event_type: str,
204
+ token_data: dict[str, _typing.Any]):
204
205
  from firebase_functions.identity_fn import AuthBlockingEvent
205
206
  return AuthBlockingEvent(
206
207
  data=_auth_user_record_from_token_data(token_data["user_record"]),
207
208
  locale=token_data.get("locale"),
209
+ event_type=event_type,
208
210
  event_id=token_data["event_id"],
209
211
  ip_address=token_data["ip_address"],
210
212
  user_agent=token_data["user_agent"],
211
213
  timestamp=_dt.datetime.fromtimestamp(token_data["iat"]),
212
214
  additional_user_info=_additional_user_info_from_token_data(token_data),
213
215
  credential=_credential_from_token_data(token_data, _time.time()),
216
+ email_type=token_data.get("email_type"),
217
+ sms_type=token_data.get("sms_type"),
214
218
  )
215
219
 
216
220
 
@@ -351,7 +355,7 @@ def before_operation_handler(
351
355
  raise HttpsError(FunctionsErrorCode.INVALID_ARGUMENT, "Bad Request")
352
356
  jwt_token = request.json["data"]["jwt"]
353
357
  decoded_token = _token_verifier.verify_auth_blocking_token(jwt_token)
354
- event = _auth_blocking_event_from_token_data(decoded_token)
358
+ event = _auth_blocking_event_from_token_data(event_type, decoded_token)
355
359
  auth_response: BeforeCreateResponse | BeforeSignInResponse | None = _with_init(
356
360
  func)(event)
357
361
  if not auth_response:
@@ -241,7 +241,7 @@ def _param_to_spec(
241
241
  spec_dict["type"] = "boolean"
242
242
  elif isinstance(param, _params.IntParam):
243
243
  spec_dict["type"] = "int"
244
- elif isinstance(param, _params.FloatParam):
244
+ elif isinstance(param, _params._FloatParam):
245
245
  spec_dict["type"] = "float"
246
246
  elif isinstance(param, _params.SecretParam):
247
247
  spec_dict["type"] = "secret"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: firebase-functions
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Firebase Functions Python SDK
5
5
  Home-page: https://github.com/firebase/firebase-functions-python
6
6
  Author: Firebase Team
@@ -3,10 +3,10 @@ functions-framework>=3.0.0
3
3
  firebase-admin>=6.0.0
4
4
  pyyaml>=6.0
5
5
  typing-extensions>=4.4.0
6
- cloudevents==1.9.0
6
+ cloudevents<2.0.0,>=1.2.0
7
7
  flask-cors>=3.0.10
8
8
  pyjwt[crypto]>=2.5.0
9
- google-events>=0.5.0
9
+ google-events==0.5.0
10
10
  google-cloud-firestore>=2.11.0
11
11
 
12
12
  [dev]
@@ -0,0 +1,226 @@
1
+ # mypy: ignore-errors
2
+ """
3
+ Logger module tests.
4
+ """
5
+
6
+ import pytest
7
+ import json
8
+ from firebase_functions import logger
9
+
10
+
11
+ class TestLogger:
12
+ """
13
+ Tests for the logger module.
14
+ """
15
+
16
+ def test_format_should_be_valid_json(self,
17
+ capsys: pytest.CaptureFixture[str]):
18
+ logger.log(foo="bar")
19
+ raw_log_output = capsys.readouterr().out
20
+ try:
21
+ json.loads(raw_log_output)
22
+ except json.JSONDecodeError:
23
+ pytest.fail("Log output was not valid JSON.")
24
+
25
+ def test_log_should_have_severity(self, capsys: pytest.CaptureFixture[str]):
26
+ logger.log(foo="bar")
27
+ raw_log_output = capsys.readouterr().out
28
+ log_output = json.loads(raw_log_output)
29
+ assert "severity" in log_output
30
+
31
+ def test_severity_should_be_debug(self, capsys: pytest.CaptureFixture[str]):
32
+ logger.debug(foo="bar")
33
+ raw_log_output = capsys.readouterr().out
34
+ log_output = json.loads(raw_log_output)
35
+ assert log_output["severity"] == "DEBUG"
36
+
37
+ def test_severity_should_be_notice(self,
38
+ capsys: pytest.CaptureFixture[str]):
39
+ logger.log(foo="bar")
40
+ raw_log_output = capsys.readouterr().out
41
+ log_output = json.loads(raw_log_output)
42
+ assert log_output["severity"] == "NOTICE"
43
+
44
+ def test_severity_should_be_info(self, capsys: pytest.CaptureFixture[str]):
45
+ logger.info(foo="bar")
46
+ raw_log_output = capsys.readouterr().out
47
+ log_output = json.loads(raw_log_output)
48
+ assert log_output["severity"] == "INFO"
49
+
50
+ def test_severity_should_be_warning(self,
51
+ capsys: pytest.CaptureFixture[str]):
52
+ logger.warn(foo="bar")
53
+ raw_log_output = capsys.readouterr().out
54
+ log_output = json.loads(raw_log_output)
55
+ assert log_output["severity"] == "WARNING"
56
+
57
+ def test_severity_should_be_error(self, capsys: pytest.CaptureFixture[str]):
58
+ logger.error(foo="bar")
59
+ raw_log_output = capsys.readouterr().err
60
+ log_output = json.loads(raw_log_output)
61
+ assert log_output["severity"] == "ERROR"
62
+
63
+ def test_log_should_have_message(self, capsys: pytest.CaptureFixture[str]):
64
+ logger.log("bar")
65
+ raw_log_output = capsys.readouterr().out
66
+ log_output = json.loads(raw_log_output)
67
+ assert "message" in log_output
68
+
69
+ def test_log_should_have_other_keys(self,
70
+ capsys: pytest.CaptureFixture[str]):
71
+ logger.log(foo="bar")
72
+ raw_log_output = capsys.readouterr().out
73
+ log_output = json.loads(raw_log_output)
74
+ assert "foo" in log_output
75
+
76
+ def test_message_should_be_space_separated(
77
+ self, capsys: pytest.CaptureFixture[str]):
78
+ logger.log("bar", "qux")
79
+ expected_message = "bar qux"
80
+ raw_log_output = capsys.readouterr().out
81
+ log_output = json.loads(raw_log_output)
82
+ assert log_output["message"] == expected_message
83
+
84
+ def test_remove_circular_references(self,
85
+ capsys: pytest.CaptureFixture[str]):
86
+ # Create an object with a circular reference.
87
+ circ = {"b": "foo"}
88
+ circ["circ"] = circ
89
+
90
+ entry = {
91
+ "severity": "ERROR",
92
+ "message": "testing circular",
93
+ "circ": circ,
94
+ }
95
+ logger.write(entry)
96
+ raw_log_output = capsys.readouterr().err
97
+ log_output = json.loads(raw_log_output)
98
+
99
+ expected = {
100
+ "severity": "ERROR",
101
+ "message": "testing circular",
102
+ "circ": {
103
+ "b": "foo",
104
+ "circ": "[CIRCULAR]"
105
+ },
106
+ }
107
+ assert log_output == expected
108
+
109
+ def test_remove_circular_references_in_arrays(
110
+ self, capsys: pytest.CaptureFixture[str]):
111
+ # Create an object with a circular reference inside an array.
112
+ circ = {"b": "foo"}
113
+ circ["circ"] = [circ]
114
+
115
+ entry = {
116
+ "severity": "ERROR",
117
+ "message": "testing circular",
118
+ "circ": circ,
119
+ }
120
+ logger.write(entry)
121
+ raw_log_output = capsys.readouterr().err
122
+ log_output = json.loads(raw_log_output)
123
+
124
+ expected = {
125
+ "severity": "ERROR",
126
+ "message": "testing circular",
127
+ "circ": {
128
+ "b": "foo",
129
+ "circ": ["[CIRCULAR]"]
130
+ },
131
+ }
132
+ assert log_output == expected
133
+
134
+ def test_no_false_circular_for_duplicates(
135
+ self, capsys: pytest.CaptureFixture[str]):
136
+ # Ensure that duplicate objects (used in multiple keys) are not marked as circular.
137
+ obj = {"a": "foo"}
138
+ entry = {
139
+ "severity": "ERROR",
140
+ "message": "testing circular",
141
+ "a": obj,
142
+ "b": obj,
143
+ }
144
+ logger.write(entry)
145
+ raw_log_output = capsys.readouterr().err
146
+ log_output = json.loads(raw_log_output)
147
+
148
+ expected = {
149
+ "severity": "ERROR",
150
+ "message": "testing circular",
151
+ "a": {
152
+ "a": "foo"
153
+ },
154
+ "b": {
155
+ "a": "foo"
156
+ },
157
+ }
158
+ assert log_output == expected
159
+
160
+ def test_no_false_circular_in_array_duplicates(
161
+ self, capsys: pytest.CaptureFixture[str]):
162
+ # Ensure that duplicate objects in arrays are not falsely detected as circular.
163
+ obj = {"a": "foo"}
164
+ arr = [
165
+ {
166
+ "a": obj,
167
+ "b": obj
168
+ },
169
+ {
170
+ "a": obj,
171
+ "b": obj
172
+ },
173
+ ]
174
+ entry = {
175
+ "severity": "ERROR",
176
+ "message": "testing circular",
177
+ "a": arr,
178
+ "b": arr,
179
+ }
180
+ logger.write(entry)
181
+ raw_log_output = capsys.readouterr().err
182
+ log_output = json.loads(raw_log_output)
183
+
184
+ expected = {
185
+ "severity":
186
+ "ERROR",
187
+ "message":
188
+ "testing circular",
189
+ "a": [
190
+ {
191
+ "a": {
192
+ "a": "foo"
193
+ },
194
+ "b": {
195
+ "a": "foo"
196
+ }
197
+ },
198
+ {
199
+ "a": {
200
+ "a": "foo"
201
+ },
202
+ "b": {
203
+ "a": "foo"
204
+ }
205
+ },
206
+ ],
207
+ "b": [
208
+ {
209
+ "a": {
210
+ "a": "foo"
211
+ },
212
+ "b": {
213
+ "a": "foo"
214
+ }
215
+ },
216
+ {
217
+ "a": {
218
+ "a": "foo"
219
+ },
220
+ "b": {
221
+ "a": "foo"
222
+ }
223
+ },
224
+ ],
225
+ }
226
+ assert log_output == expected
@@ -65,7 +65,7 @@ full_stack = _manifest.ManifestStack(
65
65
  params=[
66
66
  _params.BoolParam("BOOL_TEST", default=False),
67
67
  _params.IntParam("INT_TEST", description="int_description"),
68
- _params.FloatParam("FLOAT_TEST", immutable=True),
68
+ _params._FloatParam("FLOAT_TEST", immutable=True),
69
69
  _params.SecretParam("SECRET_TEST"),
70
70
  _params.StringParam("STRING_TEST"),
71
71
  _params.ListParam("LIST_TEST", default=["1", "2", "3"]),
@@ -17,6 +17,7 @@ Options unit tests.
17
17
  from firebase_functions import options, https_fn
18
18
  from firebase_functions import params
19
19
  from firebase_functions.private.serving import functions_as_yaml, merge_required_apis
20
+ from pytest import raises
20
21
  # pylint: disable=protected-access
21
22
 
22
23
 
@@ -44,7 +45,7 @@ def test_global_options_merged_with_provider_options():
44
45
  Testing a global option is used when no provider option is set.
45
46
  """
46
47
  options.set_global_options(max_instances=66)
47
- pubsub_options = options.PubSubOptions(topic="foo") #pylint: disable=unexpected-keyword-arg
48
+ pubsub_options = options.PubSubOptions(topic="foo") # pylint: disable=unexpected-keyword-arg
48
49
  pubsub_options_dict = pubsub_options._asdict_with_global_options()
49
50
  assert (pubsub_options_dict["topic"] == "foo"
50
51
  ), "'topic' property missing from dict"
@@ -170,7 +171,7 @@ def test_merge_apis_duplicate_apis():
170
171
  This test evaluates the merge_required_apis function when the
171
172
  input list contains duplicate APIs with different reasons.
172
173
  The desired outcome for this test is a list where the duplicate
173
- APIs are merged properly and reasons are combined.
174
+ APIs are merged properly and reasons are combined.
174
175
  This test ensures that the function correctly merges the duplicate
175
176
  APIs and combines the reasons associated with them.
176
177
  """
@@ -217,3 +218,16 @@ def test_merge_apis_duplicate_apis():
217
218
  for actual_item in merged_apis:
218
219
  assert (actual_item in expected_output
219
220
  ), f"Unexpected item {actual_item} found in the merged list"
221
+
222
+
223
+ def test_invoker_with_one_element_doesnt_throw():
224
+ options.HttpsOptions(invoker=["public"])._endpoint(func_name="test")
225
+
226
+
227
+ def test_invoker_with_no_element_throws():
228
+ with raises(
229
+ AssertionError,
230
+ match=
231
+ "HttpsOptions: Invalid option for invoker - must be a non-empty list."
232
+ ):
233
+ options.HttpsOptions(invoker=[])._endpoint(func_name="test")
@@ -58,28 +58,28 @@ class TestFloatParams:
58
58
  def test_float_param_value(self):
59
59
  """Testing if float params correctly returns a value."""
60
60
  environ["FLOAT_VALUE_TEST"] = "123.456"
61
- assert params.FloatParam("FLOAT_VALUE_TEST",).value == 123.456, \
61
+ assert params._FloatParam("FLOAT_VALUE_TEST",).value == 123.456, \
62
62
  "Failure, params value != 123.456"
63
63
 
64
64
  def test_float_param_empty_default(self):
65
65
  """Testing if float params defaults to empty float if no value and no default."""
66
- assert params.FloatParam("FLOAT_DEFAULT_TEST1").value == float(), \
66
+ assert params._FloatParam("FLOAT_DEFAULT_TEST1").value == float(), \
67
67
  "Failure, params value is not float"
68
68
 
69
69
  def test_float_param_default(self):
70
70
  """Testing if float param defaults to provided default value."""
71
- assert params.FloatParam("FLOAT_DEFAULT_TEST2",
71
+ assert params._FloatParam("FLOAT_DEFAULT_TEST2", \
72
72
  default=float(456.789)).value == 456.789, \
73
73
  "Failure, params default value != 456.789"
74
74
 
75
75
  def test_float_param_equality(self):
76
76
  """Test float equality."""
77
- assert (params.FloatParam("FLOAT_TEST1",
78
- default=123.456).equals(123.456).value
79
- is True), "Failure, equality check returned False"
80
- assert (params.FloatParam("FLOAT_TEST2",
81
- default=456.789).equals(123.456).value
82
- is False), "Failure, equality check returned False"
77
+ assert (params._FloatParam("FLOAT_TEST1", \
78
+ default=123.456).equals(123.456).value \
79
+ is True), "Failure, equality check returned False"
80
+ assert (params._FloatParam("FLOAT_TEST2", \
81
+ default=456.789).equals(123.456).value \
82
+ is False), "Failure, equality check returned False"
83
83
 
84
84
 
85
85
  class TestIntParams:
@@ -1,81 +0,0 @@
1
- """
2
- Logger module tests.
3
- """
4
-
5
- import pytest
6
- import json
7
- from firebase_functions import logger
8
-
9
-
10
- class TestLogger:
11
- """
12
- Tests for the logger module.
13
- """
14
-
15
- def test_format_should_be_valid_json(self,
16
- capsys: pytest.CaptureFixture[str]):
17
- logger.log(foo="bar")
18
- raw_log_output = capsys.readouterr().out
19
- try:
20
- json.loads(raw_log_output)
21
- except json.JSONDecodeError:
22
- pytest.fail("Log output was not valid JSON.")
23
-
24
- def test_log_should_have_severity(self, capsys: pytest.CaptureFixture[str]):
25
- logger.log(foo="bar")
26
- raw_log_output = capsys.readouterr().out
27
- log_output = json.loads(raw_log_output)
28
- assert "severity" in log_output
29
-
30
- def test_severity_should_be_debug(self, capsys: pytest.CaptureFixture[str]):
31
- logger.debug(foo="bar")
32
- raw_log_output = capsys.readouterr().out
33
- log_output = json.loads(raw_log_output)
34
- assert log_output["severity"] == "DEBUG"
35
-
36
- def test_severity_should_be_notice(self,
37
- capsys: pytest.CaptureFixture[str]):
38
- logger.log(foo="bar")
39
- raw_log_output = capsys.readouterr().out
40
- log_output = json.loads(raw_log_output)
41
- assert log_output["severity"] == "NOTICE"
42
-
43
- def test_severity_should_be_info(self, capsys: pytest.CaptureFixture[str]):
44
- logger.info(foo="bar")
45
- raw_log_output = capsys.readouterr().out
46
- log_output = json.loads(raw_log_output)
47
- assert log_output["severity"] == "INFO"
48
-
49
- def test_severity_should_be_warning(self,
50
- capsys: pytest.CaptureFixture[str]):
51
- logger.warn(foo="bar")
52
- raw_log_output = capsys.readouterr().out
53
- log_output = json.loads(raw_log_output)
54
- assert log_output["severity"] == "WARNING"
55
-
56
- def test_severity_should_be_error(self, capsys: pytest.CaptureFixture[str]):
57
- logger.error(foo="bar")
58
- raw_log_output = capsys.readouterr().err
59
- log_output = json.loads(raw_log_output)
60
- assert log_output["severity"] == "ERROR"
61
-
62
- def test_log_should_have_message(self, capsys: pytest.CaptureFixture[str]):
63
- logger.log("bar")
64
- raw_log_output = capsys.readouterr().out
65
- log_output = json.loads(raw_log_output)
66
- assert "message" in log_output
67
-
68
- def test_log_should_have_other_keys(self,
69
- capsys: pytest.CaptureFixture[str]):
70
- logger.log(foo="bar")
71
- raw_log_output = capsys.readouterr().out
72
- log_output = json.loads(raw_log_output)
73
- assert "foo" in log_output
74
-
75
- def test_message_should_be_space_separated(
76
- self, capsys: pytest.CaptureFixture[str]):
77
- logger.log("bar", "qux")
78
- expected_message = "bar qux"
79
- raw_log_output = capsys.readouterr().out
80
- log_output = json.loads(raw_log_output)
81
- assert log_output["message"] == expected_message