Skip to content

Commit 6739d47

Browse files
authored
Merge branch 'main' into feat/logger
2 parents e7d3bb0 + 5159a30 commit 6739d47

File tree

5 files changed

+107
-64
lines changed

5 files changed

+107
-64
lines changed

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()

src/firebase_functions/private/_alerts_fn.py

+18-44
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
# pylint: disable=protected-access,cyclic-import
1717
import typing as _typing
18-
import datetime as _dt
1918
import cloudevents.http as _ce
19+
import util as _util
2020
from firebase_functions.alerts import FirebaseAlertData
2121

2222
from functions_framework import logging as _logging
@@ -102,14 +102,10 @@ def new_nonfatal_issue_payload_from_ce_payload(payload: dict):
102102

103103
def regression_alert_payload_from_ce_payload(payload: dict):
104104
from firebase_functions.alerts.crashlytics_fn import RegressionAlertPayload
105-
return RegressionAlertPayload(
106-
type=payload["type"],
107-
issue=issue_from_ce_payload(payload["issue"]),
108-
resolve_time=_dt.datetime.strptime(
109-
payload["resolveTime"],
110-
"%Y-%m-%dT%H:%M:%S.%f%z",
111-
),
112-
)
105+
return RegressionAlertPayload(type=payload["type"],
106+
issue=issue_from_ce_payload(payload["issue"]),
107+
resolve_time=_util.timestamp_conversion(
108+
payload["resolveTime"]))
113109

114110

