firebase-functions 0.1.2__tar.gz → 0.2.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 (53) hide show
  1. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/PKG-INFO +1 -1
  2. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/__init__.py +1 -1
  3. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/alerts_fn.py +2 -2
  4. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/core.py +1 -1
  5. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/db_fn.py +5 -5
  6. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/https_fn.py +2 -2
  7. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/identity_fn.py +15 -4
  8. firebase_functions-0.2.0/src/firebase_functions/logger.py +130 -0
  9. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/params.py +3 -3
  10. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/_identity_fn.py +29 -8
  11. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/util.py +2 -0
  12. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/pubsub_fn.py +1 -1
  13. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/remote_config_fn.py +2 -2
  14. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/storage_fn.py +1 -1
  15. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/tasks_fn.py +1 -1
  16. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions.egg-info/PKG-INFO +1 -1
  17. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions.egg-info/SOURCES.txt +2 -0
  18. firebase_functions-0.2.0/tests/test_logger.py +81 -0
  19. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/LICENSE +0 -0
  20. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/README.md +0 -0
  21. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/pyproject.toml +0 -0
  22. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/setup.cfg +0 -0
  23. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/setup.py +0 -0
  24. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/alerts/__init__.py +0 -0
  25. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/alerts/app_distribution_fn.py +0 -0
  26. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/alerts/billing_fn.py +0 -0
  27. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/alerts/crashlytics_fn.py +0 -0
  28. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/alerts/performance_fn.py +0 -0
  29. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/eventarc_fn.py +0 -0
  30. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/firestore_fn.py +0 -0
  31. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/options.py +0 -0
  32. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/__init__.py +0 -0
  33. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/_alerts_fn.py +0 -0
  34. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/manifest.py +0 -0
  35. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/path_pattern.py +0 -0
  36. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/serving.py +0 -0
  37. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/private/token_verifier.py +0 -0
  38. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/scheduler_fn.py +0 -0
  39. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions/test_lab_fn.py +0 -0
  40. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions.egg-info/dependency_links.txt +0 -0
  41. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions.egg-info/requires.txt +0 -0
  42. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/src/firebase_functions.egg-info/top_level.txt +0 -0
  43. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_eventarc_fn.py +0 -0
  44. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_manifest.py +0 -0
  45. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_options.py +0 -0
  46. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_params.py +0 -0
  47. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_path_pattern.py +0 -0
  48. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_pubsub_fn.py +0 -0
  49. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_remote_config_fn.py +0 -0
  50. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_scheduler_fn.py +0 -0
  51. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_tasks_fn.py +0 -0
  52. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_test_lab_fn.py +0 -0
  53. {firebase_functions-0.1.2 → firebase_functions-0.2.0}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: firebase_functions
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Firebase Functions Python SDK
5
5
  Home-page: https://github.com/firebase/firebase-functions-python
6
6
  Author: Firebase Team
@@ -15,4 +15,4 @@
15
15
  Firebase Functions for Python.
16
16
  """
17
17
 
18
- __version__ = "0.1.2"
18
+ __version__ = "0.2.0"
@@ -46,7 +46,7 @@ class AlertEvent(_CloudEvent[T]):
46
46
  app_id: str | None
47
47
  """
48
48
  The Firebase App ID that's associated with the alert. This is optional,
49
- and only present when the alert is targeting at a specific Firebase App.
49
+ and only present when the alert is targeting a specific Firebase App.
50
50
  """
51
51
 
52
52
 
@@ -66,7 +66,7 @@ def on_alert_published(
66
66
  **kwargs
67
67
  ) -> _typing.Callable[[OnAlertPublishedCallable], OnAlertPublishedCallable]:
68
68
  """
69
- Event handler which triggers when a Firebase Alerts event is published.
69
+ Event handler that triggers when a Firebase Alerts event is published.
70
70
 
71
71
  Example:
72
72
 
@@ -25,7 +25,7 @@ T = _typing.TypeVar("T")
25
25
  class CloudEvent(_typing.Generic[T]):
26
26
  """
27
27
  A CloudEvent is the base of a cross-platform format for encoding a serverless event.
28
- More information can be found at https://github.com/cloudevents/spec
28
+ More information can be found at https://github.com/cloudevents/spec.
29
29
  """
