firebase-functions 0.4.0__tar.gz → 0.4.2__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.4.0 → firebase_functions-0.4.2}/PKG-INFO +1 -1
  2. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/__init__.py +1 -1
  3. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/firestore_fn.py +6 -6
  4. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/https_fn.py +4 -7
  5. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/logger.py +4 -4
  6. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/util.py +12 -18
  7. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/pubsub_fn.py +7 -0
  8. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/tasks_fn.py +44 -6
  9. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions.egg-info/PKG-INFO +1 -1
  10. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_pubsub_fn.py +29 -0
  11. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_tasks_fn.py +0 -35
  12. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/LICENSE +0 -0
  13. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/README.md +0 -0
  14. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/pyproject.toml +0 -0
  15. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/setup.cfg +0 -0
  16. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/setup.py +0 -0
  17. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/alerts/__init__.py +0 -0
  18. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/alerts/app_distribution_fn.py +0 -0
  19. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/alerts/billing_fn.py +0 -0
  20. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/alerts/crashlytics_fn.py +0 -0
  21. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/alerts/performance_fn.py +0 -0
  22. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/alerts_fn.py +0 -0
  23. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/core.py +0 -0
  24. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/db_fn.py +0 -0
  25. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/eventarc_fn.py +0 -0
  26. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/identity_fn.py +0 -0
  27. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/options.py +0 -0
  28. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/params.py +0 -0
  29. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/__init__.py +0 -0
  30. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/_alerts_fn.py +0 -0
  31. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/_identity_fn.py +0 -0
  32. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/manifest.py +0 -0
  33. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/path_pattern.py +0 -0
  34. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/serving.py +0 -0
  35. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/private/token_verifier.py +0 -0
  36. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/py.typed +0 -0
  37. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/remote_config_fn.py +0 -0
  38. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/scheduler_fn.py +0 -0
  39. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/storage_fn.py +0 -0
  40. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions/test_lab_fn.py +0 -0
  41. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions.egg-info/SOURCES.txt +0 -0
  42. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions.egg-info/dependency_links.txt +0 -0
  43. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions.egg-info/requires.txt +0 -0
  44. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/src/firebase_functions.egg-info/top_level.txt +0 -0
  45. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_db.py +0 -0
  46. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_eventarc_fn.py +0 -0
  47. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_firestore_fn.py +0 -0
  48. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_https_fn.py +0 -0
  49. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_identity_fn.py +0 -0
  50. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_init.py +0 -0
  51. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_logger.py +0 -0
  52. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_manifest.py +0 -0
  53. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_options.py +0 -0
  54. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_params.py +0 -0
  55. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_path_pattern.py +0 -0
  56. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_remote_config_fn.py +0 -0
  57. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_scheduler_fn.py +0 -0
  58. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_storage_fn.py +0 -0
  59. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/tests/test_test_lab_fn.py +0 -0
  60. {firebase_functions-0.4.0 → firebase_functions-0.4.2}/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.0
