Skip to content

Commit

Permalink
fix: Handle int for DateTimeProperty (googleapis#285)
Browse files Browse the repository at this point in the history
In Datastore, projection queries involving entities with DateTime
properties return integer timestamps instead of `datetime.datetime`
objects. This fix handles that case and returns `datetime.datetime`
objects regardless of the query type.

Fixes googleapis#261.
  • Loading branch information
Chris Rossi authored Jan 9, 2020
1 parent 9478306 commit 2fe5be3
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 1 deletion.
10 changes: 9 additions & 1 deletion google/cloud/ndb/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3601,14 +3601,22 @@ def _from_base_type(self, value):
"""Convert a value from the "base" value type for this property.
Args:
value (datetime.datetime): The value to be converted.
value (Union[int, datetime.datetime]): The value to be converted.
The value will be `int` for entities retrieved by a projection
query and is a timestamp as the number of nanoseconds since the
epoch.
Returns:
Optional[datetime.datetime]: If ``tzinfo`` is set on this property,
the value converted to the timezone in ``tzinfo``. Otherwise
returns the value without ``tzinfo`` or ``None`` if value did
not have ``tzinfo`` set.
"""
if isinstance(value, six.integer_types):
# Projection query, value is integer nanoseconds
seconds = value / 1e6
value = datetime.datetime.fromtimestamp(seconds, pytz.utc)

if self._tzinfo is not None:
return value.astimezone(self._tzinfo)

Expand Down
34 changes: 34 additions & 0 deletions tests/system/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
System tests for queries.
"""

import datetime
import functools
import operator

import grpc
import pytest
import pytz

import test_utils.system

Expand Down Expand Up @@ -194,6 +196,38 @@ class SomeKind(ndb.Model):
results[1].bar


@pytest.mark.usefixtures("client_context")
def test_projection_datetime(ds_entity):
"""Regression test for Issue #261
https://github.com/googleapis/python-ndb/issues/261
"""
entity_id = test_utils.system.unique_resource_id()
ds_entity(
KIND,
entity_id,
foo=datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC),
)
entity_id = test_utils.system.unique_resource_id()
ds_entity(
KIND,
entity_id,
foo=datetime.datetime(2010, 5, 12, 2, 43, tzinfo=pytz.UTC),
)

class SomeKind(ndb.Model):
foo = ndb.DateTimeProperty()
bar = ndb.StringProperty()

query = SomeKind.query(projection=("foo",))
results = eventually(query.fetch, _length_equals(2))

results = sorted(results, key=operator.attrgetter("foo"))

assert results[0].foo == datetime.datetime(2010, 5, 12, 2, 42)
assert results[1].foo == datetime.datetime(2010, 5, 12, 2, 43)


@pytest.mark.usefixtures("client_context")
def test_distinct_on(ds_entity):
for i in range(6):
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2707,6 +2707,14 @@ def test__from_base_type_convert_timezone():
2010, 5, 11, 20, tzinfo=timezone(-4)
)

@staticmethod
def test__from_base_type_int():
prop = model.DateTimeProperty(name="dt_val")
value = 1273632120000000
assert prop._from_base_type(value) == datetime.datetime(
2010, 5, 12, 2, 42
)

@staticmethod
def test__to_base_type_noop():
prop = model.DateTimeProperty(name="dt_val", tzinfo=timezone(-4))
Expand Down

0 comments on commit 2fe5be3

Please sign in to comment.