diff --git a/appengine/memcache/best_practices/README.md b/appengine/memcache/best_practices/README.md new file mode 100644 index 000000000000..9c72cb15aa81 --- /dev/null +++ b/appengine/memcache/best_practices/README.md @@ -0,0 +1,5 @@ +# Memcache Best Practices + +Code snippets for [Memcache Cache Best Practices article](https://cloud.google.com/appengine/articles/best-practices-for-app-engine-memcache) + + diff --git a/appengine/memcache/best_practices/batch/app.yaml b/appengine/memcache/best_practices/batch/app.yaml new file mode 100644 index 000000000000..ef0ebcae28b4 --- /dev/null +++ b/appengine/memcache/best_practices/batch/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: batch.app diff --git a/appengine/memcache/best_practices/batch/batch.py b/appengine/memcache/best_practices/batch/batch.py new file mode 100644 index 000000000000..a1267c8732fd --- /dev/null +++ b/appengine/memcache/best_practices/batch/batch.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. +# +# 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 logging + +from google.appengine.api import memcache +import webapp2 + + +class MainPage(webapp2.RequestHandler): + def get(self): + # [START batch] + values = {'comment': 'I did not ... ', 'comment_by': 'Bill Holiday'} + if not memcache.set_multi(values): + logging.error('Unable to set Memcache values') + tvalues = memcache.get_multi(('comment', 'comment_by')) + self.response.write(tvalues) + # [END batch] + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/appengine/memcache/best_practices/batch/batch_test.py b/appengine/memcache/best_practices/batch/batch_test.py new file mode 100644 index 000000000000..74363741fc15 --- /dev/null +++ b/appengine/memcache/best_practices/batch/batch_test.py @@ -0,0 +1,28 @@ +# 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. + +import batch + +import pytest +import webtest + + +@pytest.fixture +def app(testbed): + return webtest.TestApp(batch.app) + + +def test_get(app): + response = app.get('/') + assert 'Bill Holiday' in response.body diff --git a/appengine/memcache/best_practices/failure/app.yaml b/appengine/memcache/best_practices/failure/app.yaml new file mode 100644 index 000000000000..60fabb0490e5 --- /dev/null +++ b/appengine/memcache/best_practices/failure/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: failure.app diff --git a/appengine/memcache/best_practices/failure/failure.py b/appengine/memcache/best_practices/failure/failure.py new file mode 100644 index 000000000000..767d2094a785 --- /dev/null +++ b/appengine/memcache/best_practices/failure/failure.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. +# +# 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 logging + +from google.appengine.api import memcache +import webapp2 + + +def read_from_persistent_store(): + """Fake method for demonstration purposes. Usually would return + a value from a database like Cloud Datastore or MySQL.""" + return "a persistent value" + + +class ReadPage(webapp2.RequestHandler): + def get(self): + key = "some-key" + # [START memcache-read] + v = memcache.get(key) + if v is None: + v = read_from_persistent_store() + memcache.add(key, v) + # [END memcache-read] + + self.response.content_type = 'text/html' + self.response.write(str(v)) + + +class DeletePage(webapp2.RequestHandler): + def get(self): + key = "some key" + seconds = 5 + memcache.set(key, "some value") + # [START memcache-delete] + memcache.delete(key, seconds) # clears cache + # write to persistent datastore + # Do not attempt to put new value in cache, first reader will do that + # [END memcache-delete] + self.response.content_type = 'text/html' + self.response.write('done') + + +class MainPage(webapp2.RequestHandler): + def get(self): + value = 3 + # [START memcache-failure] + if not memcache.set('counter', value): + logging.error("Memcache set failed") + # Other error handling here + # [END memcache-failure] + self.response.content_type = 'text/html' + self.response.write('done') + + +app = webapp2.WSGIApplication([ + ('/', MainPage), + ('/delete', DeletePage), + ('/read', ReadPage), +], debug=True) diff --git a/appengine/memcache/best_practices/failure/failure_test.py b/appengine/memcache/best_practices/failure/failure_test.py new file mode 100644 index 000000000000..d1f034db123a --- /dev/null +++ b/appengine/memcache/best_practices/failure/failure_test.py @@ -0,0 +1,35 @@ +# 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. + +import failure + +import pytest +import webtest + + +@pytest.fixture +def app(testbed): + return webtest.TestApp(failure.app) + + +def test_get(app): + app.get('/') + + +def test_read(app): + app.get('/read') + + +def test_delete(app): + app.get('/delete') diff --git a/appengine/memcache/best_practices/migration_step1/app.yaml b/appengine/memcache/best_practices/migration_step1/app.yaml new file mode 100644 index 000000000000..02e9132c4b05 --- /dev/null +++ b/appengine/memcache/best_practices/migration_step1/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: migration1.app diff --git a/appengine/memcache/best_practices/migration_step1/migration1.py b/appengine/memcache/best_practices/migration_step1/migration1.py new file mode 100644 index 000000000000..3cd3ad7a7250 --- /dev/null +++ b/appengine/memcache/best_practices/migration_step1/migration1.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. +# +# 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 logging + +from google.appengine.api import memcache +from google.appengine.ext import ndb +import webapp2 + + +# [START best-practice-1] +class Person(ndb.Model): + name = ndb.StringProperty(required=True) + + +def get_or_add_person(name): + person = memcache.get(name) + if person is None: + person = Person(name=name) + memcache.add(name, person) + else: + logging.info('Found in cache: ' + name) + return person +# [END best-practice-1] + + +class MainPage(webapp2.RequestHandler): + def get(self): + person = get_or_add_person('Stevie Wonder') + self.response.content_type = 'text/html' + self.response.write(person.name) + + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/appengine/memcache/best_practices/migration_step1/migration1_test.py b/appengine/memcache/best_practices/migration_step1/migration1_test.py new file mode 100644 index 000000000000..794188b8aa11 --- /dev/null +++ b/appengine/memcache/best_practices/migration_step1/migration1_test.py @@ -0,0 +1,22 @@ +# 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. + +import migration1 + +import webtest + + +def test_get(testbed): + app = webtest.TestApp(migration1.app) + app.get('/') diff --git a/appengine/memcache/best_practices/migration_step2/app.yaml b/appengine/memcache/best_practices/migration_step2/app.yaml new file mode 100644 index 000000000000..0a2dd7051d8d --- /dev/null +++ b/appengine/memcache/best_practices/migration_step2/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: migration2.app diff --git a/appengine/memcache/best_practices/migration_step2/migration2.py b/appengine/memcache/best_practices/migration_step2/migration2.py new file mode 100644 index 000000000000..1f3b0c810ee9 --- /dev/null +++ b/appengine/memcache/best_practices/migration_step2/migration2.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. +# +# 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 logging + +from google.appengine.api import memcache +from google.appengine.ext import ndb +import webapp2 + + +# [START best-practice-2] +class Person(ndb.Model): + name = ndb.StringProperty(required=True) + userid = ndb.StringProperty(required=True) + + +def get_or_add_person(name, userid): + person = memcache.get(name) + if person is None: + person = Person(name=name, userid=userid) + memcache.add(name, person) + else: + logging.info('Found in cache: ' + name + ', userid: ' + person.userid) + return person +# [END best-practice-2] + + +class MainPage(webapp2.RequestHandler): + def get(self): + person = get_or_add_person('Stevie Wonder', "1") + self.response.content_type = 'text/html' + self.response.write(person.name) + + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/appengine/memcache/best_practices/migration_step2/migration2_test.py b/appengine/memcache/best_practices/migration_step2/migration2_test.py new file mode 100644 index 000000000000..be70621692bb --- /dev/null +++ b/appengine/memcache/best_practices/migration_step2/migration2_test.py @@ -0,0 +1,22 @@ +# 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. + +import migration2 + +import webtest + + +def test_get(testbed): + app = webtest.TestApp(migration2.app) + app.get('/') diff --git a/appengine/memcache/best_practices/sharing/app.yaml b/appengine/memcache/best_practices/sharing/app.yaml new file mode 100644 index 000000000000..17dc52934b59 --- /dev/null +++ b/appengine/memcache/best_practices/sharing/app.yaml @@ -0,0 +1,7 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: sharing.app diff --git a/appengine/memcache/best_practices/sharing/sharing.py b/appengine/memcache/best_practices/sharing/sharing.py new file mode 100644 index 000000000000..3371c0ef1ebb --- /dev/null +++ b/appengine/memcache/best_practices/sharing/sharing.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc. +# +# 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 memcache +import webapp2 + + +class MainPage(webapp2.RequestHandler): + def get(self): + # [START sharing] + self.response.headers['Content-Type'] = 'text/plain' + + who = memcache.get('who') + self.response.write('Previously incremented by %s\n' % who) + memcache.set('who', 'Python') + + count = memcache.incr('count', 1, initial_value=0) + self.response.write('Count incremented by Python = %s\n' % count) + # [END sharing] + + +app = webapp2.WSGIApplication([ + ('/', MainPage), +], debug=True) diff --git a/appengine/memcache/best_practices/sharing/sharing_test.py b/appengine/memcache/best_practices/sharing/sharing_test.py new file mode 100644 index 000000000000..c23ee61f2d9b --- /dev/null +++ b/appengine/memcache/best_practices/sharing/sharing_test.py @@ -0,0 +1,23 @@ +# 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. + +import sharing + +import webtest + + +def test_get(testbed): + app = webtest.TestApp(sharing.app) + response = app.get('/') + assert 'Previously incremented by ' in response.body