diff --git a/appengine/__init__.py b/appengine/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/memcache/__init__.py b/appengine/memcache/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/memcache/guestbook/README.md b/appengine/memcache/guestbook/README.md new file mode 100644 index 000000000000..c51b3eec8ed5 --- /dev/null +++ b/appengine/memcache/guestbook/README.md @@ -0,0 +1,57 @@ +## Memcache Guestbook Sample + +This is a sample app for Google App Engine that exercises the [memcache Python API](https://cloud.google.com/appengine/docs/python/memcache/usingmemcache). + +See our other [Google Cloud Platform github +repos](https://github.com/GoogleCloudPlatform) for sample applications and +scaffolding for other python frameworks and use cases. + +## 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. + + ``` + gcloud components update app + gcloud auth login + gcloud config set project + ``` + You don't need a valid app-id to run locally, but will need a valid id to deploy below. + +1. Clone this repo. + + ``` + git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + cd appengine/memcache/guestbook/ + ``` +1. Run this project locally from the command line. + + ``` + gcloud preview app run ./app.yaml + ``` + +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 + ``` +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 ./app.yaml + ``` + +1. Congratulations! Your application is now live at your-app-id.appspot.com + +## Contributing changes + +* See [CONTRIBUTING.md](../../../CONTRIBUTING.md) + +## Licensing + +* See [LICENSE](../../../LICENSE) diff --git a/appengine/memcache/guestbook/__init__.py b/appengine/memcache/guestbook/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/memcache/guestbook/app.yaml b/appengine/memcache/guestbook/app.yaml new file mode 100644 index 000000000000..5227472ff0cf --- /dev/null +++ b/appengine/memcache/guestbook/app.yaml @@ -0,0 +1,22 @@ +# 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: + +# This handler tells app engine how to route requests to a WSGI application. +# The script value is in the format . +# where is a WSGI application object. +- url: .* # This regex directs all routes to main.app + script: main.app + +libraries: +- name: webapp2 + version: "2.5.2" diff --git a/appengine/memcache/guestbook/favicon.ico b/appengine/memcache/guestbook/favicon.ico new file mode 100644 index 000000000000..23c553a2966c Binary files /dev/null and b/appengine/memcache/guestbook/favicon.ico differ diff --git a/appengine/memcache/guestbook/index.yaml b/appengine/memcache/guestbook/index.yaml new file mode 100644 index 000000000000..f933a1c8e6d2 --- /dev/null +++ b/appengine/memcache/guestbook/index.yaml @@ -0,0 +1,17 @@ +indexes: + +# AUTOGENERATED + +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. + +- kind: Greeting + ancestor: yes + properties: + - name: date + direction: desc diff --git a/appengine/memcache/guestbook/main.py b/appengine/memcache/guestbook/main.py new file mode 100644 index 000000000000..958c5e7230fc --- /dev/null +++ b/appengine/memcache/guestbook/main.py @@ -0,0 +1,138 @@ +# 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. + +# [START all] +import cgi +import cStringIO +import logging +import urllib +import webapp2 + +from google.appengine.ext import ndb +from google.appengine.api import memcache +from google.appengine.api import users + + +class Greeting(ndb.Model): + """Models an individual Guestbook entry with author, content, and date.""" + author = ndb.StringProperty() + content = ndb.StringProperty() + date = ndb.DateTimeProperty(auto_now_add=True) + + +def guestbook_key(guestbook_name=None): + """Constructs a Datastore key for a Guestbook entity with guestbook_name.""" + return ndb.Key('Guestbook', guestbook_name or 'default_guestbook') + + +class MainPage(webapp2.RequestHandler): + def get(self): + self.response.out.write('') + guestbook_name = self.request.get('guestbook_name') + + greetings = self.get_greetings(guestbook_name) + stats = memcache.get_stats() + + self.response.write('Cache Hits:{}
'.format(stats['hits'])) + self.response.write('Cache Misses:{}

'.format( + stats['misses'])) + self.response.write(greetings) + + self.response.write(""" +
+
+
+
+
+
Guestbook name: +
+ + """.format(urllib.urlencode({'guestbook_name': guestbook_name}), + cgi.escape(guestbook_name))) + + # [START check_memcache] + def get_greetings(self, guestbook_name): + """ + get_greetings() + Checks the cache to see if there are cached greetings. + If not, call render_greetings and set the cache + + Args: + guestbook_name: Guestbook entity group key (string). + + Returns: + A string of HTML containing greetings. + """ + greetings = memcache.get('{}:greetings'.format(guestbook_name)) + if greetings is None: + greetings = self.render_greetings(guestbook_name) + if not memcache.add('{}:greetings'.format(guestbook_name), + greetings, 10): + logging.error('Memcache set failed.') + return greetings + # [END check_memcache] + + # [START query_datastore] + def render_greetings(self, guestbook_name): + """ + render_greetings() + Queries the database for greetings, iterate through the + results and create the HTML. + + Args: + guestbook_name: Guestbook entity group key (string). + + Returns: + A string of HTML containing greetings + """ + greetings = ndb.gql('SELECT * ' + 'FROM Greeting ' + 'WHERE ANCESTOR IS :1 ' + 'ORDER BY date DESC LIMIT 10', + guestbook_key(guestbook_name)) + output = cStringIO.StringIO() + for greeting in greetings: + if greeting.author: + output.write('{} wrote:'.format(greeting.author)) + else: + output.write('An anonymous person wrote:') + output.write('
{}
'.format( + cgi.escape(greeting.content))) + return output.getvalue() + # [END query_datastore] + + +class Guestbook(webapp2.RequestHandler): + def post(self): + # We set the same parent key on the 'Greeting' to ensure each greeting + # is in the same entity group. Queries across the single entity group + # are strongly consistent. However, the write rate to a single entity + # group is limited to ~1/second. + guestbook_name = self.request.get('guestbook_name') + greeting = Greeting(parent=guestbook_key(guestbook_name)) + + if users.get_current_user(): + greeting.author = users.get_current_user().nickname() + + greeting.content = self.request.get('content') + greeting.put() + self.redirect('/?' + + urllib.urlencode({'guestbook_name': guestbook_name})) + + +app = webapp2.WSGIApplication([('/', MainPage), + ('/sign', Guestbook)], + debug=True) + +# [END all]