Skip to content

Commit ba52d5b

Browse files
author
Jonathan Wayne Parrott
committed
Merge pull request #120 from GoogleCloudPlatform/appengine-multitenancy-standards
Bringing namespace samples up to standards
2 parents fc44cec + b4f411f commit ba52d5b

File tree

11 files changed

+335
-91
lines changed

11 files changed

+335
-91
lines changed

appengine/multitenancy/README.md

Lines changed: 8 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,13 @@
1-
## Multitenancy Using Namespaces Sample
1+
## Google App Engine Namespaces
22

3-
This is a sample app for Google App Engine that exercises the [namespace manager Python API](https://cloud.google.com/appengine/docs/python/multitenancy/multitenancy).
3+
This sample demonstrates how to use Google App Engine's [Namespace Manager API](https://cloud.google.com/appengine/docs/python/multitenancy/multitenancy) in Python.
44

5-
See our other [Google Cloud Platform github
6-
repos](https://github.com/GoogleCloudPlatform) for sample applications and
7-
scaffolding for other python frameworks and use cases.
5+
### Running the sample
86

9-
## Run Locally
10-
1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/), including the [gcloud tool](https://cloud.google.com/sdk/gcloud/), and [gcloud app component](https://cloud.google.com/sdk/gcloud-app).
11-
2. Setup the gcloud tool.
7+
You can run the sample on your development server:
8+
9+
$ dev_appserver.py .
1210

13-
```
14-
gcloud components update app
15-
gcloud auth login
16-
gcloud config set project <your-app-id>
17-
```
18-
You don't need a valid app-id to run locally, but will need a valid id to deploy below.
19-
20-
1. Clone this repo.
11+
Or deploy the application:
2112

22-
```
23-
git clone https://github.com/GoogleCloudPlatform/appengine-multitenancy-python.git
24-
```
25-
1. Run this project locally from the command line.
26-
27-
```
28-
gcloud preview app run appengine-multitenancy-python/
29-
```
30-
31-
1. Visit the application at [http://localhost:8080](http://localhost:8080).
32-
33-
## Deploying
34-
35-
1. Use the [Cloud Developer Console](https://console.developer.google.com) to create a project/app id. (App id and project id are identical)
36-
2. Configure gcloud with your app id.
37-
38-
```
39-
gcloud config set project <your-app-id>
40-
```
41-
1. Use the [Admin Console](https://appengine.google.com) to view data, queues, and other App Engine specific administration tasks.
42-
1. Use gcloud to deploy your app.
43-
44-
```
45-
gcloud preview app deploy appengine-multitenancy-python/
46-
```
47-
48-
1. Congratulations! Your application is now live at your-app-id.appspot.com
49-
50-
## Contributing changes
51-
52-
* See [CONTRIBUTING.md](CONTRIBUTING.md)
53-
54-
## Licensing
55-
56-
* See [LICENSE](LICENSE)
13+
$ appcfg.py update .

appengine/multitenancy/__init__.py

Whitespace-only changes.

appengine/multitenancy/app.yaml

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
1-
# This file specifies your Python application's runtime configuration
2-
# including URL routing, versions, static file uploads, etc. See
3-
# https://developers.google.com/appengine/docs/python/config/appconfig
4-
# for details.
5-
61
version: 1
72
runtime: python27
83
api_version: 1
94
threadsafe: yes
105

11-
# Handlers define how to route requests to your application.
126
handlers:
7+
- url: /datastore.*
8+
script: datastore.app
9+
- url: /memcache.*
10+
script: memcache.app
11+
- url: /task.*
12+
script: taskqueue.app
1313

14-
# This handler tells app engine how to route requests to a WSGI application.
15-
# The script value is in the format <path.to.module>.<wsgi_application>
16-
# where <wsgi_application> is a WSGI application object.
17-
- url: .* # This regex directs all routes to main.app
18-
script: main.app
19-
20-
libraries:
21-
- name: webapp2
22-
version: "2.5.2"

appengine/multitenancy/main.py renamed to appengine/multitenancy/datastore.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,52 +12,59 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
"""
16+
Sample App Engine application demonstrating how to use the Namespace Manager
17+
API with Datastore.
18+
19+
For more information about App Engine, see README.md under /appengine.
20+
"""
21+
1522
# [START all]
1623
from google.appengine.api import namespace_manager
1724
from google.appengine.ext import ndb
1825
import webapp2
1926

2027

2128
class Counter(ndb.Model):
22-
"""Model for containing a count."""
2329
count = ndb.IntegerProperty()
2430

2531

32+
@ndb.transactional
2633
def update_counter(name):
2734
"""Increment the named counter by 1."""
35+
counter = Counter.get_by_id(name)
36+
if counter is None:
37+
counter = Counter(id=name, count=0)
2838

29-
def _update_counter(inner_name):
30-
counter = Counter.get_by_id(inner_name)
31-
if counter is None:
32-
counter = Counter(id=inner_name)
33-
counter.count = 0
34-
counter.count += 1
35-
counter.put()
39+
counter.count += 1
40+
counter.put()
3641

37-
# Update counter in a transaction.
38-
ndb.transaction(lambda: _update_counter(name))
39-
counter = Counter.get_by_id(name)
4042
return counter.count
4143

4244

43-
class SomeRequest(webapp2.RequestHandler):
44-
"""Perform synchronous requests to update counter."""
45+
class DatastoreCounterHandler(webapp2.RequestHandler):
46+
"""Increments counters in the global namespace as well as in whichever
47+
namespace is specified by the request, which is arbitrarily named 'default'
48+
if not specified."""
49+
50+
def get(self, namespace='default'):
51+
global_count = update_counter('counter')
4552

46-
def get(self):
47-
update_counter('SomeRequest')
48-
# try/finally pattern to temporarily set the namespace.
4953
# Save the current namespace.
50-
namespace = namespace_manager.get_namespace()
54+
previous_namespace = namespace_manager.get_namespace()
5155
try:
52-
namespace_manager.set_namespace('-global-')
53-
x = update_counter('SomeRequest')
56+
namespace_manager.set_namespace(namespace)
57+
namespace_count = update_counter('counter')
5458
finally:
5559
# Restore the saved namespace.
56-
namespace_manager.set_namespace(namespace)
57-
self.response.write('<html><body><p>Updated counters')
58-
self.response.write(' to %s' % x)
59-
self.response.write('</p></body></html>')
60+
namespace_manager.set_namespace(previous_namespace)
61+
62+
self.response.write('Global: {}, Namespace {}: {}'.format(
63+
global_count, namespace, namespace_count))
6064

6165

62-
app = webapp2.WSGIApplication([('/', SomeRequest)], debug=True)
66+
app = webapp2.WSGIApplication([
67+
(r'/datastore', DatastoreCounterHandler),
68+
(r'/datastore/(.*)', DatastoreCounterHandler)
69+
], debug=True)
6370
# [END all]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2015 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 tests
16+
import webtest
17+
18+
from . import datastore
19+
20+
21+
class TestNamespaceDatastoreSample(tests.AppEngineTestbedCase):
22+
23+
def setUp(self):
24+
super(TestNamespaceDatastoreSample, self).setUp()
25+
self.app = webtest.TestApp(datastore.app)
26+
27+
def test_get(self):
28+
response = self.app.get('/datastore')
29+
self.assertEqual(response.status_int, 200)
30+
self.assertTrue('Global: 1' in response.body)
31+
32+
response = self.app.get('/datastore/a')
33+
self.assertEqual(response.status_int, 200)
34+
self.assertTrue('Global: 2' in response.body)
35+
self.assertTrue('a: 1' in response.body)
36+
37+
response = self.app.get('/datastore/b')
38+
self.assertEqual(response.status_int, 200)
39+
self.assertTrue('Global: 3' in response.body)
40+
self.assertTrue('b: 1' in response.body)

appengine/multitenancy/favicon.ico

-8.15 KB
Binary file not shown.

appengine/multitenancy/memcache.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2015 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+
"""
16+
Sample App Engine application demonstrating how to use the Namespace Manager
17+
API with Memcache.
18+
19+
For more information about App Engine, see README.md under /appengine.
20+
"""
21+
22+
# [START all]
23+
from google.appengine.api import memcache
24+
from google.appengine.api import namespace_manager
25+
import webapp2
26+
27+
28+
class MemcacheCounterHandler(webapp2.RequestHandler):
29+
"""Increments counters in the global namespace as well as in whichever
30+
namespace is specified by the request, which is arbitrarily named 'default'
31+
if not specified."""
32+
33+
def get(self, namespace='default'):
34+
global_count = memcache.incr('counter', initial_value=0)
35+
36+
# Save the current namespace.
37+
previous_namespace = namespace_manager.get_namespace()
38+
try:
39+
namespace_manager.set_namespace(namespace)
40+
namespace_count = memcache.incr('counter', initial_value=0)
41+
finally:
42+
# Restore the saved namespace.
43+
namespace_manager.set_namespace(previous_namespace)
44+
45+
self.response.write('Global: {}, Namespace {}: {}'.format(
46+
global_count, namespace, namespace_count))
47+
48+
49+
app = webapp2.WSGIApplication([
50+
(r'/memcache', MemcacheCounterHandler),
51+
(r'/memcache/(.*)', MemcacheCounterHandler)
52+
], debug=True)
53+
# [END all]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2015 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 tests
16+
import webtest
17+
18+
from . import memcache
19+
20+
21+
class TestNamespaceMemcacheSample(tests.AppEngineTestbedCase):
22+
23+
def setUp(self):
24+
super(TestNamespaceMemcacheSample, self).setUp()
25+
self.app = webtest.TestApp(memcache.app)
26+
27+
def test_get(self):
28+
response = self.app.get('/memcache')
29+
self.assertEqual(response.status_int, 200)
30+
self.assertTrue('Global: 1' in response.body)
31+
32+
response = self.app.get('/memcache/a')
33+
self.assertEqual(response.status_int, 200)
34+
self.assertTrue('Global: 2' in response.body)
35+
self.assertTrue('a: 1' in response.body)
36+
37+
response = self.app.get('/memcache/b')
38+
self.assertEqual(response.status_int, 200)
39+
self.assertTrue('Global: 3' in response.body)
40+
self.assertTrue('b: 1' in response.body)

0 commit comments

Comments
 (0)