From 5ae6a4123828c0060cdc30c14346d00c1e6ff641 Mon Sep 17 00:00:00 2001 From: Jerjou Cheng Date: Tue, 12 Apr 2016 17:14:23 -0700 Subject: [PATCH] Move ndb entities code snippets into github --- appengine/ndb/entities/README.md | 11 + appengine/ndb/entities/snippets.py | 263 ++++++++++++++++++++++++ appengine/ndb/entities/snippets_test.py | 205 ++++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 appengine/ndb/entities/README.md create mode 100644 appengine/ndb/entities/snippets.py create mode 100644 appengine/ndb/entities/snippets_test.py diff --git a/appengine/ndb/entities/README.md b/appengine/ndb/entities/README.md new file mode 100644 index 000000000000..c1c4adef6db3 --- /dev/null +++ b/appengine/ndb/entities/README.md @@ -0,0 +1,11 @@ +## App Engine Datastore NDB Entities Samples + +This contains snippets used in the NDB entity documentation, demonstrating +various operation on ndb entities. + + +These samples are used on the following documentation page: + +> https://cloud.google.com/appengine/docs/python/ndb/entities + + diff --git a/appengine/ndb/entities/snippets.py b/appengine/ndb/entities/snippets.py new file mode 100644 index 000000000000..0634e842e7b0 --- /dev/null +++ b/appengine/ndb/entities/snippets.py @@ -0,0 +1,263 @@ +# 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 google.appengine.ext import ndb + + +class Account(ndb.Model): + username = ndb.StringProperty() + userid = ndb.IntegerProperty() + email = ndb.StringProperty() + + +def create_model_using_keyword_arguments(): + sandy = Account( + username='Sandy', userid=123, email='sandy@example.com') + return sandy + + +def create_model_using_attributes(): + sandy = Account() + sandy.username = 'Sandy' + sandy.userid = 123 + sandy.email = 'sandy@example.com' + return sandy + + +def demonstrate_model_constructor_type_checking(): + bad = Account( + username='Sandy', userid='not integer') # raises an exception + return bad + + +def dmonstrate_model_attribute_type_checking(sandy): + sandy.username = 42 # raises an exception + + +def save_model(sandy): + sandy_key = sandy.put() + return sandy_key + + +def get_url_safe_key(sandy_key): + url_string = sandy_key.urlsafe() + return url_string + + +def get_model_from_url_safe_key(url_string): + sandy_key = ndb.Key(urlsafe=url_string) + sandy = sandy_key.get() + return sandy + + +def get_key_and_numeric_id_from_url_safe_key(url_string): + key = ndb.Key(urlsafe=url_string) + kind_string = key.kind() + ident = key.id() + return key, ident, kind_string + + +def update_model_from_key(key): + sandy = key.get() + sandy.email = 'sandy@example.co.uk' + sandy.put() + + +def delete_model(sandy): + sandy.key.delete() + + +def create_model_with_named_key(): + account = Account( + username='Sandy', userid=1234, email='sandy@example.com', + id='sandy@example.com') + + return account.key.id() # returns 'sandy@example.com' + + +def set_key_directly(account): + account.key = ndb.Key('Account', 'sandy@example.com') + + # You can also use the model class object itself, rather than its name, + # to specify the entity's kind: + account.key = ndb.Key(Account, 'sandy@example.com') + + +def create_model_with_generated_id(): + # note: no id kwarg + account = Account(username='Sandy', userid=1234, email='sandy@example.com') + account.put() + # Account.key will now have a key of the form: ndb.Key(Account, 71321839) + # where the value 71321839 was generated by Datastore for us. + return account + + +class Revision(ndb.Model): + message_text = ndb.StringProperty() + + +def demonstrate_models_with_parent_hierarchy(): + ndb.Key('Account', 'sandy@example.com', 'Message', 123, 'Revision', '1') + ndb.Key('Account', 'sandy@example.com', 'Message', 123, 'Revision', '2') + ndb.Key('Account', 'larry@example.com', 'Message', 456, 'Revision', '1') + ndb.Key('Account', 'larry@example.com', 'Message', 789, 'Revision', '2') + + +def equivalent_ways_to_define_key_with_parent(): + ndb.Key('Account', 'sandy@example.com', 'Message', 123, 'Revision', '1') + + ndb.Key('Revision', '1', parent=ndb.Key( + 'Account', 'sandy@example.com', 'Message', 123)) + + ndb.Key('Revision', '1', parent=ndb.Key( + 'Message', 123, parent=ndb.Key('Account', 'sandy@example.com'))) + + sandy_key = ndb.Key(Account, 'sandy@example.com') + return sandy_key + + +def create_model_with_parent_keys(): + account_key = ndb.Key(Account, 'sandy@example.com') + + # Ask Datastore to allocate an ID. + new_id = ndb.Model.allocate_ids(size=1, parent=account_key)[0] + + # Datastore returns us an integer ID that we can use to create the message + # key + message_key = ndb.Key('Message', new_id, parent=account_key) + + # Now we can put the message into Datastore + initial_revision = Revision( + message_text='Hello', id='1', parent=message_key) + initial_revision.put() + + return initial_revision + + +def get_parent_key_of_model(initial_revision): + message_key = initial_revision.key.parent() + return message_key + + +def operate_on_multiple_keys_at_once(list_of_entities): + list_of_keys = ndb.put_multi(list_of_entities) + list_of_entities = ndb.get_multi(list_of_keys) + ndb.delete_multi(list_of_keys) + + +class Mine(ndb.Expando): + pass + + +def create_expando_model(): + e = Mine() + e.foo = 1 + e.bar = 'blah' + e.tags = ['exp', 'and', 'oh'] + e.put() + + return e + + +def get_properties_defined_on_expando(e): + return e._properties + # { + # 'foo': GenericProperty('foo'), + # 'bar': GenericProperty('bar'), + # 'tags': GenericProperty('tags', repeated=True) + # } + + +class FlexEmployee(ndb.Expando): + name = ndb.StringProperty() + age = ndb.IntegerProperty() + + +def create_expando_model_with_defined_properties(): + employee = FlexEmployee(name='Sandy', location='SF') + return employee + + +class Specialized(ndb.Expando): + _default_indexed = False + + +def create_expando_model_that_isnt_indexed_by_default(): + e = Specialized(foo='a', bar=['b']) + return e._properties + # { + # 'foo': GenericProperty('foo', indexed=False), + # 'bar': GenericProperty('bar', indexed=False, repeated=True) + # } + + +def demonstrate_wrong_way_to_query_expando(): + FlexEmployee.query(FlexEmployee.location == 'SF') + + +def demonstrate_right_way_to_query_expando(): + FlexEmployee.query(ndb.GenericProperty('location') == 'SF') + + +notification = None + + +def _notify(message): + global notification + notification = message + + +class Friend(ndb.Model): + name = ndb.StringProperty() + + def _pre_put_hook(self): + _notify('Gee wiz I have a new friend!') + + @classmethod + def _post_delete_hook(cls, key, future): + _notify('I have found occasion to rethink our friendship.') + + +def demonstrate_model_put_and_delete_hooks(): + f = Friend() + f.name = 'Carole King' + f.put() # _pre_put_hook is called + yield f + fut = f.key.delete_async() # _post_delete_hook not yet called + fut.get_result() # _post_delete_hook is called + yield f + + +class MyModel(ndb.Model): + pass + + +def reserve_model_ids(): + first, last = MyModel.allocate_ids(100) + return first, last + + +def reserve_model_ids_with_a_parent(p): + first, last = MyModel.allocate_ids(100, parent=p) + return first, last + + +def construct_keys_from_range_of_reserved_ids(first, last): + keys = [ndb.Key(MyModel, id) for id in range(first, last+1)] + return keys + + +def reserve_model_ids_up_to(N): + first, last = MyModel.allocate_ids(max=N) + return first, last diff --git a/appengine/ndb/entities/snippets_test.py b/appengine/ndb/entities/snippets_test.py new file mode 100644 index 000000000000..761572238399 --- /dev/null +++ b/appengine/ndb/entities/snippets_test.py @@ -0,0 +1,205 @@ +# 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 inspect + +from google.appengine.ext import ndb +from google.appengine.ext.ndb.google_imports import datastore_errors +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_create_model_using_keyword_arguments(client): + result = snippets.create_model_using_keyword_arguments() + assert isinstance(result, snippets.Account) + + +def test_create_model_using_attributes(client): + result = snippets.create_model_using_attributes() + assert isinstance(result, snippets.Account) + + +def test_demonstrate_model_constructor_type_checking(client): + with pytest.raises(datastore_errors.BadValueError): + snippets.demonstrate_model_constructor_type_checking() + + +def test_dmonstrate_model_attribute_type_checking(client): + with pytest.raises(datastore_errors.BadValueError): + snippets.dmonstrate_model_attribute_type_checking( + snippets.create_model_using_keyword_arguments()) + + +def test_save_model(client): + result = snippets.save_model( + snippets.create_model_using_keyword_arguments()) + assert isinstance(result, snippets.ndb.Key) + + +def test_get_url_safe_key(client): + 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): + sandy_key = snippets.save_model( + snippets.create_model_using_keyword_arguments()) + result = snippets.get_model_from_url_safe_key( + snippets.get_url_safe_key(sandy_key)) + assert isinstance(result, snippets.Account) + assert result.username == 'Sandy' + + +def test_get_key_and_numeric_id_from_url_safe_key(client): + sandy_key = snippets.save_model( + snippets.create_model_using_keyword_arguments()) + urlsafe = snippets.get_url_safe_key(sandy_key) + key, ident, kind_string = ( + snippets.get_key_and_numeric_id_from_url_safe_key(urlsafe)) + assert isinstance(key, ndb.Key) + assert isinstance(ident, long) + assert isinstance(kind_string, str) + + +def test_update_model_from_key(client): + sandy = snippets.create_model_using_keyword_arguments() + sandy_key = snippets.save_model(sandy) + urlsafe = snippets.get_url_safe_key(sandy_key) + key, ident, kind_string = ( + snippets.get_key_and_numeric_id_from_url_safe_key(urlsafe)) + snippets.update_model_from_key(key) + assert key.get().email == 'sandy@example.co.uk' + + +def test_delete_model(client): + 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): + result = snippets.create_model_with_named_key() + assert 'sandy@example.com' == result + + +def test_set_key_directly(client): + account = snippets.Account() + snippets.set_key_directly(account) + assert account.key.id() == 'sandy@example.com' + + +def test_create_model_with_generated_id(client): + result = snippets.create_model_with_generated_id() + assert isinstance(result.key.id(), long) + + +def test_demonstrate_models_with_parent_hierarchy(client): + snippets.demonstrate_models_with_parent_hierarchy() + + +def test_equivalent_ways_to_define_key_with_parent(client): + result = snippets.equivalent_ways_to_define_key_with_parent() + assert result.id() == 'sandy@example.com' + + +def test_create_model_with_parent_keys(client): + result = snippets.create_model_with_parent_keys() + assert result.message_text == 'Hello' + + +def test_get_parent_key_of_model(client): + 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): + 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): + result = snippets.create_expando_model() + assert result.foo == 1 + + +def test_get_properties_defined_on_expando(client): + result = snippets.get_properties_defined_on_expando( + snippets.create_expando_model()) + assert result['foo'] is not None + assert result['bar'] is not None + assert result['tags'] is not None + + +def test_create_expando_model_with_defined_properties(client): + result = snippets.create_expando_model_with_defined_properties() + assert result.name == 'Sandy' + + +def test_create_expando_model_that_isnt_indexed_by_default(client): + 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): + with pytest.raises(AttributeError): + snippets.demonstrate_wrong_way_to_query_expando() + + +def test_demonstrate_right_way_to_query_expando(client): + snippets.demonstrate_right_way_to_query_expando() + + +def test_demonstrate_model_put_and_delete_hooks(client): + iterator = snippets.demonstrate_model_put_and_delete_hooks() + iterator.next() + assert snippets.notification == 'Gee wiz I have a new friend!' + iterator.next() + assert snippets.notification == ( + 'I have found occasion to rethink our friendship.') + + +def test_reserve_model_ids(client): + first, last = snippets.reserve_model_ids() + assert last - first >= 99 + + +def test_reserve_model_ids_with_a_parent(client): + 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): + 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): + first, last = snippets.reserve_model_ids_up_to(5) + assert last - first >= 4