115111
def trending_issue_details_from_ce_payload(payload: dict):
@@ -125,10 +121,7 @@ def trending_issue_details_from_ce_payload(payload: dict):
125121
def stability_digest_payload_from_ce_payload(payload: dict):
126122
from firebase_functions.alerts.crashlytics_fn import StabilityDigestPayload
127123
return StabilityDigestPayload(
128-
digest_date=_dt.datetime.strptime(
129-
payload["digestDate"],
130-
"%Y-%m-%dT%H:%M:%S.%f%z",
131-
),
124+
digest_date=_util.timestamp_conversion(payload["digestDate"]),
132125
trending_issues=[
133126
trending_issue_details_from_ce_payload(issue)
134127
for issue in payload["trendingIssues"]
@@ -139,10 +132,7 @@ def velocity_alert_payload_from_ce_payload(payload: dict):
139132
from firebase_functions.alerts.crashlytics_fn import VelocityAlertPayload
140133
return VelocityAlertPayload(
141134
issue=issue_from_ce_payload(payload["issue"]),
142-
create_time=_dt.datetime.strptime(
143-
payload["createTime"],
144-
"%Y-%m-%dT%H:%M:%S.%f%z",
145-
),
135+
create_time=_util.timestamp_conversion(payload["createTime"]),
146136
crash_count=payload["crashCount"],
147137
crash_percentage=payload["crashPercentage"],
148138
first_version=payload["firstVersion"],
@@ -186,14 +176,9 @@ def firebase_alert_data_from_ce(event_dict: dict,) -> FirebaseAlertData:
186176
_logging.warning(f"Unhandled Firebase Alerts alert type: {alert_type}")
187177

188178
return FirebaseAlertData(
189-
create_time=_dt.datetime.strptime(
190-
event_dict["createTime"],
191-
"%Y-%m-%dT%H:%M:%S.%f%z",
192-
),
193-
end_time=_dt.datetime.strptime(
194-
event_dict["endTime"],
195-
"%Y-%m-%dT%H:%M:%S.%f%z",
196-
) if "endTime" in event_dict else None,
179+
create_time=_util.timestamp_conversion(event_dict["createTime"]),
180+
end_time=_util.timestamp_conversion(event_dict["endTime"])
181+
if "endTime" in event_dict else None,
197182
payload=alert_payload,
198183
)
199184

@@ -204,25 +189,14 @@ def event_from_ce_helper(raw: _ce.CloudEvent, cls, app_id=True):
204189
event_dict = {**event_data, **event_attributes}
205190
alert_type: str = event_dict["alerttype"]
206191
event_kwargs = {
207-
"alert_type":
208-
alert_type,
209-
"data":
210-
firebase_alert_data_from_ce(event_dict),
211-
"id":
212-
event_dict["id"],
213-
"source":
214-
event_dict["source"],
215-
"specversion":
216-
event_dict["specversion"],
217-
"subject":
218-
event_dict["subject"] if "subject" in event_dict else None,
219-
"time":
220-
_dt.datetime.strptime(
221-
event_dict["time"],
222-
"%Y-%m-%dT%H:%M:%S.%f%z",
223-
),
224-
"type":
225-
event_dict["type"],
192+
"alert_type": alert_type,
193+
"data": firebase_alert_data_from_ce(event_dict),
194+
"id": event_dict["id"],
195+
"source": event_dict["source"],
196+
"specversion": event_dict["specversion"],
197+
"subject": event_dict["subject"] if "subject" in event_dict else None,
198+
"time": _util.timestamp_conversion(event_dict["time"]),
199+
"type": event_dict["type"],
226200
}
227201
if app_id:
228202
event_kwargs["app_id"] = event_dict.get("appid")

src/firebase_functions/private/_identity_fn.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def _auth_user_record_from_token_data(token_data: dict[str, _typing.Any]):
114114
return AuthUserRecord(
115115
uid=token_data["uid"],
116116
email=token_data.get("email"),
117-
email_verified=token_data["email_verified"],
117+
email_verified=token_data.get("email_verified"),
118118
display_name=token_data.get("display_name"),
119119
photo_url=token_data.get("photo_url"),
120120
phone_number=token_data.get("phone_number"),

src/firebase_functions/private/util.py

+38-3
Original file line numberDiff line numberDiff line change
@@ -337,20 +337,55 @@ def nanoseconds_timestamp_conversion(time: str) -> _dt.datetime:
337337
return event_time
338338

339339

340-
def is_precision_timestamp(time: str) -> bool:
340+
def second_timestamp_conversion(time: str) -> _dt.datetime:
341+
"""Converts a second timestamp and returns a datetime object of the current time in UTC"""
342+
return _dt.datetime.strptime(
343+
time,
344+
"%Y-%m-%dT%H:%M:%S%z",
345+
)
346+
347+
348+
class PrecisionTimestamp(_enum.Enum):
349+
"""
350+
The status of a token.
351+
"""
352+
353+
NANOSECONDS = "NANOSECONDS"
354+
355+
MICROSECONDS = "MICROSECONDS"
356+
357+
SECONDS = "SECONDS"
358+
359+
360+
def get_precision_timestamp(time: str) -> PrecisionTimestamp:
341361
"""Return a bool which indicates if the timestamp is in nanoseconds"""
342362
# Split the string into date-time and fraction of second
343363
try:
344364
_, s_fraction = time.split(".")
345365
except ValueError:
346-
return False # If there's no decimal, it's not a nanosecond timestamp.
366+
return PrecisionTimestamp.SECONDS
347367

348368
# Split the fraction from the timezone specifier ('Z' or 'z')
349369
s_fraction, _ = s_fraction.split(
350370
"Z") if "Z" in s_fraction else s_fraction.split("z")
351371

352372
# If the fraction is more than 6 digits long, it's a nanosecond timestamp
353-
return len(s_fraction) > 6
373+
if len(s_fraction) > 6:
374+
return PrecisionTimestamp.NANOSECONDS
375+
else:
376+
return PrecisionTimestamp.MICROSECONDS
377+
378+
379+
def timestamp_conversion(time: str) -> _dt.datetime:
380+
"""Converts a timestamp and returns a datetime object of the current time in UTC"""
381+
precision_timestamp = get_precision_timestamp(time)
382+
383+
if precision_timestamp == PrecisionTimestamp.NANOSECONDS:
384+
return nanoseconds_timestamp_conversion(time)
385+
elif precision_timestamp == PrecisionTimestamp.MICROSECONDS:
386+
return microsecond_timestamp_conversion(time)
387+
elif precision_timestamp == PrecisionTimestamp.SECONDS:
388+
return second_timestamp_conversion(time)
354389

355390

356391
def microsecond_timestamp_conversion(time: str) -> _dt.datetime:

tests/test_util.py

+49-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
Internal utils tests.
1616
"""
1717
from os import environ, path
18-
from firebase_functions.private.util import firebase_config, microsecond_timestamp_conversion, nanoseconds_timestamp_conversion, is_precision_timestamp, normalize_path, deep_merge
18+
from firebase_functions.private.util import firebase_config, microsecond_timestamp_conversion, nanoseconds_timestamp_conversion, get_precision_timestamp, normalize_path, deep_merge, PrecisionTimestamp, second_timestamp_conversion
1919
import datetime as _dt
2020

2121
test_bucket = "python-functions-testing.appspot.com"
@@ -81,6 +81,24 @@ def test_nanosecond_conversion():
8181
input_timestamp) == expected_datetime
8282

8383

84+
def test_second_conversion():
85+
"""
86+
Testing seconds_timestamp_conversion works as intended
87+
"""
88+
timestamps = [
89+
("2023-01-01T12:34:56Z", "2023-01-01T12:34:56Z"),
90+
("2023-02-14T14:37:52Z", "2023-02-14T14:37:52Z"),
91+
("2023-03-21T06:43:58Z", "2023-03-21T06:43:58Z"),
92+
("2023-10-06T07:00:00Z", "2023-10-06T07:00:00Z"),
93+
]
94+
95+
for input_timestamp, expected_output in timestamps:
96+
expected_datetime = _dt.datetime.strptime(expected_output,
97+
"%Y-%m-%dT%H:%M:%SZ")
98+
expected_datetime = expected_datetime.replace(tzinfo=_dt.timezone.utc)
99+
assert second_timestamp_conversion(input_timestamp) == expected_datetime
100+
101+
84102
def test_is_nanoseconds_timestamp():
85103
"""
86104
Testing is_nanoseconds_timestamp works as intended
@@ -95,19 +113,40 @@ def test_is_nanoseconds_timestamp():
95113
nanosecond_timestamp3 = "2023-03-21T06:43:58.564738291Z"
96114
nanosecond_timestamp4 = "2023-08-15T22:22:22.222222222Z"
97115

98-
assert is_precision_timestamp(microsecond_timestamp1) is False
99-
assert is_precision_timestamp(microsecond_timestamp2) is False
100-
assert is_precision_timestamp(microsecond_timestamp3) is False
101-
assert is_precision_timestamp(microsecond_timestamp4) is False
102-
assert is_precision_timestamp(nanosecond_timestamp1) is True
103-
assert is_precision_timestamp(nanosecond_timestamp2) is True
104-
assert is_precision_timestamp(nanosecond_timestamp3) is True
105-
assert is_precision_timestamp(nanosecond_timestamp4) is True
116+
second_timestamp1 = "2023-01-01T12:34:56Z"
117+
second_timestamp2 = "2023-02-14T14:37:52Z"
118+
second_timestamp3 = "2023-03-21T06:43:58Z"
119+
second_timestamp4 = "2023-08-15T22:22:22Z"
120+
121+
assert get_precision_timestamp(
122+
microsecond_timestamp1) is PrecisionTimestamp.MICROSECONDS
123+
assert get_precision_timestamp(
124+
microsecond_timestamp2) is PrecisionTimestamp.MICROSECONDS
125+
assert get_precision_timestamp(
126+
microsecond_timestamp3) is PrecisionTimestamp.MICROSECONDS
127+
assert get_precision_timestamp(
128+
microsecond_timestamp4) is PrecisionTimestamp.MICROSECONDS
129+
assert get_precision_timestamp(
130+
nanosecond_timestamp1) is PrecisionTimestamp.NANOSECONDS
131+
assert get_precision_timestamp(
132+
nanosecond_timestamp2) is PrecisionTimestamp.NANOSECONDS
133+
assert get_precision_timestamp(
134+
nanosecond_timestamp3) is PrecisionTimestamp.NANOSECONDS
135+
assert get_precision_timestamp(
136+
nanosecond_timestamp4) is PrecisionTimestamp.NANOSECONDS
137+
assert get_precision_timestamp(
138+
second_timestamp1) is PrecisionTimestamp.SECONDS
139+
assert get_precision_timestamp(
140+
second_timestamp2) is PrecisionTimestamp.SECONDS
141+
assert get_precision_timestamp(
142+
second_timestamp3) is PrecisionTimestamp.SECONDS
143+
assert get_precision_timestamp(
144+
second_timestamp4) is PrecisionTimestamp.SECONDS
106145

107146

108147
def test_normalize_document_path():
109148
"""
110-
Testing "document" path passed to Firestore event listener
149+
Testing "document" path passed to Firestore event listener
111150
is normalized.
112151
"""
113152
test_path = "/test/document/"

0 commit comments

Comments
 (0)