-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move property subclasses snippets from cloudsite.
- Loading branch information
Jerjou Cheng
committed
Apr 27, 2016
1 parent
b283d50
commit 8b76ad7
Showing
4 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
## App Engine Datastore NDB Property Subclasses Samples | ||
|
||
This contains snippets used in the NDB property subclasses documentation, | ||
demonstrating various operation on ndb property subclasses. | ||
|
||
<!-- auto-doc-link --> | ||
These samples are used on the following documentation page: | ||
|
||
> https://cloud.google.com/appengine/docs/python/ndb/subclassprop | ||
<!-- end-auto-doc-link --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# Copyright 2016 Google Inc. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from datetime import date | ||
|
||
from google.appengine.ext import ndb | ||
|
||
|
||
class LongIntegerProperty(ndb.StringProperty): | ||
def _validate(self, value): | ||
if not isinstance(value, (int, long)): | ||
raise TypeError('expected an integer, got %s' % repr(value)) | ||
|
||
def _to_base_type(self, value): | ||
return str(value) # Doesn't matter if it's an int or a long | ||
|
||
def _from_base_type(self, value): | ||
return long(value) # Always return a long | ||
|
||
|
||
class BoundedLongIntegerProperty(ndb.StringProperty): | ||
def __init__(self, bits, **kwds): | ||
assert isinstance(bits, int) | ||
assert bits > 0 and bits % 4 == 0 # Make it simple to use hex | ||
super(BoundedLongIntegerProperty, self).__init__(**kwds) | ||
self._bits = bits | ||
|
||
def _validate(self, value): | ||
assert -(2 ** (self._bits - 1)) <= value < 2 ** (self._bits - 1) | ||
|
||
def _to_base_type(self, value): | ||
# convert from signed -> unsigned | ||
if value < 0: | ||
value += 2 ** self._bits | ||
assert 0 <= value < 2 ** self._bits | ||
# Return number as a zero-padded hex string with correct number of | ||
# digits: | ||
return '%0*x' % (self._bits // 4, value) | ||
|
||
def _from_base_type(self, value): | ||
value = int(value, 16) | ||
if value >= 2 ** (self._bits - 1): | ||
value -= 2 ** self._bits | ||
return value | ||
|
||
|
||
# Define an entity class holding some long integers. | ||
class MyModel(ndb.Model): | ||
name = ndb.StringProperty() | ||
abc = LongIntegerProperty(default=0) | ||
xyz = LongIntegerProperty(repeated=True) | ||
|
||
|
||
class FuzzyDate(object): | ||
def __init__(self, first, last=None): | ||
assert isinstance(first, date) | ||
assert last is None or isinstance(last, date) | ||
self.first = first | ||
self.last = last or first | ||
|
||
|
||
class FuzzyDateModel(ndb.Model): | ||
first = ndb.DateProperty() | ||
last = ndb.DateProperty() | ||
|
||
|
||
class FuzzyDateProperty(ndb.StructuredProperty): | ||
def __init__(self, **kwds): | ||
super(FuzzyDateProperty, self).__init__(FuzzyDateModel, **kwds) | ||
|
||
def _validate(self, value): | ||
assert isinstance(value, FuzzyDate) | ||
|
||
def _to_base_type(self, value): | ||
return FuzzyDateModel(first=value.first, last=value.last) | ||
|
||
def _from_base_type(self, value): | ||
return FuzzyDate(value.first, value.last) | ||
|
||
|
||
class MaybeFuzzyDateProperty(FuzzyDateProperty): | ||
def _validate(self, value): | ||
if isinstance(value, date): | ||
return FuzzyDate(value) # Must return the converted value! | ||
# Otherwise, return None and leave validation to the base class | ||
|
||
|
||
# Class to record historic people and events in their life. | ||
class HistoricPerson(ndb.Model): | ||
name = ndb.StringProperty() | ||
birth = FuzzyDateProperty() | ||
death = FuzzyDateProperty() | ||
# Parallel lists: | ||
event_dates = FuzzyDateProperty(repeated=True) | ||
event_names = ndb.StringProperty(repeated=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Copyright 2016 Google Inc. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from datetime import date | ||
|
||
import my_models | ||
|
||
|
||
def create_entity(): | ||
# Create an entity and write it to the Datastore. | ||
entity = my_models.MyModel(name='booh', xyz=[10**100, 6**666]) | ||
assert entity.abc == 0 | ||
key = entity.put() | ||
return key | ||
|
||
|
||
def read_and_update_entity(key): | ||
# Read an entity back from the Datastore and update it. | ||
entity = key.get() | ||
entity.abc += 1 | ||
entity.xyz.append(entity.abc//3) | ||
entity.put() | ||
|
||
|
||
def query_entity(): | ||
# Query for a MyModel entity whose xyz contains 6**666. | ||
# (NOTE: using ordering operations don't work, but == does.) | ||
results = my_models.MyModel.query( | ||
my_models.MyModel.xyz == 6**666).fetch(10) | ||
return results | ||
|
||
|
||
def create_and_query_columbus(): | ||
columbus = my_models.HistoricPerson( | ||
name='Christopher Columbus', | ||
birth=my_models.FuzzyDate(date(1451, 8, 22), date(1451, 10, 31)), | ||
death=my_models.FuzzyDate(date(1506, 5, 20)), | ||
event_dates=[my_models.FuzzyDate( | ||
date(1492, 1, 1), date(1492, 12, 31))], | ||
event_names=['Discovery of America']) | ||
columbus.put() | ||
|
||
# Query for historic people born no later than 1451. | ||
results = my_models.HistoricPerson.query( | ||
my_models.HistoricPerson.birth.last <= date(1451, 12, 31)).fetch() | ||
return results |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# Copyright 2016 Google Inc. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import datetime | ||
|
||
from google.appengine.ext import ndb | ||
import my_models | ||
import pytest | ||
import snippets | ||
|
||
|
||
def test_create_entity(testbed): | ||
assert my_models.MyModel.query().count() == 0 | ||
snippets.create_entity() | ||
entities = my_models.MyModel.query().fetch() | ||
assert len(entities) == 1 | ||
assert entities[0].name == 'booh' | ||
|
||
|
||
def test_read_and_update_entity(testbed): | ||
key = snippets.create_entity() | ||
entities = my_models.MyModel.query().fetch() | ||
assert len(entities) == 1 | ||
assert entities[0].abc == 0 | ||
len_xyz = len(entities[0].xyz) | ||
|
||
snippets.read_and_update_entity(key) | ||
entities = my_models.MyModel.query().fetch() | ||
assert len(entities) == 1 | ||
assert entities[0].abc == 1 | ||
assert len(entities[0].xyz) == len_xyz + 1 | ||
|
||
|
||
def test_query_entity(testbed): | ||
results = snippets.query_entity() | ||
assert len(results) == 0 | ||
|
||
snippets.create_entity() | ||
results = snippets.query_entity() | ||
assert len(results) == 1 | ||
|
||
|
||
def test_create_columbus(testbed): | ||
entities = snippets.create_and_query_columbus() | ||
assert len(entities) == 1 | ||
assert entities[0].name == 'Christopher Columbus' | ||
assert (entities[0].birth.first < entities[0].birth.last < | ||
entities[0].death.first) | ||
|
||
|
||
def test_long_integer_property(testbed): | ||
with pytest.raises(TypeError): | ||
my_models.MyModel( | ||
name='not integer test', | ||
xyz=['not integer']) | ||
|
||
|
||
def test_bounded_long_integer_property(testbed): | ||
class TestBoundedLongIntegerProperty(ndb.Model): | ||
num = my_models.BoundedLongIntegerProperty(4) | ||
|
||
# Test out of the bounds | ||
with pytest.raises(AssertionError): | ||
TestBoundedLongIntegerProperty(num=0xF) | ||
with pytest.raises(AssertionError): | ||
TestBoundedLongIntegerProperty(num=-0xF) | ||
|
||
# This should work | ||
working_instance = TestBoundedLongIntegerProperty(num=0b111) | ||
assert working_instance.num == 0b111 | ||
working_instance.num = 0b10 | ||
assert working_instance.num == 2 | ||
|
||
|
||
def test_maybe_fuzzy_date_property(testbed): | ||
class TestMaybeFuzzyDateProperty(ndb.Model): | ||
first_date = my_models.MaybeFuzzyDateProperty() | ||
second_date = my_models.MaybeFuzzyDateProperty() | ||
|
||
two_types_of_dates = TestMaybeFuzzyDateProperty( | ||
first_date=my_models.FuzzyDate( | ||
datetime.date(1984, 2, 27), datetime.date(1984, 2, 29)), | ||
second_date=datetime.date(2015, 6, 27)) | ||
|
||
assert isinstance(two_types_of_dates.first_date, my_models.FuzzyDate) | ||
assert isinstance(two_types_of_dates.second_date, my_models.FuzzyDate) |