So I am trying to update a datetime field in cloud firestore via cloud function as follows:
transaction.update(doc_ref, {'dateTimeField1': dateTimeValue})
Google sends datetime objects as string in event param of the cloud function in the format %Y-%m-%dT%H:%M:%SZ
or %Y-%m-%dT%H:%M:%S.%fZ
.
For Example: 2019-01-25T15:25:03.881Z
I am converting it to datetime object as follows:
try:
datetime_obj = datetime.datetime.strptime(datetime_obj, '%Y-%m-%dT%H:%M:%S.%fZ')
except:
datetime_obj = datetime.datetime.strptime(datetime_obj, '%Y-%m-%dT%H:%M:%SZ')
datetime_obj = datetime_obj.replace(tzinfo=timezone('UTC'))
But when I try to perform the operation I am greeted with the following error:
AttributeError: _nanosecond
Traceback:
File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/batch.py", line 112, in update
reference._document_path, field_updates, option
File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/_helpers.py", line 822, in pbs_for_update
update_pb = extractor.get_update_pb(document_path)
File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/_helpers.py", line 459, in get_update_pb
name=document_path, fields=encode_dict(self.set_fields)
File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/_helpers.py", line 215, in encode_dict
return {key: encode_value(value) for key, value in six.iteritems(values_dict)}
File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/_helpers.py", line 215, in <dictcomp>
return {key: encode_value(value) for key, value in six.iteritems(values_dict)}
File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/_helpers.py", line 169, in encode_value
return document_pb2.Value(timestamp_value=value.timestamp_pb())
File "/env/local/lib/python3.7/site-packages/google/api_core/datetime_helpers.py", line 278, in timestamp_pb
nanos = self._nanosecond or self.microsecond * 1000
AttributeError: _nanosecond
Are datetimes allowed to be set via transactions or I am missing something here?
EDIT:
code snippet:
@firestore.transactional
def update_datetime_field(transaction, doc_ref, datetime_value):
try:
datetime_obj = datetime.datetime.strptime(datetime_value, '%Y-%m-%dT%H:%M:%S.%fZ')
except:
datetime_obj = datetime.datetime.strptime(datetime_value, '%Y-%m-%dT%H:%M:%SZ')
datetime_obj = datetime_obj.replace(tzinfo=timezone('UTC'))
# Example of datetime_obj -> datetime.datetime(2019, 1, 25, 15, 25, 3, 881000, tzinfo=<UTC>)
transaction.update(doc_ref, {'datetimeField1': datetime_obj})
return True
More info:
collection1/document1/collection2/document2
EDIT 2:
A better complete picture:
from firebase_admin import credentials, firestore
# initialize firebase admin sdk
creds = credentials.ApplicationDefault()
firebase_admin.initialize_app(creds,{'projectId': 'myProjectId'})
@firestore.transactional
def update_datetime_field(transaction, doc_ref, datetime_value):
try:
datetime_obj = datetime.datetime.strptime(datetime_value, '%Y-%m-%dT%H:%M:%S.%fZ')
except:
datetime_obj = datetime.datetime.strptime(datetime_value, '%Y-%m-%dT%H:%M:%SZ')
datetime_obj = datetime_obj.replace(tzinfo=timezone('UTC'))
# Example of datetime_obj -> datetime.datetime(2019, 1, 25, 15, 25, 3, 881000, tzinfo=<UTC>)
transaction.update(doc_ref, {'datetimeField1': datetime_obj})
return True
def update_datetime_in_transaction(event, context):
datetime_value = event['value']['fields']['datetimeField1']['timestampValue']
# this looks something like 2019-01-25T15:25:03.881Z
# prepare document reference to document
doc_ref = prepare_doc_ref(event, context)
# update_datetime_field
client = firestore.client()
transaction = client.transaction()
update_datetime_field(transaction, doc_ref, datetime_value)
return True
EDIT 3:
So firestore python sdk expects the _nanosecond
attribute which is not available in the python standard library's datetime at the moment (will be added in future. more details here)
So after examining their codebase, I found a class called DatetimeWithNanoseconds
which adds the nanoseconds support to traditional datetime object.
The code for the class (datetime_helpers.py file in google/api_core) is as follows (some parts deleted intentionally for brevity):
class DatetimeWithNanoseconds(datetime.datetime):
"""Track nanosecond in addition to normal datetime attrs.
Nanosecond can be passed only as a keyword argument.
"""
__slots__ = ('_nanosecond',)
@classmethod
def from_rfc3339(cls, stamp):
with_nanos = _RFC3339_NANOS.match(stamp)
if with_nanos is None:
raise ValueError(
'Timestamp: {}, does not match pattern: {}'.format(
stamp, _RFC3339_NANOS.pattern))
bare = datetime.datetime.strptime(
with_nanos.group('no_fraction'), _RFC3339_NO_FRACTION)
fraction = with_nanos.group('nanos')
if fraction is None:
nanos = 0
else:
scale = 9 - len(fraction)
nanos = int(fraction) * (10 ** scale)
return cls(bare.year, bare.month, bare.day,
bare.hour, bare.minute, bare.second,
nanosecond=nanos, tzinfo=pytz.UTC)
So now, I can use this class instead of datetime.datetime
to parse the datetime sent as string in the event param of the cloud functions using the DatetimeWithNanoseconds.from_rfc3339(timestamp)
method.
Example:
from google.api_core.datetime_helpers import DatetimeWithNanoseconds
d1 = DatetimeWithNanoseconds.from_rfc3339('2019-01-25T15:25:03.881Z')
print(d1)
# DatetimeWithNanoseconds(2019, 1, 25, 15, 25, 3, 881000, tzinfo=<UTC>)
The class also has rfc3339()
method to give you the string representation.
Example:
d1.rfc3339()
# 2019-01-25T15:25:03.881Z
Alternative solution:
You can use pandas.Timestamp()
instead of DatetimeWithNanoseconds.from_rfc3339()
too.
Example:
import pandas as pd
d1 = pd.Timestamp('2019-01-25T15:25:03.881Z')
print(d1)
# Timestamp('2019-01-25 15:25:03.881000+0000', tz='UTC')
I recommend using DatetimeWithNanoseconds
as it comes along with the sdk and you needn't add an extra dependency of pandas in the requirements.txt
which can increase the invocation latency during the cold start. More details here.
Hopefully this helps.