30
30
 
31
31
  specversion: str
@@ -61,7 +61,7 @@ class Event(_core.CloudEvent[T]):
61
61
 
62
62
  params: dict[str, str]
63
63
  """
64
- An dict containing the values of the path patterns.
64
+ A dict containing the values of the path patterns.
65
65
  Only named capture groups are populated - {key}, {key=*}, {key=**}
66
66
  """
67
67
 
@@ -125,7 +125,7 @@ def _db_endpoint_handler(
125
125
  @_util.copy_func_kwargs(DatabaseOptions)
126
126
  def on_value_written(**kwargs) -> _typing.Callable[[_C1], _C1]:
127
127
  """
128
- Event handler which triggers when data is created, updated, or deleted in Realtime Database.
128
+ Event handler that triggers when data is created, updated, or deleted in Realtime Database.
129
129
 
130
130
  Example:
131
131
 
@@ -175,7 +175,7 @@ def on_value_written(**kwargs) -> _typing.Callable[[_C1], _C1]:
175
175
  @_util.copy_func_kwargs(DatabaseOptions)
176
176
  def on_value_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
177
177
  """
178
- Event handler which triggers when data is updated in Realtime Database.
178
+ Event handler that triggers when data is updated in Realtime Database.
179
179
 
180
180
  Example:
181
181
 
@@ -225,7 +225,7 @@ def on_value_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
225
225
  @_util.copy_func_kwargs(DatabaseOptions)
226
226
  def on_value_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
227
227
  """
228
- Event handler which triggers when data is created in Realtime Database.
228
+ Event handler that triggers when data is created in Realtime Database.
229
229
 
230
230
  Example:
231
231
 
@@ -275,7 +275,7 @@ def on_value_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
275
275
  @_util.copy_func_kwargs(DatabaseOptions)
276
276
  def on_value_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]:
277
277
  """
278
- Event handler which triggers when data is deleted in Realtime Database.
278
+ Event handler that triggers when data is deleted in Realtime Database.
279
279
 
280
280
  Example:
281
281
 
@@ -119,9 +119,9 @@ class FunctionsErrorCode(str, _enum.Enum):
119
119
 
120
120
  INTERNAL = "internal"
121
121
  """
122
- Internal errors. Means some invariants expected by
122
+ Internal errors. Means some invariants expected by the
123
123
  underlying system have been broken. If you see one of these errors,
124
- something is very broken.
124
+ something is severely broken.
125
125
  """
126
126
 
127
127
  UNAVAILABLE = "unavailable"
@@ -36,7 +36,7 @@ class AuthUserInfo:
36
36
  """The user identifier for the linked provider."""
37
37
 
38
38
  provider_id: str
39
- """The linked provider ID (e.g., "google.com" for the Google provider)."""
39
+ """The linked provider ID (such as "google.com" for the Google provider)."""
40
40
 
41
41
  display_name: str | None = None
42
42
  """The display name for the linked provider."""
@@ -110,7 +110,7 @@ class AuthMultiFactorSettings:
110
110
  @_dataclasses.dataclass(frozen=True)
111
111
  class AuthUserRecord:
112
112
  """
113
- The UserRecord passed to auth blocking Cloud Functions from the identity platform.
113
+ The UserRecord passed to auth blocking functions from the identity platform.
114
114
  """
115
115
 
116
116
  uid: str
@@ -155,7 +155,7 @@ class AuthUserRecord:
155
155
 
156
156
  provider_data: list[AuthUserInfo]
157
157
  """
158
- An array of providers (e.g., Google, Facebook) linked to the user.
158
+ An array of providers (such as Google or Facebook) linked to the user.
159
159
  """
160
160
 
161
161
  password_hash: str | None
@@ -203,6 +203,9 @@ class AdditionalUserInfo:
203
203
  is_new_user: bool
204
204
  """A boolean indicating if the user is new or not."""
205
205
 
206
+ recaptcha_score: float | None
207
+ """The user's reCAPTCHA score, if available."""
208
+
206
209
 
207
210
  @_dataclasses.dataclass(frozen=True)
208
211
  class Credential:
@@ -243,7 +246,7 @@ class AuthBlockingEvent:
243
246
 
244
247
  data: AuthUserRecord
245
248
  """
246
- The UserRecord passed to auth blocking Cloud Functions from the identity platform.
249
+ The UserRecord passed to auth blocking functions from the identity platform.
247
250
  """
248
251
 
249
252
  locale: str | None
@@ -282,6 +285,12 @@ class AuthBlockingEvent:
282
285
  The time the event was triggered."""
283
286
 
284
287
 
288
+ RecaptchaActionOptions = _typing.Literal["ALLOW", "BLOCK"]
289
+ """
290
+ The reCAPTCHA action options.
291
+ """
292
+
293
+
285
294
  class BeforeCreateResponse(_typing.TypedDict, total=False):
286
295
  """
287
296
  The handler response type for 'before_user_created' blocking events.
@@ -302,6 +311,8 @@ class BeforeCreateResponse(_typing.TypedDict, total=False):
302
311
  custom_claims: dict[str, _typing.Any] | None
303
312
  """The user's custom claims object if available."""
304
313
 
314
+ recaptcha_action_override: RecaptchaActionOptions | None
315
+
305
316
 
306
317
  class BeforeSignInResponse(BeforeCreateResponse, total=False):
307
318
  """
@@ -0,0 +1,130 @@
1
+ """
2
+ Logger module for Firebase Functions.
3
+ """
4
+
5
+ import enum as _enum
6
+ import json as _json
7
+ import sys as _sys
8
+ import typing as _typing
9
+ import typing_extensions as _typing_extensions
10
+
11
+
12
+ class LogSeverity(str, _enum.Enum):
13
+ """
14
+ `LogSeverity` indicates the detailed severity of the log entry. See
15
+ `LogSeverity <https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity>`.
16
+ """
17
+
18
+ DEBUG = "DEBUG"
19
+ INFO = "INFO"
20
+ NOTICE = "NOTICE"
21
+ WARNING = "WARNING"
22
+ ERROR = "ERROR"
23
+ CRITICAL = "CRITICAL"
24
+ ALERT = "ALERT"
25
+ EMERGENCY = "EMERGENCY"
26
+
27
+
28
+ class LogEntry(_typing.TypedDict):
29
+ """
30
+ `LogEntry` represents a log entry.
31
+ See `LogEntry <https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry>`_.
32
+ """
33
+
34
+ severity: _typing_extensions.Required[LogSeverity]
35
+ message: _typing_extensions.NotRequired[str]
36
+
37
+
38
+ def _entry_from_args(severity: LogSeverity, *args, **kwargs) -> LogEntry:
39
+ """
40
+ Creates a `LogEntry` from the given arguments.
41
+ """
42
+
43
+ message: str = " ".join([
44
+ value
45
+ if isinstance(value, str) else _json.dumps(_remove_circular(value))
46
+ for value in args
47
+ ])
48
+
49
+ other: _typing.Dict[str, _typing.Any] = {
50
+ key: value if isinstance(value, str) else _remove_circular(value)
51
+ for key, value in kwargs.items()
52
+ }
53
+
54
+ entry: _typing.Dict[str, _typing.Any] = {"severity": severity, **other}
55
+ if message:
56
+ entry["message"] = message
57
+
58
+ return _typing.cast(LogEntry, entry)
59
+
60
+
61
+ def _remove_circular(obj: _typing.Any,
62
+ refs: _typing.Set[_typing.Any] | None = None):
63
+ """
64
+ Removes circular references from the given object and replaces them with "[CIRCULAR]".
65
+ """
66
+
67
+ if refs is None:
68
+ refs = set()
69
+
70
+ if id(obj) in refs:
71
+ return "[CIRCULAR]"
72
+
73
+ if not isinstance(obj, (str, int, float, bool, type(None))):
74
+ refs.add(id(obj))
75
+
76
+ if isinstance(obj, dict):
77
+ return {key: _remove_circular(value, refs) for key, value in obj.items()}
78
+ elif isinstance(obj, list):
79
+ return [_remove_circular(value, refs) for _, value in enumerate(obj)]
80
+ elif isinstance(obj, tuple):
81
+ return tuple(
82
+ _remove_circular(value, refs) for _, value in enumerate(obj))
83
+ else:
84
+ return obj
85
+
86
+
87
+ def _get_write_file(severity: LogSeverity) -> _typing.TextIO:
88
+ if severity == LogSeverity.ERROR:
89
+ return _sys.stderr
90
+ return _sys.stdout
91
+
92
+
93
+ def write(entry: LogEntry) -> None:
94
+ write_file = _get_write_file(entry["severity"])
95
+ print(_json.dumps(_remove_circular(entry)), file=write_file)
96
+
97
+
98
+ def debug(*args, **kwargs) -> None:
99
+ """
100
+ Logs a debug message.
101
+ """
102
+ write(_entry_from_args(LogSeverity.DEBUG, *args, **kwargs))
103
+
104
+
105
+ def log(*args, **kwargs) -> None:
106
+ """
107
+ Logs a log message.
108
+ """
109
+ write(_entry_from_args(LogSeverity.NOTICE, *args, **kwargs))
110
+
111
+
112
+ def info(*args, **kwargs) -> None:
113
+ """
114
+ Logs an info message.
115
+ """
116
+ write(_entry_from_args(LogSeverity.INFO, *args, **kwargs))
117
+
118
+
119
+ def warn(*args, **kwargs) -> None:
120
+ """
121
+ Logs a warning message.
122
+ """
123
+ write(_entry_from_args(LogSeverity.WARNING, *args, **kwargs))
124
+
125
+
126
+ def error(*args, **kwargs) -> None:
127
+ """
128
+ Logs an error message.
129
+ """
130
+ write(_entry_from_args(LogSeverity.ERROR, *args, **kwargs))
@@ -144,18 +144,18 @@ class TextInput:
144
144
  """
145
145
  Specifies that a Param's value should be determined by prompting the user
146
146
  to type it in interactively at deploy-time. Input that does not match the provided
147
- validation_regex, if present, will be retried.
147
+ validation_regex, if present, is retried.
148
148
  """
149
149
 
150
150
  example: str | None = None
151
151
  """
152
- An example of the input required that will be displayed alongside the input prompt.
152
+ An example of the input required that is displayed alongside the input prompt.
153
153
  """
154
154
 
155
155
  validation_regex: str | None = None
156
156
  """
157
157
  Validation regex for the input.
158
- Input that does not match this regex, if present, will be retried.
158
+ Input that does not match this regex, if present, is retried.
159
159
  """
160
160
 
161
161
  validation_error_message: str | None = None
@@ -114,7 +114,7 @@ def _auth_user_record_from_token_data(token_data: dict[str, _typing.Any]):
114
114
  return AuthUserRecord(
115
115
  uid=token_data["uid"],
116
116
  email=token_data.get("email"),
117
- email_verified=token_data.get("email_verified"),
117
+ email_verified=bool(token_data.get("email_verified")),
118
118
  display_name=token_data.get("display_name"),
119
119
  photo_url=token_data.get("photo_url"),
120
120
  phone_number=token_data.get("phone_number"),
@@ -165,6 +165,7 @@ def _additional_user_info_from_token_data(token_data: dict[str, _typing.Any]):
165
165
  profile=profile,
166
166
  username=username,
167
167
  is_new_user=is_new_user,
168
+ recaptcha_score=token_data.get("recaptcha_score"),
168
169
  )
169
170
 
170
171
 
@@ -302,9 +303,35 @@ def _validate_auth_response(
302
303
  auth_response_dict["customClaims"] = auth_response["custom_claims"]
303
304
  if "session_claims" in auth_response_keys:
304
305
  auth_response_dict["sessionClaims"] = auth_response["session_claims"]
306
+ if "recaptcha_action_override" in auth_response_keys:
307
+ auth_response_dict["recaptchaActionOverride"] = auth_response[
308
+ "recaptcha_action_override"]
305
309
  return auth_response_dict
306
310
 
307
311
 
312
+ def _generate_response_payload(
313
+ auth_response_dict: dict[str, _typing.Any] | None
314
+ ) -> dict[str, _typing.Any]:
315
+ if not auth_response_dict:
316
+ return {}
317
+
318
+ formatted_auth_response = auth_response_dict.copy()
319
+ recaptcha_action_override = formatted_auth_response.pop(
320
+ "recaptchaActionOverride", None)
321
+ result = {}
322
+ update_mask = ",".join(formatted_auth_response.keys())
323
+
324
+ if len(update_mask) != 0:
325
+ result["userRecord"] = {
326
+ **formatted_auth_response, "updateMask": update_mask
327
+ }
328
+
329
+ if recaptcha_action_override is not None:
330
+ result["recaptchaActionOverride"] = recaptcha_action_override
331
+
332
+ return result
333
+
334
+
308
335
  def before_operation_handler(
309
336
  func: _typing.Callable,
310
337
  event_type: str,
@@ -329,13 +356,7 @@ def before_operation_handler(
329
356
  if not auth_response:
330
357
  return _jsonify({})
331
358
  auth_response_dict = _validate_auth_response(event_type, auth_response)
332
- update_mask = ",".join(auth_response_dict.keys())
333
- result = {
334
- "userRecord": {
335
- **auth_response_dict,
336
- "updateMask": update_mask,
337
- }
338
- }
359
+ result = _generate_response_payload(auth_response_dict)
339
360
  return _jsonify(result)
340
361
  # Disable broad exceptions lint since we want to handle all exceptions.
341
362
  # pylint: disable=broad-except
@@ -387,6 +387,8 @@ def timestamp_conversion(time: str) -> _dt.datetime:
387
387
  elif precision_timestamp == PrecisionTimestamp.SECONDS:
388
388
  return second_timestamp_conversion(time)
389
389
 
390
+ raise ValueError("Invalid timestamp")
391
+
390
392
 
391
393
  def microsecond_timestamp_conversion(time: str) -> _dt.datetime:
392
394
  """Converts a microsecond timestamp and returns a datetime object of the current time in UTC"""
@@ -157,7 +157,7 @@ def _message_handler(
157
157
  @_util.copy_func_kwargs(PubSubOptions)
158
158
  def on_message_published(**kwargs) -> _typing.Callable[[_C1], _C1]:
159
159
  """
160
- Event handler which triggers on a message being published to a Pub/Sub topic.
160
+ Event handler that triggers on a message being published to a Pub/Sub topic.
161
161
 
162
162
  Example:
163
163
 
@@ -141,8 +141,8 @@ class ConfigUpdateData:
141
141
 
142
142
  rollback_source: int | None = None
143
143
  """
144
- Only present if this version is the result of a rollback, and will be
145
- the version number of the Remote Config template that was rolled-back to.
144
+ Only present if this version is the result of a rollback, and is
145
+ the version number of the Remote Config template that was rolled back to.
146
146
  """
147
147
 
148
148
 
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  """
15
- Cloud functions to handle events from Google Cloud Storage.
15
+ Functions to handle events from Google Cloud Storage.
16
16
  """
17
17
  # pylint: disable=protected-access
18
18
  import dataclasses as _dataclasses
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Cloud functions to handle Tasks enqueued with Google Cloud Tasks."""
14
+ """Functions to handle Tasks enqueued with Google Cloud Tasks."""
15
15
 
16
16
  # pylint: disable=protected-access
17
17
  import typing as _typing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: firebase-functions
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Firebase Functions Python SDK
5
5
  Home-page: https://github.com/firebase/firebase-functions-python
6
6
  Author: Firebase Team
@@ -10,6 +10,7 @@ src/firebase_functions/eventarc_fn.py
10
10
  src/firebase_functions/firestore_fn.py
11
11
  src/firebase_functions/https_fn.py
12
12
  src/firebase_functions/identity_fn.py
13
+ src/firebase_functions/logger.py
13
14
  src/firebase_functions/options.py
14
15
  src/firebase_functions/params.py
15
16
  src/firebase_functions/pubsub_fn.py
@@ -37,6 +38,7 @@ src/firebase_functions/private/serving.py
37
38
  src/firebase_functions/private/token_verifier.py
38
39
  src/firebase_functions/private/util.py
39
40
  tests/test_eventarc_fn.py
41
+ tests/test_logger.py
40
42
  tests/test_manifest.py
41
43
  tests/test_options.py
42
44
  tests/test_params.py
@@ -0,0 +1,81 @@
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