Skip to content

Commit

Permalink
Merge pull request #120 from GoogleCloudPlatform/appengine-multitenan…
Browse files Browse the repository at this point in the history
…cy-standards

Bringing namespace samples up to standards
  • Loading branch information
Jonathan Wayne Parrott committed Sep 25, 2015
2 parents fc44cec + b4f411f commit ba52d5b
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 91 deletions.
59 changes: 8 additions & 51 deletions appengine/multitenancy/README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,13 @@
## Multitenancy Using Namespaces Sample
## Google App Engine Namespaces

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).
This sample demonstrates how to use Google App Engine's [Namespace Manager API](https://cloud.google.com/appengine/docs/python/multitenancy/multitenancy) in Python.

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

## Run Locally
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).
2. Setup the gcloud tool.
You can run the sample on your development server:
$ dev_appserver.py .

```
gcloud components update app
gcloud auth login
gcloud config set project <your-app-id>
```
You don't need a valid app-id to run locally, but will need a valid id to deploy below.

1. Clone this repo.
Or deploy the application:

```
git clone https://github.com/GoogleCloudPlatform/appengine-multitenancy-python.git
```
1. Run this project locally from the command line.

```
gcloud preview app run appengine-multitenancy-python/
```

1. Visit the application at [http://localhost:8080](http://localhost:8080).

## Deploying

1. Use the [Cloud Developer Console](https://console.developer.google.com) to create a project/app id. (App id and project id are identical)
2. Configure gcloud with your app id.

```
gcloud config set project <your-app-id>
```
1. Use the [Admin Console](https://appengine.google.com) to view data, queues, and other App Engine specific administration tasks.
1. Use gcloud to deploy your app.

```
gcloud preview app deploy appengine-multitenancy-python/
```

1. Congratulations! Your application is now live at your-app-id.appspot.com

## Contributing changes

* See [CONTRIBUTING.md](CONTRIBUTING.md)

## Licensing

* See [LICENSE](LICENSE)
$ appcfg.py update .
Empty file.
21 changes: 6 additions & 15 deletions appengine/multitenancy/app.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
# This file specifies your Python application's runtime configuration
# including URL routing, versions, static file uploads, etc. See
# https://developers.google.com/appengine/docs/python/config/appconfig
# for details.

version: 1
runtime: python27
api_version: 1
threadsafe: yes

# Handlers define how to route requests to your application.
handlers:
- url: /datastore.*
script: datastore.app
- url: /memcache.*
script: memcache.app
- url: /task.*
script: taskqueue.app

# This handler tells app engine how to route requests to a WSGI application.
# The script value is in the format <path.to.module>.<wsgi_application>
# where <wsgi_application> is a WSGI application object.
- url: .* # This regex directs all routes to main.app
script: main.app

libraries:
- name: webapp2
version: "2.5.2"
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,59 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Sample App Engine application demonstrating how to use the Namespace Manager
API with Datastore.
For more information about App Engine, see README.md under /appengine.
"""

# [START all]
from google.appengine.api import namespace_manager
from google.appengine.ext import ndb
import webapp2


class Counter(ndb.Model):
"""Model for containing a count."""
count = ndb.IntegerProperty()


@ndb.transactional
def update_counter(name):
"""Increment the named counter by 1."""
counter = Counter.get_by_id(name)
if counter is None:
counter = Counter(id=name, count=0)

def _update_counter(inner_name):
counter = Counter.get_by_id(inner_name)
if counter is None:
counter = Counter(id=inner_name)
counter.count = 0
counter.count += 1
counter.put()
counter.count += 1
counter.put()

# Update counter in a transaction.
ndb.transaction(lambda: _update_counter(name))
counter = Counter.get_by_id(name)
return counter.count


class SomeRequest(webapp2.RequestHandler):
"""Perform synchronous requests to update counter."""
class DatastoreCounterHandler(webapp2.RequestHandler):
"""Increments counters in the global namespace as well as in whichever
namespace is specified by the request, which is arbitrarily named 'default'
if not specified."""

def get(self, namespace='default'):
global_count = update_counter('counter')

def get(self):
update_counter('SomeRequest')
# try/finally pattern to temporarily set the namespace.
# Save the current namespace.
namespace = namespace_manager.get_namespace()
previous_namespace = namespace_manager.get_namespace()
try:
namespace_manager.set_namespace('-global-')
x = update_counter('SomeRequest')
namespace_manager.set_namespace(namespace)
namespace_count = update_counter('counter')
finally:
# Restore the saved namespace.
namespace_manager.set_namespace(namespace)
self.response.write('<html><body><p>Updated counters')
self.response.write(' to %s' % x)
self.response.write('</p></body></html>')
namespace_manager.set_namespace(previous_namespace)

self.response.write('Global: {}, Namespace {}: {}'.format(
global_count, namespace, namespace_count))


app = webapp2.WSGIApplication([('/', SomeRequest)], debug=True)
app = webapp2.WSGIApplication([
(r'/datastore', DatastoreCounterHandler),
(r'/datastore/(.*)', DatastoreCounterHandler)
], debug=True)
# [END all]
40 changes: 40 additions & 0 deletions appengine/multitenancy/datastore_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2015 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 tests
import webtest

from . import datastore


class TestNamespaceDatastoreSample(tests.AppEngineTestbedCase):

def setUp(self):
super(TestNamespaceDatastoreSample, self).setUp()
self.app = webtest.TestApp(datastore.app)

def test_get(self):
response = self.app.get('/datastore')
self.assertEqual(response.status_int, 200)
self.assertTrue('Global: 1' in response.body)

response = self.app.get('/datastore/a')
self.assertEqual(response.status_int, 200)
self.assertTrue('Global: 2' in response.body)
self.assertTrue('a: 1' in response.body)

response = self.app.get('/datastore/b')
self.assertEqual(response.status_int, 200)
self.assertTrue('Global: 3' in response.body)
self.assertTrue('b: 1' in response.body)
Binary file removed appengine/multitenancy/favicon.ico
Binary file not shown.
53 changes: 53 additions & 0 deletions appengine/multitenancy/memcache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2015 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.

"""
Sample App Engine application demonstrating how to use the Namespace Manager
API with Memcache.
For more information about App Engine, see README.md under /appengine.
"""

# [START all]
from google.appengine.api import memcache
from google.appengine.api import namespace_manager
import webapp2


class MemcacheCounterHandler(webapp2.RequestHandler):
"""Increments counters in the global namespace as well as in whichever
namespace is specified by the request, which is arbitrarily named 'default'
if not specified."""

def get(self, namespace='default'):
global_count = memcache.incr('counter', initial_value=0)

# Save the current namespace.
previous_namespace = namespace_manager.get_namespace()
try:
namespace_manager.set_namespace(namespace)
namespace_count = memcache.incr('counter', initial_value=0)
finally:
# Restore the saved namespace.
namespace_manager.set_namespace(previous_namespace)

self.response.write('Global: {}, Namespace {}: {}'.format(
global_count, namespace, namespace_count))


app = webapp2.WSGIApplication([
(r'/memcache', MemcacheCounterHandler),
(r'/memcache/(.*)', MemcacheCounterHandler)
], debug=True)
# [END all]
40 changes: 40 additions & 0 deletions appengine/multitenancy/memcache_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2015 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 tests
import webtest

from . import memcache


class TestNamespaceMemcacheSample(tests.AppEngineTestbedCase):

def setUp(self):
super(TestNamespaceMemcacheSample, self).setUp()
self.app = webtest.TestApp(memcache.app)

def test_get(self):
response = self.app.get('/memcache')
self.assertEqual(response.status_int, 200)
self.assertTrue('Global: 1' in response.body)

response = self.app.get('/memcache/a')
self.assertEqual(response.status_int, 200)
self.assertTrue('Global: 2' in response.body)
self.assertTrue('a: 1' in response.body)

response = self.app.get('/memcache/b')
self.assertEqual(response.status_int, 200)
self.assertTrue('Global: 3' in response.body)
self.assertTrue('b: 1' in response.body)
Loading

0 comments on commit ba52d5b

Please sign in to comment.