Skip to content

Commit 4258361

Browse files
committed
Merge pull request #311 from GoogleCloudPlatform/async
Add samples for ndb async ops.
2 parents 279d631 + cbc4776 commit 4258361

13 files changed

+680
-0
lines changed

appengine/ndb/async/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## App Engine Datastore NDB Asynchronous Operations Samples
2+
3+
This contains snippets used in the NDB asynchronous operations documentation,
4+
demonstrating various ways to make asynchronous ndb operations.
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/async
10+
11+
<!-- end-auto-doc-link -->

appengine/ndb/async/app_async.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 google.appengine.api import users
16+
from google.appengine.ext import ndb
17+
import webapp2
18+
19+
20+
class Account(ndb.Model):
21+
view_counter = ndb.IntegerProperty()
22+
23+
24+
class MyRequestHandler(webapp2.RequestHandler):
25+
def get(self):
26+
acct = Account.get_by_id(users.get_current_user().user_id())
27+
acct.view_counter += 1
28+
future = acct.put_async()
29+
30+
# ...read something else from Datastore...
31+
32+
self.response.out.write('Content of the page')
33+
future.get_result()
34+
35+
app = webapp2.WSGIApplication([('/', MyRequestHandler)])

appengine/ndb/async/app_async_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 app_async
16+
import pytest
17+
import webtest
18+
19+
20+
@pytest.fixture
21+
def app(testbed):
22+
return webtest.TestApp(app_async.app)
23+
24+
25+
def test_main(app, testbed, login):
26+
app_async.Account(id='123', view_counter=4).put()
27+
28+
# Log the user in
29+
login(id='123')
30+
31+
response = app.get('/')
32+
33+
assert response.status_int == 200
34+
account = app_async.Account.get_by_id('123')
35+
assert account.view_counter == 5

appengine/ndb/async/app_sync.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 google.appengine.api import users
16+
from google.appengine.ext import ndb
17+
import webapp2
18+
19+
20+
class Account(ndb.Model):
21+
view_counter = ndb.IntegerProperty()
22+
23+
24+
class MyRequestHandler(webapp2.RequestHandler):
25+
def get(self):
26+
acct = Account.get_by_id(users.get_current_user().user_id())
27+
acct.view_counter += 1
28+
acct.put()
29+
30+
# ...read something else from Datastore...
31+
32+
self.response.out.write('Content of the page')
33+
34+
app = webapp2.WSGIApplication([('/', MyRequestHandler)])

appengine/ndb/async/app_sync_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 app_sync
16+
import pytest
17+
import webtest
18+
19+
20+
@pytest.fixture
21+
def app(testbed):
22+
return webtest.TestApp(app_sync.app)
23+
24+
25+
def test_main(app, testbed, login):
26+
app_sync.Account(id='123', view_counter=4).put()
27+
28+
# Log the user in
29+
login(id='123')
30+
31+
response = app.get('/')
32+
33+
assert response.status_int == 200
34+
account = app_sync.Account.get_by_id('123')
35+
assert account.view_counter == 5
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This is in a separate folder to isolate it from the other apps.
2+
This is necessary because the test won't pass when run with the other tests.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 google.appengine.api import users
16+
from google.appengine.ext import ndb
17+
import webapp2
18+
19+
20+
class Account(ndb.Model):
21+
view_counter = ndb.IntegerProperty()
22+
23+
24+
class MyRequestHandler(webapp2.RequestHandler):
25+
@ndb.toplevel
26+
def get(self):
27+
acct = Account.get_by_id(users.get_current_user().user_id())
28+
acct.view_counter += 1
29+
acct.put_async() # Ignoring the Future this returns
30+
31+
# ...read something else from Datastore...
32+
33+
self.response.out.write('Content of the page')
34+
35+
36+
# This is actually redundant, since the `get` decorator already handles it, but
37+
# for demonstration purposes, you can also make the entire app toplevel with
38+
# the following.
39+
app = ndb.toplevel(webapp2.WSGIApplication([('/', MyRequestHandler)]))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 app_toplevel
16+
import pytest
17+
import webtest
18+
19+
20+
@pytest.fixture
21+
def app(testbed):
22+
return webtest.TestApp(app_toplevel.app)
23+
24+
25+
def test_main(app, testbed, login):
26+
app_toplevel.Account(id='123', view_counter=4).put()
27+
28+
# Log the user in
29+
login(id='123')
30+
31+
response = app.get('/')
32+
33+
assert response.status_int == 200
34+
account = app_toplevel.Account.get_by_id('123')
35+
assert account.view_counter == 5

