Skip to content

Commit

Permalink
Move property subclasses snippets from cloudsite.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jerjou Cheng committed Apr 27, 2016
1 parent b283d50 commit 8b76ad7
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 0 deletions.
11 changes: 11 additions & 0 deletions appengine/ndb/property_subclasses/README.md
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 -->
106 changes: 106 additions & 0 deletions appengine/ndb/property_subclasses/my_models.py
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)
57 changes: 57 additions & 0 deletions appengine/ndb/property_subclasses/snippets.py
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
97 changes: 97 additions & 0 deletions appengine/ndb/property_subclasses/snippets_test.py
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)

0 comments on commit 8b76ad7

Please sign in to comment.