Skip to content

Commit cbc91a1

Browse files
authored
Merge branch 'main' into supported-region-as-a-str-enum
2 parents 968f126 + 7f83050 commit cbc91a1

21 files changed

+423
-117
lines changed

Diff for: .github/workflows/release.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
# 4. the title prefix 'chore: Release '.
7575
if: >
7676
github.event.pull_request.merged &&
77-
github.ref == 'main' &&
77+
github.ref == 'refs/heads/main' &&
7878
contains(github.event.pull_request.labels.*.name, 'release:publish') &&
7979
startsWith(github.event.pull_request.title, 'chore: Release ')
8080

Diff for: docs/generate.sh

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ PY_MODULES='firebase_functions.core
9393
firebase_functions.firestore_fn
9494
firebase_functions.https_fn
9595
firebase_functions.identity_fn
96+
firebase_functions.logger
9697
firebase_functions.options
9798
firebase_functions.params
9899
firebase_functions.pubsub_fn

Diff for: src/firebase_functions/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
Firebase Functions for Python.
1616
"""
1717

18-
__version__ = "0.1.1"
18+
__version__ = "0.2.0"

Diff for: src/firebase_functions/alerts_fn.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class AlertEvent(_CloudEvent[T]):
4646
app_id: str | None
4747
"""
4848
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.
5050
"""
5151

5252

@@ -66,7 +66,7 @@ def on_alert_published(
6666
**kwargs
6767
) -> _typing.Callable[[OnAlertPublishedCallable], OnAlertPublishedCallable]:
6868
"""
69-
Event handler which triggers when a Firebase Alerts event is published.
69+
Event handler that triggers when a Firebase Alerts event is published.
7070
7171
Example:
7272

Diff for: src/firebase_functions/core.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
class CloudEvent(_typing.Generic[T]):
2626
"""
2727
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.
2929
"""
3030

3131
specversion: str

Diff for: src/firebase_functions/db_fn.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class Event(_core.CloudEvent[T]):
6161

6262
params: dict[str, str]
6363
"""
64-
An dict containing the values of the path patterns.
64+
A dict containing the values of the path patterns.
6565
Only named capture groups are populated - {key}, {key=*}, {key=**}
6666
"""
6767

