From b283d50c2deb3e74eed3d9dc2bc19ded7d105810 Mon Sep 17 00:00:00 2001 From: Jerjou Cheng Date: Tue, 26 Apr 2016 16:03:26 -0700 Subject: [PATCH 1/2] testbed clears datastore between tests. Remove the unnecessary 'client' fixture --- appengine/ndb/entities/snippets_test.py | 79 ++++++++++------------- appengine/ndb/properties/snippets_test.py | 33 +++------- 2 files changed, 44 insertions(+), 68 deletions(-) diff --git a/appengine/ndb/entities/snippets_test.py b/appengine/ndb/entities/snippets_test.py index 0b1eb6cf217..0e31641f0a1 100644 --- a/appengine/ndb/entities/snippets_test.py +++ b/appengine/ndb/entities/snippets_test.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import inspect - from google.appengine.api import users from google.appengine.ext import ndb from google.appengine.ext.ndb.google_imports import datastore_errors @@ -21,55 +19,46 @@ import snippets -@pytest.yield_fixture -def client(testbed): - yield testbed - - for name, obj in inspect.getmembers(snippets): - if inspect.isclass(obj) and issubclass(obj, ndb.Model): - ndb.delete_multi(obj.query().iter(keys_only=True)) - - -def test_create_model_using_keyword_arguments(client): +def test_create_model_using_keyword_arguments(testbed): result = snippets.create_model_using_keyword_arguments() assert isinstance(result, snippets.Account) -def test_create_model_using_attributes(client): +def test_create_model_using_attributes(testbed): result = snippets.create_model_using_attributes() assert isinstance(result, snippets.Account) -def test_create_model_using_populate(client): +def test_create_model_using_populate(testbed): result = snippets.create_model_using_populate() assert isinstance(result, snippets.Account) -def test_demonstrate_model_constructor_type_checking(client): +def test_demonstrate_model_constructor_type_checking(testbed): with pytest.raises(datastore_errors.BadValueError): snippets.demonstrate_model_constructor_type_checking() -def test_dmonstrate_model_attribute_type_checking(client): +def test_dmonstrate_model_attribute_type_checking(testbed): with pytest.raises(datastore_errors.BadValueError): snippets.dmonstrate_model_attribute_type_checking( snippets.create_model_using_keyword_arguments()) -def test_save_model(client): +def test_save_model(testbed): result = snippets.save_model( snippets.create_model_using_keyword_arguments()) assert isinstance(result, snippets.ndb.Key) -def test_get_model(client): +def test_get_model(testbed): sandy_key = snippets.save_model( snippets.create_model_using_keyword_arguments()) result = snippets.get_model(sandy_key) assert isinstance(result, snippets.Account) -def test_get_key_kind_and_id(client): +def test_get_key_kind_and_id(testbed): sandy_key = snippets.save_model( snippets.create_model_using_keyword_arguments()) kind_string, ident = snippets.get_key_kind_and_id(sandy_key) @@ -77,14 +66,14 @@ def test_get_key_kind_and_id(client): assert isinstance(ident, long) -def test_get_url_safe_key(client): +def test_get_url_safe_key(testbed): sandy_key = snippets.save_model( snippets.create_model_using_keyword_arguments()) result = snippets.get_url_safe_key(sandy_key) assert isinstance(result, str) -def test_get_model_from_url_safe_key(client): +def test_get_model_from_url_safe_key(testbed): sandy_key = snippets.save_model( snippets.create_model_using_keyword_arguments()) result = snippets.get_model_from_url_safe_key( @@ -93,7 +82,7 @@ def test_get_model_from_url_safe_key(client): assert result.username == 'Sandy' -def test_get_key_and_numeric_id_from_url_safe_key(client): +def test_get_key_and_numeric_id_from_url_safe_key(testbed): sandy_key = snippets.save_model( snippets.create_model_using_keyword_arguments()) urlsafe = snippets.get_url_safe_key(sandy_key) @@ -104,7 +93,7 @@ def test_get_key_and_numeric_id_from_url_safe_key(client): assert isinstance(kind_string, str) -def test_update_model_from_key(client): +def test_update_model_from_key(testbed): sandy = snippets.create_model_using_keyword_arguments() sandy_key = snippets.save_model(sandy) urlsafe = snippets.get_url_safe_key(sandy_key) @@ -114,64 +103,64 @@ def test_update_model_from_key(client): assert key.get().email == 'sandy@example.co.uk' -def test_delete_model(client): +def test_delete_model(testbed): sandy = snippets.create_model_using_keyword_arguments() snippets.save_model(sandy) snippets.delete_model(sandy) assert sandy.key.get() is None -def test_create_model_with_named_key(client): +def test_create_model_with_named_key(testbed): result = snippets.create_model_with_named_key() assert 'sandy@example.com' == result -def test_set_key_directly(client): +def test_set_key_directly(testbed): account = snippets.Account() snippets.set_key_directly(account) assert account.key.id() == 'sandy@example.com' -def test_create_model_with_generated_id(client): +def test_create_model_with_generated_id(testbed): result = snippets.create_model_with_generated_id() assert isinstance(result.key.id(), long) -def test_demonstrate_models_with_parent_hierarchy(client): +def test_demonstrate_models_with_parent_hierarchy(testbed): snippets.demonstrate_models_with_parent_hierarchy() -def test_equivalent_ways_to_define_key_with_parent(client): +def test_equivalent_ways_to_define_key_with_parent(testbed): snippets.equivalent_ways_to_define_key_with_parent() -def test_create_root_key(client): +def test_create_root_key(testbed): result = snippets.create_root_key() assert result.id() == 'sandy@example.com' -def test_create_model_with_parent_keys(client): +def test_create_model_with_parent_keys(testbed): result = snippets.create_model_with_parent_keys() assert result.message_text == 'Hello' -def test_get_parent_key_of_model(client): +def test_get_parent_key_of_model(testbed): initial_revision = snippets.create_model_with_parent_keys() result = snippets.get_parent_key_of_model(initial_revision) assert result.kind() == 'Message' -def test_operate_on_multiple_keys_at_once(client): +def test_operate_on_multiple_keys_at_once(testbed): snippets.operate_on_multiple_keys_at_once([ snippets.Account(email='a@a.com'), snippets.Account(email='b@b.com')]) -def test_create_expando_model(client): +def test_create_expando_model(testbed): result = snippets.create_expando_model() assert result.foo == 1 -def test_get_properties_defined_on_expando(client): +def test_get_properties_defined_on_expando(testbed): result = snippets.get_properties_defined_on_expando( snippets.create_expando_model()) assert result['foo'] is not None @@ -179,27 +168,27 @@ def test_get_properties_defined_on_expando(client): assert result['tags'] is not None -def test_create_expando_model_with_defined_properties(client): +def test_create_expando_model_with_defined_properties(testbed): result = snippets.create_expando_model_with_defined_properties() assert result.name == 'Sandy' -def test_create_expando_model_that_isnt_indexed_by_default(client): +def test_create_expando_model_that_isnt_indexed_by_default(testbed): result = snippets.create_expando_model_that_isnt_indexed_by_default() assert result['foo'] assert result['bar'] -def test_demonstrate_wrong_way_to_query_expando(client): +def test_demonstrate_wrong_way_to_query_expando(testbed): with pytest.raises(AttributeError): snippets.demonstrate_wrong_way_to_query_expando() -def test_demonstrate_right_way_to_query_expando(client): +def test_demonstrate_right_way_to_query_expando(testbed): snippets.demonstrate_right_way_to_query_expando() -def test_demonstrate_model_put_and_delete_hooks(client): +def test_demonstrate_model_put_and_delete_hooks(testbed): iterator = snippets.demonstrate_model_put_and_delete_hooks() iterator.next() assert snippets.notification == 'Gee wiz I have a new friend!' @@ -208,29 +197,29 @@ def test_demonstrate_model_put_and_delete_hooks(client): 'I have found occasion to rethink our friendship.') -def test_reserve_model_ids(client): +def test_reserve_model_ids(testbed): first, last = snippets.reserve_model_ids() assert last - first >= 99 -def test_reserve_model_ids_with_a_parent(client): +def test_reserve_model_ids_with_a_parent(testbed): first, last = snippets.reserve_model_ids_with_a_parent( snippets.Friend().key) assert last - first >= 99 -def test_construct_keys_from_range_of_reserved_ids(client): +def test_construct_keys_from_range_of_reserved_ids(testbed): result = snippets.construct_keys_from_range_of_reserved_ids( *snippets.reserve_model_ids()) assert len(result) == 100 -def test_reserve_model_ids_up_to(client): +def test_reserve_model_ids_up_to(testbed): first, last = snippets.reserve_model_ids_up_to(5) assert last - first >= 4 -def test_model_with_user(client): +def test_model_with_user(testbed): user = users.User(email='user@example.com', _user_id='123') item = snippets.ModelWithUser(user_id=user.user_id()) item.put() diff --git a/appengine/ndb/properties/snippets_test.py b/appengine/ndb/properties/snippets_test.py index f57d24c4781..0ac3e5bb8ba 100644 --- a/appengine/ndb/properties/snippets_test.py +++ b/appengine/ndb/properties/snippets_test.py @@ -12,23 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import inspect - -from google.appengine.ext import ndb -import pytest import snippets -@pytest.yield_fixture -def client(testbed): - yield testbed - - for name, obj in inspect.getmembers(snippets): - if inspect.isclass(obj) and issubclass(obj, ndb.Model): - ndb.delete_multi(obj.query().iter(keys_only=True)) - - -def test_account(client): +def test_account(testbed): account = snippets.Account( username='flan', userid=123, @@ -36,21 +23,21 @@ def test_account(client): account.put() -def test_employee(client): +def test_employee(testbed): employee = snippets.Employee( full_name='Hob Gadling', retirement_age=600) employee.put() -def test_article(client): +def test_article(testbed): article = snippets.create_article() assert article.title == 'Python versus Ruby' assert article.stars == 3 assert sorted(article.tags) == sorted(['python', 'ruby']) -def test_create_contact(client): +def test_create_contact(testbed): guido = snippets.create_contact() assert guido.name == 'Guido' addresses = guido.addresses @@ -62,7 +49,7 @@ def test_create_contact(client): assert addresses[1].city == 'SF' -def test_contact_with_local_structured_property(client): +def test_contact_with_local_structured_property(testbed): guido = snippets.create_contact_with_local_structured_property() assert guido.name == 'Guido' addresses = guido.addresses @@ -70,13 +57,13 @@ def test_contact_with_local_structured_property(client): assert addresses[1].type == 'work' -def test_create_some_entity(client): +def test_create_some_entity(testbed): entity = snippets.create_some_entity() assert entity.name == 'Nick' assert entity.name_lower == 'nick' -def test_computed_property(client): +def test_computed_property(testbed): entity = snippets.create_some_entity() entity.name = 'Nick' assert entity.name_lower == 'nick' @@ -84,7 +71,7 @@ def test_computed_property(client): assert entity.name_lower == 'nickie' -def test_create_note_store(client): +def test_create_note_store(testbed): note_stores, _ = snippets.create_note_store() assert len(note_stores) == 1 assert note_stores[0].name == 'excellent' @@ -93,7 +80,7 @@ def test_create_note_store(client): assert note_stores[0].note.when == 50 -def test_notebook(client): +def test_notebook(testbed): note1 = snippets.Note( text='Refused to die.', when=1389) @@ -113,7 +100,7 @@ def test_notebook(client): stored_notebook.put() -def test_part(client, capsys): +def test_part(testbed, capsys): snippets.print_part() stdout, _ = capsys.readouterr() assert stdout.strip() == 'RED' From 8b76ad7509372a4ebae47dc39683a3db62ffd208 Mon Sep 17 00:00:00 2001 From: Jerjou Cheng Date: Tue, 26 Apr 2016 16:04:00 -0700 Subject: [PATCH 2/2] Move property subclasses snippets from cloudsite. --- appengine/ndb/property_subclasses/README.md | 11 ++ .../ndb/property_subclasses/my_models.py | 106 ++++++++++++++++++ appengine/ndb/property_subclasses/snippets.py | 57 ++++++++++ .../ndb/property_subclasses/snippets_test.py | 97 ++++++++++++++++ 4 files changed, 271 insertions(+) create mode 100644 appengine/ndb/property_subclasses/README.md create mode 100644 appengine/ndb/property_subclasses/my_models.py create mode 100644 appengine/ndb/property_subclasses/snippets.py create mode 100644 appengine/ndb/property_subclasses/snippets_test.py diff --git a/appengine/ndb/property_subclasses/README.md b/appengine/ndb/property_subclasses/README.md new file mode 100644 index 00000000000..a1e5cb627c3 --- /dev/null +++ b/appengine/ndb/property_subclasses/README.md @@ -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. + + +These samples are used on the following documentation page: + +> https://cloud.google.com/appengine/docs/python/ndb/subclassprop + + diff --git a/appengine/ndb/property_subclasses/my_models.py b/appengine/ndb/property_subclasses/my_models.py new file mode 100644 index 00000000000..ef1a6e8bdd7 --- /dev/null +++ b/appengine/ndb/property_subclasses/my_models.py @@ -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) diff --git a/appengine/ndb/property_subclasses/snippets.py b/appengine/ndb/property_subclasses/snippets.py new file mode 100644 index 00000000000..50c04a192d5 --- /dev/null +++ b/appengine/ndb/property_subclasses/snippets.py @@ -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 diff --git a/appengine/ndb/property_subclasses/snippets_test.py b/appengine/ndb/property_subclasses/snippets_test.py new file mode 100644 index 00000000000..0ef4b63fcec --- /dev/null +++ b/appengine/ndb/property_subclasses/snippets_test.py @@ -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)