3
+ Version: 0.4.2
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.4.0"
18
+ __version__ = "0.4.2"
@@ -134,8 +134,6 @@ def _firestore_endpoint_handler(
134
134
  event_namespace = event_attributes["namespace"]
135
135
  event_document = event_attributes["document"]
136
136
  event_database = event_attributes["database"]
137
- event_auth_type = event_attributes["authtype"]
138
- event_auth_id = event_attributes["authid"]
139
137
 
140
138
  time = event_attributes["time"]
141
139
  event_time = _util.timestamp_conversion(time)
@@ -208,6 +206,8 @@ def _firestore_endpoint_handler(
208
206
  func = _core._with_init(func)
209
207
 
210
208
  if event_type.endswith(".withAuthContext"):
209
+ event_auth_type = event_attributes["authtype"]
210
+ event_auth_id = event_attributes["authid"]
211
211
  database_event_with_auth_context = AuthEvent(**vars(database_event),
212
212
  auth_type=event_auth_type,
213
213
  auth_id=event_auth_id)
@@ -270,7 +270,7 @@ def on_document_written_with_auth_context(**kwargs
270
270
  ) -> _typing.Callable[[_C1], _C1]:
271
271
  """
272
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
273
+ This trigger will also provide the authentication context of the principal who triggered
274
274
  the event.
275
275
 
276
276
  Example:
@@ -369,7 +369,7 @@ def on_document_updated_with_auth_context(**kwargs
369
369
  ) -> _typing.Callable[[_C1], _C1]:
370
370
  """
371
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
372
+ This trigger will also provide the authentication context of the principal who triggered
373
373
  the event.
374
374
 
375
375
  Example:
@@ -468,7 +468,7 @@ def on_document_created_with_auth_context(**kwargs
468
468
  ) -> _typing.Callable[[_C2], _C2]:
469
469
  """
470
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
471
+ This trigger will also provide the authentication context of the principal who triggered
472
472
  the event.
473
473
 
474
474
  Example:
@@ -567,7 +567,7 @@ def on_document_deleted_with_auth_context(**kwargs
567
567
  ) -> _typing.Callable[[_C2], _C2]:
568
568
  """
569
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
570
+ This trigger will also provide the authentication context of the principal who triggered
571
571
  the event.
572
572
 
573
573
  Example:
@@ -352,10 +352,8 @@ _C1 = _typing.Callable[[Request], Response]
352
352
  _C2 = _typing.Callable[[CallableRequest[_typing.Any]], _typing.Any]
353
353
 
354
354
 
355
- def _on_call_handler(func: _C2,
356
- request: Request,
357
- enforce_app_check: bool,
358
- verify_token: bool = True) -> Response:
355
+ def _on_call_handler(func: _C2, request: Request,
356
+ enforce_app_check: bool) -> Response:
359
357
  try:
360
358
  if not _util.valid_on_call_request(request):
361
359
  _logging.error("Invalid request, unable to process.")
@@ -365,8 +363,7 @@ def _on_call_handler(func: _C2,
365
363
  data=_json.loads(request.data)["data"],
366
364
  )
367
365
 
368
- token_status = _util.on_call_check_tokens(request,
369
- verify_token=verify_token)
366
+ token_status = _util.on_call_check_tokens(request)
370
367
 
371
368
  if token_status.auth == _util.OnCallTokenState.INVALID:
372
369
  raise HttpsError(FunctionsErrorCode.UNAUTHENTICATED,
@@ -420,7 +417,7 @@ def _on_call_handler(func: _C2,
420
417
  def on_request(**kwargs) -> _typing.Callable[[_C1], _C1]:
421
418
  """
422
419
  Handler which handles HTTPS requests.
423
- Requires a function that takes a ``Request`` and ``Response`` object,
420
+ Requires a function that takes a ``Request`` and ``Response`` object,
424
421
  the same signature as a Flask app.
425
422
 
426
423
  Example:
@@ -44,9 +44,8 @@ def _entry_from_args(severity: LogSeverity, *args, **kwargs) -> LogEntry:
44
44
  """
45
45
 
46
46
  message: str = " ".join([
47
- value
48
- if isinstance(value, str) else _json.dumps(_remove_circular(value))
49
- for value in args
47
+ value if isinstance(value, str) else _json.dumps(
48
+ _remove_circular(value), ensure_ascii=False) for value in args
50
49
  ])
51
50
 
52
51
  other: _typing.Dict[str, _typing.Any] = {
@@ -95,7 +94,8 @@ def _get_write_file(severity: LogSeverity) -> _typing.TextIO:
95
94
 
96
95
  def write(entry: LogEntry) -> None:
97
96
  write_file = _get_write_file(entry["severity"])
98
- print(_json.dumps(_remove_circular(entry)), file=write_file)
97
+ print(_json.dumps(_remove_circular(entry), ensure_ascii=False),
98
+ file=write_file)
99
99
 
100
100
 
101
101
  def debug(*args, **kwargs) -> None:
@@ -212,11 +212,10 @@ class _OnCallTokenVerification:
212
212
 
213
213
 
214
214
  def _on_call_check_auth_token(
215
- request: _Request,
216
- verify_token: bool = True,
215
+ request: _Request
217
216
  ) -> None | _typing.Literal[OnCallTokenState.INVALID] | dict[str, _typing.Any]:
218
217
  """
219
- Validates the auth token in a callable request.
218
+ Validates the auth token in a callable request.
220
219
  If verify_token is False, the token will be decoded without verification.
221
220
  """
222
221
  authorization = request.headers.get("Authorization")
@@ -227,10 +226,7 @@ def _on_call_check_auth_token(
227
226
  return OnCallTokenState.INVALID
228
227
  try:
229
228
  id_token = authorization.replace("Bearer ", "")
230
- if verify_token:
231
- auth_token = _auth.verify_id_token(id_token)
232
- else:
233
- auth_token = _unsafe_decode_id_token(id_token)
229
+ auth_token = _auth.verify_id_token(id_token)
234
230
  return auth_token
235
231
  # pylint: disable=broad-except
236
232
  except Exception as err:
@@ -273,25 +269,23 @@ def _unsafe_decode_id_token(token: str):
273
269
  return payload
274
270
 
275
271
 
276
- def on_call_check_tokens(request: _Request,
277
- verify_token: bool = True) -> _OnCallTokenVerification:
272
+ def on_call_check_tokens(request: _Request) -> _OnCallTokenVerification:
278
273
  """Check tokens"""
279
274
  verifications = _OnCallTokenVerification()
280
275
 
281
- auth_token = _on_call_check_auth_token(request, verify_token=verify_token)
276
+ auth_token = _on_call_check_auth_token(request)
282
277
  if auth_token is None:
283
278
  verifications.auth = OnCallTokenState.MISSING
284
279
  elif isinstance(auth_token, dict):
285
280
  verifications.auth = OnCallTokenState.VALID
286
281
  verifications.auth_token = auth_token
287
282
 
288
- if verify_token:
289
- app_token = _on_call_check_app_token(request)
290
- if app_token is None:
291
- verifications.app = OnCallTokenState.MISSING
292
- elif isinstance(app_token, dict):
293
- verifications.app = OnCallTokenState.VALID
294
- verifications.app_token = app_token
283
+ app_token = _on_call_check_app_token(request)
284
+ if app_token is None:
285
+ verifications.app = OnCallTokenState.MISSING
286
+ elif isinstance(app_token, dict):
287
+ verifications.app = OnCallTokenState.VALID
288
+ verifications.app_token = app_token
295
289
 
296
290
  log_payload = {
297
291
  **verifications.as_dict(),
@@ -301,7 +295,7 @@ def on_call_check_tokens(request: _Request,
301
295
  }
302
296
 
303
297
  errs = []
304
- if verify_token and verifications.app == OnCallTokenState.INVALID:
298
+ if verifications.app == OnCallTokenState.INVALID:
305
299
  errs.append(("AppCheck token was rejected.", log_payload))
306
300
 
307
301
  if verifications.auth == OnCallTokenState.INVALID:
@@ -105,6 +105,13 @@ def _message_handler(
105
105
  data = event_dict["data"]
106
106
  message_dict = data["message"]
107
107
 
108
+ # if no microseconds are present, we should set them to 0 to prevent parsing from failing
109
+ if "." not in event_dict["time"]:
110
+ event_dict["time"] = event_dict["time"].replace("Z", ".000000Z")
111
+ if "." not in message_dict["publish_time"]:
112
+ message_dict["publish_time"] = message_dict["publish_time"].replace(
113
+ "Z", ".000000Z")
114
+
108
115
  time = _dt.datetime.strptime(
109
116
  event_dict["time"],
110
117
  "%Y-%m-%dT%H:%M:%S.%f%z",
@@ -16,14 +16,55 @@
16
16
  # pylint: disable=protected-access
17
17
  import typing as _typing
18
18
  import functools as _functools
19
+ import dataclasses as _dataclasses
20
+ import json as _json
19
21
 
20
- from flask import Request, Response
22
+ from flask import Request, Response, make_response as _make_response, jsonify as _jsonify
21
23
 
24
+ import firebase_functions.core as _core
22
25
  import firebase_functions.options as _options
23
26
  import firebase_functions.private.util as _util
24
- from firebase_functions.https_fn import CallableRequest, _on_call_handler
27
+ from firebase_functions.https_fn import CallableRequest, HttpsError, FunctionsErrorCode
28
+
29
+ from functions_framework import logging as _logging
25
30
 
26
31
  _C = _typing.Callable[[CallableRequest[_typing.Any]], _typing.Any]
32
+ _C1 = _typing.Callable[[Request], Response]
33
+ _C2 = _typing.Callable[[CallableRequest[_typing.Any]], _typing.Any]
34
+
35
+
36
+ def _on_call_handler(func: _C2, request: Request) -> Response:
37
+ try:
38
+ if not _util.valid_on_call_request(request):
39
+ _logging.error("Invalid request, unable to process.")
40
+ raise HttpsError(FunctionsErrorCode.INVALID_ARGUMENT, "Bad Request")
41
+ context: CallableRequest = CallableRequest(
42
+ raw_request=request,
43
+ data=_json.loads(request.data)["data"],
44
+ )
45
+
46
+ instance_id = request.headers.get("Firebase-Instance-ID-Token")
47
+ if instance_id is not None:
48
+ # Validating the token requires an http request, so we don't do it.
49
+ # If the user wants to use it for something, it will be validated then.
50
+ # Currently, the only real use case for this token is for sending
51
+ # pushes with FCM. In that case, the FCM APIs will validate the token.
52
+ context = _dataclasses.replace(
53
+ context,
54
+ instance_id_token=request.headers.get(
55
+ "Firebase-Instance-ID-Token"),
56
+ )
57
+ result = _core._with_init(func)(context)
58
+ return _jsonify(result=result)
59
+ # Disable broad exceptions lint since we want to handle all exceptions here
60
+ # and wrap as an HttpsError.
61
+ # pylint: disable=broad-except
62
+ except Exception as err:
63
+ if not isinstance(err, HttpsError):
64
+ _logging.error("Unhandled error: %s", err)
65
+ err = HttpsError(FunctionsErrorCode.INTERNAL, "INTERNAL")
66
+ status = err._http_error_code.status
67
+ return _make_response(_jsonify(error=err._as_dict()), status)
27
68
 
28
69
 
29
70
  @_util.copy_func_kwargs(_options.TaskQueueOptions)
@@ -53,10 +94,7 @@ def on_task_dispatched(**kwargs) -> _typing.Callable[[_C], Response]:
53
94
 
54
95
  @_functools.wraps(func)
55
96
  def on_task_dispatched_wrapped(request: Request) -> Response:
56
- return _on_call_handler(func,
57
- request,
58
- enforce_app_check=False,
59
- verify_token=False)
97
+ return _on_call_handler(func, request)
60
98
 
61
99
  _util.set_func_endpoint_attr(
62
100
  on_task_dispatched_wrapped,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: firebase-functions
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Firebase Functions Python SDK
5
5
  Home-page: https://github.com/firebase/firebase-functions-python
6
6
  Author: Firebase Team
@@ -129,3 +129,32 @@ class TestPubSub(unittest.TestCase):
129
129
  _message_handler(func, raw_event)
130
130
 
131
131
  self.assertEqual("world", hello)
132
+
133
+ def test_datetime_without_mircroseconds_doesnt_throw(self):
134
+ time = "2023-03-11T13:25:37Z"
135
+ raw_event = _CloudEvent(
136
+ attributes={
137
+ "id": "test-message",
138
+ "source": "https://example.com/pubsub",
139
+ "specversion": "1.0",
140
+ "time": time,
141
+ "type": "com.example.pubsub.message",
142
+ },
143
+ data={
144
+ "message": {
145
+ "attributes": {
146
+ "key": "value"
147
+ },
148
+ "data": "eyJ0ZXN0IjogInZhbHVlIn0=",
149
+ "message_id": "message-id-123",
150
+ "publish_time": time,
151
+ },
152
+ "subscription": "my-subscription",
153
+ },
154
+ )
155
+ try:
156
+ _message_handler(lambda _: None, raw_event)
157
+ # pylint: disable=broad-except
158
+ except Exception:
159
+ self.fail(
160
+ "Datetime without microseconds should not throw an exception")
@@ -71,41 +71,6 @@ class TestTasks(unittest.TestCase):
71
71
  '{"result":"Hello World"}\n',
72
72
  )
73
73
 
74
- def test_token_is_decoded(self):
75
- """
76
- Test that the token is decoded instead of verifying auth first.
77
- """
78
- app = Flask(__name__)
79
-
80
- @on_task_dispatched()
81
- def example(request: CallableRequest[object]) -> str:
82
- auth = request.auth
83
- # Make mypy happy
84
- if auth is None:
85
- self.fail("Auth is None")
86
- return "No Auth"
87
- self.assertEqual(auth.token["sub"], "firebase")
88
- self.assertEqual(auth.token["name"], "John Doe")
89
- return "Hello World"
90
-
91
- with app.test_request_context("/"):
92
- # pylint: disable=line-too-long
93
- test_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmaXJlYmFzZSIsIm5hbWUiOiJKb2huIERvZSJ9.74A24Y821E7CZx8aYCsCKo0Y-W0qXwqME-14QlEMcB0"
94
- environ = EnvironBuilder(
95
- method="POST",
96
- headers={
97
- "Authorization": f"Bearer {test_token}"
98
- },
99
- json={
100
- "data": {
101
- "test": "value"
102
- },
103
- },
104
- ).get_environ()
105
- request = Request(environ)
106
- response = example(request)
107
- self.assertEqual(response.status_code, 200)
108
-
109
74
  def test_calls_init(self):
110
75
  hello = None
111
76