@@ -125,7 +125,7 @@ def _db_endpoint_handler(
125125
@_util.copy_func_kwargs(DatabaseOptions)
126126
def on_value_written(**kwargs) -> _typing.Callable[[_C1], _C1]:
127127
"""
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.
129129
130130
Example:
131131
@@ -175,7 +175,7 @@ def on_value_written_wrapped(raw: _ce.CloudEvent):
175175
@_util.copy_func_kwargs(DatabaseOptions)
176176
def on_value_updated(**kwargs) -> _typing.Callable[[_C1], _C1]:
177177
"""
178-
Event handler which triggers when data is updated in Realtime Database.
178+
Event handler that triggers when data is updated in Realtime Database.
179179
180180
Example:
181181
@@ -225,7 +225,7 @@ def on_value_updated_wrapped(raw: _ce.CloudEvent):
225225
@_util.copy_func_kwargs(DatabaseOptions)
226226
def on_value_created(**kwargs) -> _typing.Callable[[_C2], _C2]:
227227
"""
228-
Event handler which triggers when data is created in Realtime Database.
228+
Event handler that triggers when data is created in Realtime Database.
229229
230230
Example:
231231
@@ -275,7 +275,7 @@ def on_value_created_wrapped(raw: _ce.CloudEvent):
275275
@_util.copy_func_kwargs(DatabaseOptions)
276276
def on_value_deleted(**kwargs) -> _typing.Callable[[_C2], _C2]:
277277
"""
278-
Event handler which triggers when data is deleted in Realtime Database.
278+
Event handler that triggers when data is deleted in Realtime Database.
279279
280280
Example:
281281

Diff for: src/firebase_functions/firestore_fn.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,7 @@ def _firestore_endpoint_handler(
112112
event_database = event_attributes["database"]
113113

114114
time = event_attributes["time"]
115-
is_nanoseconds = _util.is_precision_timestamp(time)
116-
117-
if is_nanoseconds:
118-
event_time = _util.nanoseconds_timestamp_conversion(time)
119-
else:
120-
event_time = _util.microsecond_timestamp_conversion(time)
115+
event_time = _util.timestamp_conversion(time)
121116

122117
if _DEFAULT_APP_NAME not in _apps:
123118
initialize_app()

Diff for: src/firebase_functions/https_fn.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ class FunctionsErrorCode(str, _enum.Enum):
119119

120120
INTERNAL = "internal"
121121
"""
122-
Internal errors. Means some invariants expected by
122+
Internal errors. Means some invariants expected by the
123123
underlying system have been broken. If you see one of these errors,
124-
something is very broken.
124+
something is severely broken.
125125
"""
126126

127127
UNAVAILABLE = "unavailable"

Diff for: src/firebase_functions/identity_fn.py

+21-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414
"""Cloud functions to handle Eventarc events."""
1515

16-
# pylint: disable=protected-access
16+
# pylint: disable=protected-access,cyclic-import
1717
import typing as _typing
1818
import functools as _functools
1919
import datetime as _dt
@@ -36,7 +36,7 @@ class AuthUserInfo:
3636
"""The user identifier for the linked provider."""
3737

3838
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)."""
4040

4141
display_name: str | None = None
4242
"""The display name for the linked provider."""
@@ -59,7 +59,7 @@ class AuthUserMetadata:
5959
creation_time: _dt.datetime
6060
"""The date the user was created."""
6161

62-
last_sign_in_time: _dt.datetime
62+
last_sign_in_time: _typing.Optional[_dt.datetime]
6363
"""The date the user last signed in."""
6464

6565

@@ -110,7 +110,7 @@ class AuthMultiFactorSettings:
110110
@_dataclasses.dataclass(frozen=True)
111111
class AuthUserRecord:
112112
"""
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.
114114
"""
115115

116116
uid: str
@@ -123,7 +123,7 @@ class AuthUserRecord:
123123
The user's primary email, if set.
124124
"""
125125

126-
email_verified: bool
126+
email_verified: bool | None
127127
"""
128128
Whether or not the user's primary email is verified.
129129
"""
@@ -155,7 +155,7 @@ class AuthUserRecord:
155155

156156
provider_data: list[AuthUserInfo]
157157
"""
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.
159159
"""
160160

161161
password_hash: str | None
@@ -203,6 +203,9 @@ class AdditionalUserInfo:
203203
is_new_user: bool
204204
"""A boolean indicating if the user is new or not."""
205205

206+
recaptcha_score: float | None
207+
"""The user's reCAPTCHA score, if available."""
208+
206209

207210
@_dataclasses.dataclass(frozen=True)
208211
class Credential:
@@ -243,12 +246,12 @@ class AuthBlockingEvent:
243246

244247
data: AuthUserRecord
245248
"""
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.
247250
"""
248251

249252
locale: str | None
250253
"""
251-
The application locale. You can set the locale using the client SDK,
254+
The application locale. You can set the locale using the client SDK,
252255
or by passing the locale header in the REST API.
253256
Example: 'fr' or 'sv-SE'
254257
"""
@@ -282,6 +285,12 @@ class AuthBlockingEvent:
282285
The time the event was triggered."""
283286

284287

288+
RecaptchaActionOptions = _typing.Literal["ALLOW", "BLOCK"]
289+
"""
290+
The reCAPTCHA action options.
291+
"""
292+
293+
285294
class BeforeCreateResponse(_typing.TypedDict, total=False):
286295
"""
287296
The handler response type for 'before_user_created' blocking events.
@@ -302,6 +311,8 @@ class BeforeCreateResponse(_typing.TypedDict, total=False):
302311
custom_claims: dict[str, _typing.Any] | None
303312
"""The user's custom claims object if available."""
304313

314+
recaptcha_action_override: RecaptchaActionOptions | None
315+
305316

306317
class BeforeSignInResponse(BeforeCreateResponse, total=False):
307318
"""
@@ -345,7 +356,7 @@ def example(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInRes
345356
:param \\*\\*kwargs: Options.
346357
:type \\*\\*kwargs: as :exc:`firebase_functions.options.BlockingOptions`
347358
:rtype: :exc:`typing.Callable`
348-
\\[ \\[ :exc:`firebase_functions.identity_fn.AuthBlockingEvent` \\],
359+
\\[ \\[ :exc:`firebase_functions.identity_fn.AuthBlockingEvent` \\],
349360
:exc:`firebase_functions.identity_fn.BeforeSignInResponse` \\| `None` \\]
350361
A function that takes a AuthBlockingEvent and optionally returns BeforeSignInResponse.
351362
"""
@@ -399,7 +410,7 @@ def example(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateRes
399410
:param \\*\\*kwargs: Options.
400411
:type \\*\\*kwargs: as :exc:`firebase_functions.options.BlockingOptions`
401412
:rtype: :exc:`typing.Callable`
402-
\\[ \\[ :exc:`firebase_functions.identity_fn.AuthBlockingEvent` \\],
413+
\\[ \\[ :exc:`firebase_functions.identity_fn.AuthBlockingEvent` \\],
403414
:exc:`firebase_functions.identity_fn.BeforeCreateResponse` \\| `None` \\]
404415
A function that takes a AuthBlockingEvent and optionally returns BeforeCreateResponse.
405416
"""

Diff for: src/firebase_functions/logger.py

+130
Original file line numberDiff line numberDiff line change
@@ -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))

Diff for: src/firebase_functions/params.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,18 @@ class TextInput:
144144
"""
145145
Specifies that a Param's value should be determined by prompting the user
146146
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.
148148
"""
149149

150150
example: str | None = None
151151
"""
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.
153153
"""
154154

155155
validation_regex: str | None = None
156156
"""
157157
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.
159159
"""
160160

161161
validation_error_message: str | None = None

0 commit comments

Comments
 (0)