diff --git a/appengine/taskqueue/counter/README.md b/appengine/taskqueue/counter/README.md
index 4ccee7143a0..e97ad7df0ed 100644
--- a/appengine/taskqueue/counter/README.md
+++ b/appengine/taskqueue/counter/README.md
@@ -1,5 +1,13 @@
# App Engine Task Queue Counter
+To run this app locally, specify both `.yaml` files to `dev_appserver.py`:
+
+ dev_appserver.py -A your-app-id application.yaml worker.yaml
+
+To deploy this application, specify both `.yaml` files to `appcfg.py`:
+
+ appcfg.py update -A your-app-id -V 1 application.yaml worker.yaml
+
These samples are used on the following documentation page:
diff --git a/appengine/taskqueue/counter/application.py b/appengine/taskqueue/counter/application.py
new file mode 100644
index 00000000000..dbd27cd849b
--- /dev/null
+++ b/appengine/taskqueue/counter/application.py
@@ -0,0 +1,74 @@
+# 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.api import taskqueue
+from google.appengine.ext import ndb
+import webapp2
+
+
+COUNTER_KEY = 'default counter'
+
+
+class Counter(ndb.Model):
+ count = ndb.IntegerProperty(indexed=False)
+
+
+class MainPageHandler(webapp2.RequestHandler):
+ def get(self):
+ counter = Counter.get_by_id(COUNTER_KEY)
+ count = counter.count if counter else 0
+
+ self.response.write("""
+ Count: {count}
+
+ """.format(count=count))
+
+
+class EnqueueTaskHandler(webapp2.RequestHandler):
+ def post(self):
+ amount = int(self.request.get('amount'))
+
+ task = taskqueue.add(
+ url='/update_counter',
+ target='worker',
+ params={'amount': amount})
+
+ self.response.write(
+ 'Task {} enqueued, ETA {}.'.format(task.name, task.eta))
+
+
+class AsyncEnqueueTaskHandler(webapp2.RequestHandler):
+ def post(self):
+ amount = int(self.request.get('amount'))
+
+ future = taskqueue.add_async(
+ url='/update_counter',
+ target='worker',
+ params={'amount': amount})
+
+ task = future.wait()
+
+ self.response.write(
+ 'Task {} enqueued, ETA {}.'.format(task.name, task.eta))
+
+
+app = webapp2.WSGIApplication([
+ ('/', MainPageHandler),
+ ('/enqueue', EnqueueTaskHandler),
+ ('/enqueue_async', EnqueueTaskHandler)
+], debug=True)
diff --git a/appengine/taskqueue/counter/app.yaml b/appengine/taskqueue/counter/application.yaml
similarity index 54%
rename from appengine/taskqueue/counter/app.yaml
rename to appengine/taskqueue/counter/application.yaml
index e2b5e55709c..b4e5019bd3c 100644
--- a/appengine/taskqueue/counter/app.yaml
+++ b/appengine/taskqueue/counter/application.yaml
@@ -1,11 +1,8 @@
runtime: python27
api_version: 1
threadsafe: true
+module: default
handlers:
- url: /.*
- script: main.app
-
-libraries:
-- name: jinja2
- version: 2.6
+ script: application.app
diff --git a/appengine/taskqueue/counter/counter_test.py b/appengine/taskqueue/counter/application_test.py
similarity index 62%
rename from appengine/taskqueue/counter/counter_test.py
rename to appengine/taskqueue/counter/application_test.py
index 6facb14b043..7303e7da30c 100644
--- a/appengine/taskqueue/counter/counter_test.py
+++ b/appengine/taskqueue/counter/application_test.py
@@ -12,18 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from google.appengine.ext import ndb
-import main
+import application
import webtest
+import worker
-def test_app(testbed, run_tasks):
- key_name = 'foo'
+def test_all(testbed, run_tasks):
+ test_app = webtest.TestApp(application.app)
+ test_worker = webtest.TestApp(worker.app)
- app = webtest.TestApp(main.app)
- app.post('/', {'key': key_name})
- run_tasks(app)
+ response = test_app.get('/')
+ assert '0' in response.body
- key = ndb.Key('Counter', key_name)
- counter = key.get()
- assert counter.count == 1
+ test_app.post('/enqueue', {'amount': 5})
+ run_tasks(test_worker)
+
+ response = test_app.get('/')
+ assert '5' in response.body
diff --git a/appengine/taskqueue/counter/counter.html b/appengine/taskqueue/counter/counter.html
deleted file mode 100644
index a9f6a39d360..00000000000
--- a/appengine/taskqueue/counter/counter.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-{% for counter in counters %}
--
- {{counter.key.id()|e}}: {{counter.count|e}}
-
-{% endfor %}
-
-
diff --git a/appengine/taskqueue/counter/main.py b/appengine/taskqueue/counter/main.py
deleted file mode 100644
index b25570bf1a1..00000000000
--- a/appengine/taskqueue/counter/main.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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.
-
-# [START all]
-import os
-
-from google.appengine.api import taskqueue
-from google.appengine.ext import ndb
-import jinja2
-import webapp2
-
-
-JINJA_ENV = jinja2.Environment(
- loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
-
-
-class Counter(ndb.Model):
- count = ndb.IntegerProperty(indexed=False)
-
-
-class CounterHandler(webapp2.RequestHandler):
- def get(self):
- template_values = {'counters': Counter.query()}
- counter_template = JINJA_ENV.get_template('counter.html')
- self.response.out.write(counter_template.render(template_values))
-
- def post(self):
- key = self.request.get('key')
- if key != '':
- # Add the task to the default queue.
- taskqueue.add(url='/worker', params={'key': key})
- self.redirect('/')
-
-
-class CounterWorker(webapp2.RequestHandler):
- def post(self): # should run at most 1/s due to entity group limit
- key = self.request.get('key')
-
- @ndb.transactional
- def update_counter():
- counter = Counter.get_or_insert(key, count=0)
- counter.count += 1
- counter.put()
- update_counter()
-
-
-app = webapp2.WSGIApplication([
- ('/', CounterHandler),
- ('/worker', CounterWorker)
-], debug=True)
-# [END all]
diff --git a/appengine/taskqueue/counter/queue.yaml b/appengine/taskqueue/counter/queue.yaml
index 97a10dfa9f5..6b677c17a2f 100644
--- a/appengine/taskqueue/counter/queue.yaml
+++ b/appengine/taskqueue/counter/queue.yaml
@@ -1,4 +1,4 @@
queue:
-# Change the refresh rate of the default queue from 5/s to 1/s
+# Change the refresh rate of the default queue from 5/s to 1/s.
- name: default
rate: 1/s
diff --git a/appengine/taskqueue/counter/worker.py b/appengine/taskqueue/counter/worker.py
new file mode 100644
index 00000000000..128615b0478
--- /dev/null
+++ b/appengine/taskqueue/counter/worker.py
@@ -0,0 +1,44 @@
+# 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
+import webapp2
+
+
+COUNTER_KEY = 'default counter'
+
+
+class Counter(ndb.Model):
+ count = ndb.IntegerProperty(indexed=False)
+
+
+class UpdateCounterHandler(webapp2.RequestHandler):
+ def post(self):
+ amount = int(self.request.get('amount'))
+
+ # This task should run at most once per second because of the datastore
+ # transaction write throughput.
+ @ndb.transactional
+ def update_counter():
+ counter = Counter.get_or_insert(COUNTER_KEY, count=0)
+ counter.count += amount
+ counter.put()
+
+ update_counter()
+
+
+app = webapp2.WSGIApplication([
+ ('/update_counter', UpdateCounterHandler)
+], debug=True)
+# [END all]
diff --git a/appengine/taskqueue/counter/worker.yaml b/appengine/taskqueue/counter/worker.yaml
new file mode 100644
index 00000000000..4ce72708d8e
--- /dev/null
+++ b/appengine/taskqueue/counter/worker.yaml
@@ -0,0 +1,8 @@
+runtime: python27
+api_version: 1
+threadsafe: true
+module: worker
+
+handlers:
+- url: /.*
+ script: worker.app