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..7f404650f5f7 --- /dev/null +++ b/appengine/ndb/entities/snippets.py @@ -0,0 +1,258 @@ +# 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_sandy_with_keywords(): + sandy = Account(username='Sandy', + userid=123, + email='sandy@gmail.com') + return sandy + + +def create_sandy_manually(): + sandy = Account() + sandy.username = 'Sandy' + sandy.userid = 123 + sandy.email = 'sandy@gmail.com' + return sandy + + +def create_with_type_error_in_constructor(): + bad = Account(username='Sandy', + userid='not integer') # raises an exception + return bad + + +def assign_with_type_error(sandy): + sandy.username = 42 # raises an exception + + +def store_sandy(sandy): + sandy_key = sandy.put() + return sandy_key + + +def url_safe_sandy_key(sandy_key): + url_string = sandy_key.urlsafe() + return url_string + + +def get_sandy_from_urlsafe(url_string): + sandy_key = ndb.Key(urlsafe=url_string) + sandy = sandy_key.get() + return sandy + + +def id_from_urlsafe(url_string): + key = ndb.Key(urlsafe=url_string) + kind_string = key.kind() + ident = key.id() + return key, ident, kind_string + + +def update_key(key): + sandy = key.get() + sandy.email = 'sandy@gmail.co.uk' + sandy.put() + + +def delete_sandy(sandy): + sandy.key.delete() + + +def create_sandy_named_key(): + account = Account(username='Sandy', userid=1234, email='some@where.com', + id='SOME@WHERE.COM') + + return account.key.id() # returns 'SOME@WHERE.COM' + + +def set_key_directly(account): + account.key = ndb.Key('Account', 'SOME@WHERE.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, 'SOME@WHERE.COM') + + +def create_sandy_with_generated_numeric_id(): + # note: no id kwarg + account = Account(username='Sandy', userid=1234, email='some@where.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 example_message_revisions(): + ndb.Key('Account', 'sandy@foo.com', 'Message', 123, 'Revision', '1') + ndb.Key('Account', 'sandy@foo.com', 'Message', 123, 'Revision', '2') + ndb.Key('Account', 'larry@foo.com', 'Message', 456, 'Revision', '1') + ndb.Key('Account', 'larry@foo.com', 'Message', 789, 'Revision', '2') + + +def example_revision_equivalents(): + ndb.Key('Account', 'sandy@foo.com', 'Message', 123, 'Revision', '1') + + ndb.Key('Revision', '1', parent=ndb.Key( + 'Account', 'sandy@foo.com', 'Message', 123)) + + ndb.Key('Revision', '1', parent=ndb.Key( + 'Message', 123, parent=ndb.Key('Account', 'sandy@foo.com'))) + + sandy_key = ndb.Key(Account, 'sandy@foo.com') + return sandy_key + + +def insert_message(): + account_key = ndb.Key(Account, 'some@where.com') + + # Lets 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(initial_revision): + message_key = initial_revision.key.parent() + return message_key + + +def multi_key_ops(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(): + e = Mine() + e.foo = 1 + e.bar = 'blah' + e.tags = ['exp', 'and', 'oh'] + e.put() + + return e + + +def expando_properties(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_flex_employee(): + emp = FlexEmployee(name='Sandy', location='SF') + return emp + + +class Specialized(ndb.Expando): + _default_indexed = False + + +def create_specialized(): + e = Specialized(foo='a', bar=['b']) + return e._properties + # {'foo': GenericProperty('foo', indexed=False), + # 'bar': GenericProperty('bar', indexed=False, repeated=True)} + + +def non_working_flex_query(): + FlexEmployee.query(FlexEmployee.location == 'SF') + + +def working_flex_employee(): + 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 suck and nobody likes me.') + + +def demonstrate_hook(): + 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_ids(): + first, last = MyModel.allocate_ids(100) + return first, last + + +def reserve_ids_with_parent(p): + first, last = MyModel.allocate_ids(100, parent=p) + return first, last + + +def construct_keys(first, last): + keys = [ndb.Key(MyModel, id) for id in range(first, last+1)] + return keys + + +def reserve_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..ce1897caa97a --- /dev/null +++ b/appengine/ndb/entities/snippets_test.py @@ -0,0 +1,194 @@ +# 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_sandy_with_keywords(client): + result = snippets.create_sandy_with_keywords() + assert type(result) == snippets.Account + + +def test_create_sandy_manually(client): + result = snippets.create_sandy_manually() + assert type(result) == snippets.Account + + +def test_create_with_type_error_in_constructor(client): + with pytest.raises(datastore_errors.BadValueError): + snippets.create_with_type_error_in_constructor() + + +def test_assign_with_type_error(client): + with pytest.raises(datastore_errors.BadValueError): + snippets.assign_with_type_error(snippets.create_sandy_with_keywords()) + + +def test_store_sandy(client): + result = snippets.store_sandy(snippets.create_sandy_with_keywords()) + assert type(result) == snippets.ndb.Key + + +def test_url_safe_sandy_key(client): + sandy_key = snippets.store_sandy(snippets.create_sandy_with_keywords()) + result = snippets.url_safe_sandy_key(sandy_key) + assert type(result) == str + + +def test_get_sandy_from_urlsafe(client): + sandy_key = snippets.store_sandy(snippets.create_sandy_with_keywords()) + result = snippets.get_sandy_from_urlsafe( + snippets.url_safe_sandy_key(sandy_key)) + assert type(result) == snippets.Account + assert result.username == 'Sandy' + + +def test_id_from_urlsafe(client): + sandy_key = snippets.store_sandy(snippets.create_sandy_with_keywords()) + urlsafe = snippets.url_safe_sandy_key(sandy_key) + key, ident, kind_string = snippets.id_from_urlsafe(urlsafe) + assert type(key) == ndb.Key + assert type(ident) == long + assert type(kind_string) == str + + +def test_update_key(client): + sandy = snippets.create_sandy_with_keywords() + sandy_key = snippets.store_sandy(sandy) + urlsafe = snippets.url_safe_sandy_key(sandy_key) + key, ident, kind_string = snippets.id_from_urlsafe(urlsafe) + snippets.update_key(key) + assert key.get().email == 'sandy@gmail.co.uk' + + +def test_delete_sandy(client): + sandy = snippets.create_sandy_with_keywords() + snippets.store_sandy(sandy) + snippets.delete_sandy(sandy) + assert sandy.key.get() is None + + +def test_create_sandy_named_key(client): + result = snippets.create_sandy_named_key() + assert 'SOME@WHERE.COM' == result + + +def test_set_key_directly(client): + account = snippets.Account() + snippets.set_key_directly(account) + assert account.key.id() == 'SOME@WHERE.COM' + + +def test_create_sandy_with_generated_numeric_id(client): + result = snippets.create_sandy_with_generated_numeric_id() + assert type(result.key.id()) == long + + +def test_example_message_revisions(client): + snippets.example_message_revisions() + + +def test_example_revision_equivalents(client): + result = snippets.example_revision_equivalents() + assert result.id() == 'sandy@foo.com' + + +def test_insert_message(client): + result = snippets.insert_message() + assert result.message_text == 'Hello' + + +def test_get_parent_key(client): + initial_revision = snippets.insert_message() + result = snippets.get_parent_key(initial_revision) + assert result.kind() == 'Message' + + +def test_multi_key_ops(client): + snippets.multi_key_ops([ + snippets.Account(email='a@a.com'), snippets.Account(email='b@b.com')]) + + +def test_create_expando(client): + result = snippets.create_expando() + assert result.foo == 1 + + +def test_expando_properties(client): + result = snippets.expando_properties(snippets.create_expando()) + assert result['foo'] is not None + assert result['bar'] is not None + assert result['tags'] is not None + + +def test_create_flex_employee(client): + result = snippets.create_flex_employee() + assert result.name == 'Sandy' + + +def test_create_specialized(client): + result = snippets.create_specialized() + assert result['foo'] + assert result['bar'] + + +def test_non_working_flex_query(client): + with pytest.raises(AttributeError): + snippets.non_working_flex_query() + + +def test_working_flex_employee(client): + snippets.working_flex_employee() + + +def test_demonstrate_hook(client): + iterator = snippets.demonstrate_hook() + iterator.next() + assert snippets.notification == 'Gee wiz I have a new friend!' + iterator.next() + assert snippets.notification == 'I suck and nobody likes me.' + + +def test_reserve_ids(client): + first, last = snippets.reserve_ids() + assert last - first >= 99 + + +def test_reserve_ids_with_parent(client): + first, last = snippets.reserve_ids_with_parent(snippets.Friend().key) + assert last - first >= 99 + + +def test_construct_keys(client): + result = snippets.construct_keys(*snippets.reserve_ids()) + assert len(result) == 100 + + +def test_reserve_ids_up_to(client): + first, last = snippets.reserve_ids_up_to(5) + assert last - first >= 4