appengine/ndb/async/app_toplevel/index.html

Whitespace-only changes.

appengine/ndb/async/guestbook.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 google.appengine.api import users
16+
from google.appengine.ext import ndb
17+
import webapp2
18+
19+
20+
class Guestbook(ndb.Model):
21+
content = ndb.StringProperty()
22+
post_date = ndb.DateTimeProperty(auto_now_add=True)
23+
24+
25+
class Account(ndb.Model):
26+
email = ndb.StringProperty()
27+
nickname = ndb.StringProperty()
28+
29+
def nick(self):
30+
return self.nickname or self.email # Whichever is non-empty
31+
32+
33+
class Message(ndb.Model):
34+
text = ndb.StringProperty()
35+
when = ndb.DateTimeProperty(auto_now_add=True)
36+
author = ndb.KeyProperty(kind=Account) # references Account
37+
38+
39+
class MainPage(webapp2.RequestHandler):
40+
def get(self):
41+
if self.request.path == '/guestbook':
42+
if self.request.get('async'):
43+
self.get_guestbook_async()
44+
else:
45+
self.get_guestbook_sync()
46+
elif self.request.path == '/messages':
47+
if self.request.get('async'):
48+
self.get_messages_async()
49+
else:
50+
self.get_messages_sync()
51+
52+
def get_guestbook_sync(self):
53+
uid = users.get_current_user().user_id()
54+
acct = Account.get_by_id(uid) # I/O action 1
55+
qry = Guestbook.query().order(-Guestbook.post_date)
56+
recent_entries = qry.fetch(10) # I/O action 2
57+
58+
# ...render HTML based on this data...
59+
self.response.out.write('<html><body>{}</body></html>'.format(''.join(
60+
'<p>{}</p>'.format(entry.content) for entry in recent_entries)))
61+
62+
return acct, qry
63+
64+
def get_guestbook_async(self):
65+
uid = users.get_current_user().user_id()
66+
acct_future = Account.get_by_id_async(uid) # Start I/O action #1
67+
qry = Guestbook.query().order(-Guestbook.post_date)
68+
recent_entries_future = qry.fetch_async(10) # Start I/O action #2
69+
acct = acct_future.get_result() # Complete #1
70+
recent_entries = recent_entries_future.get_result() # Complete #2
71+
72+
# ...render HTML based on this data...
73+
self.response.out.write('<html><body>{}</body></html>'.format(''.join(
74+
'<p>{}</p>'.format(entry.content) for entry in recent_entries)))
75+
76+
return acct, recent_entries
77+
78+
def get_messages_sync(self):
79+
qry = Message.query().order(-Message.when)
80+
for msg in qry.fetch(20):
81+
acct = msg.author.get()
82+
self.response.out.write(
83+
'<p>On {}, {} wrote:'.format(msg.when, acct.nick()))
84+
self.response.out.write('<p>{}'.format(msg.text))
85+
86+
def get_messages_async(self):
87+
@ndb.tasklet
88+
def callback(msg):
89+
acct = yield msg.author.get_async()
90+
raise ndb.Return('On {}, {} wrote:\n{}'.format(
91+
msg.when, acct.nick(), msg.text))
92+
93+
qry = Message.query().order(-Message.when)
94+
outputs = qry.map(callback, limit=20)
95+
for output in outputs:
96+
self.response.out.write('<p>{}</p>'.format(output))
97+
98+
99+
app = webapp2.WSGIApplication([
100+
('/.*', MainPage),
101+
], debug=True)

0 commit comments

Comments
 (0)