Skip to content

Commit 8b76ad7

Browse files
author
Jerjou Cheng
committed
Move property subclasses snippets from cloudsite.
1 parent b283d50 commit 8b76ad7

File tree

4 files changed

+271
-0
lines changed

4 files changed

+271
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## App Engine Datastore NDB Property Subclasses Samples
2+
3+
This contains snippets used in the NDB property subclasses documentation,
4+
demonstrating various operation on ndb property subclasses.
5+
6+
<!-- auto-doc-link -->
7+
These samples are used on the following documentation page:
8+
9+
> https://cloud.google.com/appengine/docs/python/ndb/subclassprop
10+
11+
<!-- end-auto-doc-link -->
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright 2016 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from datetime import date
16+
17+
from google.appengine.ext import ndb
18+
19+
20+
class LongIntegerProperty(ndb.StringProperty):
21+
def _validate(self, value):
22+
if not isinstance(value, (int, long)):
23+
raise TypeError('expected an integer, got %s' % repr(value))
24+
25+
def _to_base_type(self, value):
26+
return str(value) # Doesn't matter if it's an int or a long
27+
28+
def _from_base_type(self, value):
29+
return long(value) # Always return a long
30+
31+
32+
class BoundedLongIntegerProperty(ndb.StringProperty):
33+
def __init__(self, bits, **kwds):
34+
assert isinstance(bits, int)
35+
assert bits > 0 and bits % 4 == 0 # Make it simple to use hex
36+
super(BoundedLongIntegerProperty, self).__init__(**kwds)
37+
self._bits = bits
38+
39+
def _validate(self, value):
40+
assert -(2 ** (self._bits - 1)) <= value < 2 ** (self._bits - 1)
41+
42+
def _to_base_type(self, value):
43+
# convert from signed -> unsigned
44+
if value < 0:
45+
value += 2 ** self._bits
46+
assert 0 <= value < 2 ** self._bits
47+
# Return number as a zero-padded hex string with correct number of
48+
# digits:
49+
return '%0*x' % (self._bits // 4, value)
50+
51+
def _from_base_type(self, value):
52+
value = int(value, 16)
53+
if value >= 2 ** (self._bits - 1):
54+
value -= 2 ** self._bits
55+
return value
56+
57+
58+
# Define an entity class holding some long integers.
59+
class MyModel(ndb.Model):
60+
name = ndb.StringProperty()
61+
abc = LongIntegerProperty(default=0)
62+
xyz = LongIntegerProperty(repeated=True)
63+
64+
65+
class FuzzyDate(object):
66+
def __init__(self, first, last=None):
67+
assert isinstance(first, date)
68+
assert last is None or isinstance(last, date)
69+
self.first = first
70+
self.last = last or first
71+
72+
73+
class FuzzyDateModel(ndb.Model):
74+
first = ndb.DateProperty()
75+
last = ndb.DateProperty()
76+
77+
78+
class FuzzyDateProperty(ndb.StructuredProperty):
79+
def __init__(self, **kwds):
80+
super(FuzzyDateProperty, self).__init__(FuzzyDateModel, **kwds)
81+
82+
def _validate(self, value):
83+
assert isinstance(value, FuzzyDate)
84+
85+
def _to_base_type(self, value):
86+
return FuzzyDateModel(first=value.first, last=value.last)
87+
88+
def _from_base_type(self, value):
89+
return FuzzyDate(value.first, value.last)
90+
91+
92+
class MaybeFuzzyDateProperty(FuzzyDateProperty):
93+
def _validate(self, value):
94+
if isinstance(value, date):
95+
return FuzzyDate(value) # Must return the converted value!
96+
# Otherwise, return None and leave validation to the base class
97+
98+
99+
# Class to record historic people and events in their life.
100+
class HistoricPerson(ndb.Model):
101+
name = ndb.StringProperty()
102+
birth = FuzzyDateProperty()
103+
death = FuzzyDateProperty()
104+
# Parallel lists:
105+
event_dates = FuzzyDateProperty(repeated=True)
106+
event_names = ndb.StringProperty(repeated=True)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2016 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from datetime import date
16+
17+
import my_models
18+
19+
20+
def create_entity():
21+
# Create an entity and write it to the Datastore.
22+
entity = my_models.MyModel(name='booh', xyz=[10**100, 6**666])
23+
assert entity.abc == 0
24+
key = entity.put()
25+
return key
26+
27+
28+
def read_and_update_entity(key):
29+
# Read an entity back from the Datastore and update it.
30+
entity = key.get()
31+
entity.abc += 1
32+
entity.xyz.append(entity.abc//3)
33+
entity.put()
34+
35+
36+
def query_entity():
37+
# Query for a MyModel entity whose xyz contains 6**666.
38+
# (NOTE: using ordering operations don't work, but == does.)
39+
results = my_models.MyModel.query(
40+
my_models.MyModel.xyz == 6**666).fetch(10)
41+
return results
42+
43+
44+
def create_and_query_columbus():
45+
columbus = my_models.HistoricPerson(
46+
name='Christopher Columbus',
47+
birth=my_models.FuzzyDate(date(1451, 8, 22), date(1451, 10, 31)),
48+
death=my_models.FuzzyDate(date(1506, 5, 20)),
49+
event_dates=[my_models.FuzzyDate(
50+
date(1492, 1, 1), date(1492, 12, 31))],
51+
event_names=['Discovery of America'])
52+
columbus.put()
53+
54+
# Query for historic people born no later than 1451.
55+
results = my_models.HistoricPerson.query(
56+
my_models.HistoricPerson.birth.last <= date(1451, 12, 31)).fetch()
57+
return results
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright 2016 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import datetime
16+
17+
from google.appengine.ext import ndb
18+
import my_models
19+
import pytest
20+
import snippets
21+
22+
23+
def test_create_entity(testbed):
24+
assert my_models.MyModel.query().count() == 0
25+
snippets.create_entity()
26+
entities = my_models.MyModel.query().fetch()
27+
assert len(entities) == 1
28+
assert entities[0].name == 'booh'
29+
30+
31+
def test_read_and_update_entity(testbed):
32+
key = snippets.create_entity()
33+
entities = my_models.MyModel.query().fetch()
34+
assert len(entities) == 1
35+
assert entities[0].abc == 0
36+
len_xyz = len(entities[0].xyz)
37+
38+
snippets.read_and_update_entity(key)
39+
entities = my_models.MyModel.query().fetch()
40+
assert len(entities) == 1
41+
assert entities[0].abc == 1
42+
assert len(entities[0].xyz) == len_xyz + 1
43+
44+
45+
def test_query_entity(testbed):
46+
results = snippets.query_entity()
47+
assert len(results) == 0
48+
49+
snippets.create_entity()
50+
results = snippets.query_entity()
51+
assert len(results) == 1
52+
53+
54+
def test_create_columbus(testbed):
55+
entities = snippets.create_and_query_columbus()
56+
assert len(entities) == 1
57+
assert entities[0].name == 'Christopher Columbus'
58+
assert (entities[0].birth.first < entities[0].birth.last <
59+
entities[0].death.first)
60+
61+
62+
def test_long_integer_property(testbed):
63+
with pytest.raises(TypeError):
64+
my_models.MyModel(
65+
name='not integer test',
66+
xyz=['not integer'])
67+
68+
69+
def test_bounded_long_integer_property(testbed):
70+
class TestBoundedLongIntegerProperty(ndb.Model):
71+
num = my_models.BoundedLongIntegerProperty(4)
72+
73+
# Test out of the bounds
74+
with pytest.raises(AssertionError):
75+
TestBoundedLongIntegerProperty(num=0xF)
76+
with pytest.raises(AssertionError):
77+
TestBoundedLongIntegerProperty(num=-0xF)
78+
79+
# This should work
80+
working_instance = TestBoundedLongIntegerProperty(num=0b111)
81+
assert working_instance.num == 0b111
82+
working_instance.num = 0b10
83+
assert working_instance.num == 2
84+
85+
86+
def test_maybe_fuzzy_date_property(testbed):
87+
class TestMaybeFuzzyDateProperty(ndb.Model):
88+
first_date = my_models.MaybeFuzzyDateProperty()
89+
second_date = my_models.MaybeFuzzyDateProperty()
90+
91+
two_types_of_dates = TestMaybeFuzzyDateProperty(
92+
first_date=my_models.FuzzyDate(
93+
datetime.date(1984, 2, 27), datetime.date(1984, 2, 29)),
94+
second_date=datetime.date(2015, 6, 27))
95+
96+
assert isinstance(two_types_of_dates.first_date, my_models.FuzzyDate)
97+
assert isinstance(two_types_of_dates.second_date, my_models.FuzzyDate)

0 commit comments

Comments
 (0)