From 5808366e7cf870be642840dc8d56c678da0dc22e Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Wed, 23 Jun 2021 10:59:23 -0700
Subject: [PATCH 01/56] add video to README
---
README.md | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index 93db30a..1fdeecc 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
# Python App Engine app migration
### To modern runtime, Cloud services, Python 3, and Cloud Run containers
-[Google App Engine](https://cloud.google.com/appengine) (Standard) has undergone significant changes between the legacy and next generation platforms. To address this, we've created a set of codelabs (free, online, self-paced, hands-on tutorials) to show developers how to perform individual migrations they can apply to modernize their apps for the latest runtimes, with this repo managing the samples from those codelabs.
+[Google App Engine](https://cloud.google.com/appengine) (Standard) has undergone significant changes between the legacy and next generation platforms. To address this, we've created a set of codelabs (free, online, self-paced, hands-on tutorials) and corresponding videos (when available) to show developers how to perform individual migrations they can apply to modernize their apps for the latest runtimes, with this repo managing the samples from those codelabs.
Each codelab begins with a "START" code base then walks developers through that migration step, resulting in a "FINISH" repo. If you make any mistakes along the way, you can always go back to START or compare your code with what's in the FINISH folder to see the differences. We also want to help you port to the Python 3 runtime, so some codelabs contain a bonus section for that purpose.
> **NOTE:** These migrations are *typically* aimed at Python 2 users
-> 1. *Python 3.x App Engine users*: You're *already* on the next-gen platform, so only for **non**-legacy service migrations
+> 1. *Python 3.x App Engine users*: You're *already* on the next-gen platform, so only look for **non**-legacy service migrations
> 1. *Python 2.5 App Engine users*: to revive apps from the original 2.5 runtime, [deprecated in 2013](http://googleappengine.blogspot.com/2013/03/python-25-thanks-for-good-times.html) and [shutdown in 2017](https://cloud.google.com/appengine/docs/standard/python/python25), you must [migrate from `db` to `ndb`](http://cloud.google.com/appengine/docs/standard/python/ndb/db_to_ndb) and get those apps running on Python 2.7 before attempting these migrations.
@@ -67,20 +67,20 @@ The table below summarizes migration module resources currently available along
### Summary table
-Module | Topic | Codelab | START here | FINISH here
---- | --- | --- | --- | ---
-0|Baseline app| _N/A_ (no tutorial; just review the code) | _N/A_ | Module 0 [code](/mod0-baseline) (2.x)
-1|Migrate to Flask| [link](http://g.co/codelabs/pae-migrate-flask) | Module 0 [code](/mod0-baseline) (2.x) | Module 1 [code](/mod1-flask) (2.x)
-2|Migrate to Cloud NDB| [link](http://g.co/codelabs/pae-migrate-cloudndb) | Module 1 [code](/mod1-flask) (2.x) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x)
-3|Migrate to Cloud Datastore| [link](http://g.co/codelabs/pae-migrate-datastore) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x) | Module 3 [code](/mod3a-datastore) (2.x) & [code](/mod3b-datastore) (3.x)
-4|Migrate to Cloud Run with Docker| [link](http://g.co/codelabs/pae-migrate-rundocker) | Module 2 [code](/mod2a-cloudndb) (2.x) & Module 3 [code](/mod3b-datastore) (3.x) | Module 4 [code](/mod4a-rundocker) (2.x) & [code](/mod4b-rundocker) (3.x)
-5|Migrate to Cloud Run with Buildpacks| [link](http://g.co/codelabs/pae-migrate-runbldpks) | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 5 [code](/mod5-runbldpks) (3.x)
-6|Migrate to Cloud Firestore (app)| [link](http://g.co/codelabs/pae-migrate-firestore) | Module 3 [code](/mod3b-datastore) (3.x) | Module 6 [code](/mod6-firestore) (3.x)
-7|Add App Engine push tasks| [link](http://g.co/codelabs/pae-migrate-gaetasks) | Module 1 [code]() (2.x) | Module 7 [code](/mod7-gaetasks) (2.x)
-8|Migrate to Cloud Tasks| [link](http://g.co/codelabs/pae-migrate-cloudtasks) | Module 7 [code](/mod7-gaetasks) (2.x) | Module 8 [code](/mod8-cloudtasks) (2.x)
-9|Migrate to Python 3 (Cloud Datastore & Cloud Tasks v2)| [link](http://g.co/codelabs/pae-migrate-py3dstasks) | Module 8 [code](/mod8-cloudtasks) (2.x) | Module 9 [code](/mod9-py3dstasks) (3.x)
-10|Migrate to Cloud Firestore (data)| _N/A_ | _N/A_ | _TBD_
-11|Migrate to Cloud Functions| _TBD_ | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 11 [code](/mod11-functions) (3.x)
+Module | Topic | Video | Codelab | START here | FINISH here
+--- | --- | --- | --- | --- | ---
+0|Baseline app| [link](http://twitter.com/googledevs/status/1407755281786867714?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_smsintro_201023&utm_content=-) | _N/A_ (no tutorial; just review the code) | _N/A_ | Module 0 [code](/mod0-baseline) (2.x)
+1|Migrate to Flask| _TBD_ | [link](http://g.co/codelabs/pae-migrate-flask) | Module 0 [code](/mod0-baseline) (2.x) | Module 1 [code](/mod1-flask) (2.x)
+2|Migrate to Cloud NDB| _TBD_ | [link](http://g.co/codelabs/pae-migrate-cloudndb) | Module 1 [code](/mod1-flask) (2.x) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x)
+3|Migrate to Cloud Datastore| _TBD_ | [link](http://g.co/codelabs/pae-migrate-datastore) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x) | Module 3 [code](/mod3a-datastore) (2.x) & [code](/mod3b-datastore) (3.x)
+4|Migrate to Cloud Run with Docker| _TBD_ | [link](http://g.co/codelabs/pae-migrate-rundocker) | Module 2 [code](/mod2a-cloudndb) (2.x) & Module 3 [code](/mod3b-datastore) (3.x) | Module 4 [code](/mod4a-rundocker) (2.x) & [code](/mod4b-rundocker) (3.x)
+5|Migrate to Cloud Run with Buildpacks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-runbldpks) | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 5 [code](/mod5-runbldpks) (3.x)
+6|Migrate to Cloud Firestore (app)| _TBD_ | [link](http://g.co/codelabs/pae-migrate-firestore) | Module 3 [code](/mod3b-datastore) (3.x) | Module 6 [code](/mod6-firestore) (3.x)
+7|Add App Engine push tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-gaetasks) | Module 1 [code]() (2.x) | Module 7 [code](/mod7-gaetasks) (2.x)
+8|Migrate to Cloud Tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-cloudtasks) | Module 7 [code](/mod7-gaetasks) (2.x) | Module 8 [code](/mod8-cloudtasks) (2.x)
+9|Migrate to Python 3 (Cloud Datastore & Cloud Tasks v2)| _TBD_ | [link](http://g.co/codelabs/pae-migrate-py3dstasks) | Module 8 [code](/mod8-cloudtasks) (2.x) | Module 9 [code](/mod9-py3dstasks) (3.x)
+10|Migrate to Cloud Firestore (data)| _TBD_ | _N/A_ | _N/A_ | _TBD_
+11|Migrate to Cloud Functions| _TBD_ | _TBD_ | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 11 [code](/mod11-functions) (3.x)
### Table of contents
From 3c6eff6a85dabb001509d1b98afdbe3636195bb1 Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Tue, 29 Jun 2021 00:01:46 -0700
Subject: [PATCH 02/56] rev pkg versions, switch Mod9 from DS to FS
---
mod6-firestore/requirements.txt | 2 +-
mod8-cloudtasks/main.py | 3 +-
mod9-py3dstasks/README.md | 2 +-
mod9-py3dstasks/main.py | 60 ++++++++++++++++++++------------
mod9-py3dstasks/requirements.txt | 2 +-
5 files changed, 43 insertions(+), 26 deletions(-)
diff --git a/mod6-firestore/requirements.txt b/mod6-firestore/requirements.txt
index 72ec3d4..9602ece 100644
--- a/mod6-firestore/requirements.txt
+++ b/mod6-firestore/requirements.txt
@@ -1,2 +1,2 @@
flask==1.1.2
-google-cloud-firestore==2.0.2
+google-cloud-firestore==2.1.3
diff --git a/mod8-cloudtasks/main.py b/mod8-cloudtasks/main.py
index 9150967..abd5876 100644
--- a/mod8-cloudtasks/main.py
+++ b/mod8-cloudtasks/main.py
@@ -17,13 +17,14 @@
import logging
import time
from flask import Flask, render_template, request
+import google.auth
from google.cloud import ndb, tasks
app = Flask(__name__)
ds_client = ndb.Client()
ts_client = tasks.CloudTasksClient()
-PROJECT_ID = 'PROJECT_ID' # replace w/your own
+_, PROJECT_ID = google.auth.default()
REGION_ID = 'REGION_ID' # replace w/your own
QUEUE_NAME = 'default' # replace w/your own
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION_ID, QUEUE_NAME)
diff --git a/mod9-py3dstasks/README.md b/mod9-py3dstasks/README.md
index 613ea03..9513ffd 100644
--- a/mod9-py3dstasks/README.md
+++ b/mod9-py3dstasks/README.md
@@ -1,3 +1,3 @@
# Module 9 - Migrate from Python 2 to 3 and Cloud NDB to Cloud Datastore
-This repo folder is the corresponding Python 3 code to the [Module 9 codelab](http://g.co/codelabs/pae-migrate-py3dstasks). The tutorial STARTs with the Python 2 code in the [Module 8 repo folder](/mod7-cloudtasks) and leads developers through migrating from Python 2 to 3, Cloud NDB to Cloud Datastore (plus any changes from Cloud Tasks v1 to v2), culminating in the code in this folder.
+This repo folder is the corresponding Python 3 code to the [Module 9 codelab](http://g.co/codelabs/pae-migrate-py3dstasks). The tutorial STARTs with the Python 2 code in the [Module 8 repo folder](/mod7-cloudtasks) and leads developers through migrating from Python 2 to 3, Cloud NDB to Cloud Firestore (plus any changes from Cloud Tasks v1 to v2), culminating in the code in this folder.
diff --git a/mod9-py3dstasks/main.py b/mod9-py3dstasks/main.py
index e4222cf..9720083 100644
--- a/mod9-py3dstasks/main.py
+++ b/mod9-py3dstasks/main.py
@@ -16,32 +16,43 @@
import json
import time
from flask import Flask, render_template, request
-from google.cloud import datastore, tasks
+import google.auth
+from google.cloud import firestore, tasks
app = Flask(__name__)
-ds_client = datastore.Client()
+fs_client = firestore.Client()
ts_client = tasks.CloudTasksClient()
-PROJECT_ID = 'PROJECT_ID' # replace w/your own
+_, PROJECT_ID = google.auth.default()
REGION_ID = 'REGION_ID' # replace w/your own
QUEUE_NAME = 'default' # replace w/your own
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION_ID, QUEUE_NAME)
+PATH_PREFIX = QUEUE_PATH.rsplit('/', 2)[0]
def store_visit(remote_addr, user_agent):
- 'create new Visit entity in Datastore'
- entity = datastore.Entity(key=ds_client.key('Visit'))
- entity.update({
+ 'create new Visit document in Firestore'
+ doc_ref = fs_client.collection('Visit')
+ doc_ref.add({
'timestamp': datetime.now(),
'visitor': '{}: {}'.format(remote_addr, user_agent),
})
- ds_client.put(entity)
+
+def create_queue_if():
+ 'app-internal function creating default queue if it does not exist'
+ try:
+ ts_client.get_queue(name=QUEUE_PATH)
+ except Exception as e:
+ if 'does not exist' in str(e):
+ ts_client.create_queue(parent=PATH_PREFIX,
+ queue={'name': QUEUE_PATH})
+ return True
def fetch_visits(limit):
'get most recent visits & add task to delete older visits'
- query = ds_client.query(kind='Visit')
- query.order = ['-timestamp']
- data = list(query.fetch(limit=limit))
- oldest = time.mktime(data[-1]['timestamp'].timetuple())
+ visits_ref = fs_client.collection('Visit')
+ visits = list(v.to_dict() for v in visits_ref.order_by('timestamp',
+ direction=firestore.Query.DESCENDING).limit(limit).stream())
+ oldest = time.mktime(visits[-1]['timestamp'].timetuple())
oldest_str = time.ctime(oldest)
print('Delete entities older than %s' % oldest_str)
task = {
@@ -53,22 +64,27 @@ def fetch_visits(limit):
},
}
}
- ts_client.create_task(parent=QUEUE_PATH, task=task)
- return data, oldest_str
+ if create_queue_if():
+ ts_client.create_task(parent=QUEUE_PATH, task=task)
+ return visits, oldest_str
+
+def _delete_docs(visits):
+ 'app-internal generator deleteing all old FS visit documents'
+ for visit in visits:
+ visit.reference.delete()
+ yield visit.id
@app.route('/trim', methods=['POST'])
def trim():
'(push) task queue handler to delete oldest visits'
oldest = float(request.get_json().get('oldest'))
- query = ds_client.query(kind='Visit')
- query.add_filter('timestamp', '<', datetime.fromtimestamp(oldest))
- query.keys_only()
- keys = list(visit.key for visit in query.fetch())
- nkeys = len(keys)
- if nkeys:
- print('Deleting %d entities: %s' % (
- nkeys, ', '.join(str(k.id) for k in keys)))
- ds_client.delete_multi(keys)
+ query = fs_client.collection('Visit')
+ visits = list(query.where('timestamp', '<',
+ datetime.fromtimestamp(oldest)).stream())
+ nvisits = len(visits)
+ if nvisits:
+ print('Deleting %d entities: ' % nvisits, end='')
+ print(', '.join(str(v_id) for v_id in _delete_docs(visits)))
else:
print('No entities older than: %s' % time.ctime(oldest))
return '' # need to return SOME string w/200
diff --git a/mod9-py3dstasks/requirements.txt b/mod9-py3dstasks/requirements.txt
index acc1e1b..ed5a882 100644
--- a/mod9-py3dstasks/requirements.txt
+++ b/mod9-py3dstasks/requirements.txt
@@ -1,3 +1,3 @@
flask==1.1.2
-google-cloud-datastore==2.1.3
+google-cloud-firestore==2.1.3
google-cloud-tasks==2.3.0
From 341e58cfd602603a23e63279f51c40c6d5e60f2a Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Tue, 29 Jun 2021 00:12:12 -0700
Subject: [PATCH 03/56] change Mod9 from DS to FS
---
README.md | 14 ++++++--------
mod9-py3dstasks/README.md | 3 ---
{mod9-py3dstasks => mod9-py3fstasks}/.gcloudignore | 0
mod9-py3fstasks/README.md | 3 +++
{mod9-py3dstasks => mod9-py3fstasks}/app.yaml | 0
{mod9-py3dstasks => mod9-py3fstasks}/main.py | 0
.../requirements.txt | 0
.../templates/index.html | 0
8 files changed, 9 insertions(+), 11 deletions(-)
delete mode 100644 mod9-py3dstasks/README.md
rename {mod9-py3dstasks => mod9-py3fstasks}/.gcloudignore (100%)
create mode 100644 mod9-py3fstasks/README.md
rename {mod9-py3dstasks => mod9-py3fstasks}/app.yaml (100%)
rename {mod9-py3dstasks => mod9-py3fstasks}/main.py (100%)
rename {mod9-py3dstasks => mod9-py3fstasks}/requirements.txt (100%)
rename {mod9-py3dstasks => mod9-py3fstasks}/templates/index.html (100%)
diff --git a/README.md b/README.md
index 1fdeecc..ca8b522 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ Module | Topic | Video | Codelab | START here | FINISH here
6|Migrate to Cloud Firestore (app)| _TBD_ | [link](http://g.co/codelabs/pae-migrate-firestore) | Module 3 [code](/mod3b-datastore) (3.x) | Module 6 [code](/mod6-firestore) (3.x)
7|Add App Engine push tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-gaetasks) | Module 1 [code]() (2.x) | Module 7 [code](/mod7-gaetasks) (2.x)
8|Migrate to Cloud Tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-cloudtasks) | Module 7 [code](/mod7-gaetasks) (2.x) | Module 8 [code](/mod8-cloudtasks) (2.x)
-9|Migrate to Python 3 (Cloud Datastore & Cloud Tasks v2)| _TBD_ | [link](http://g.co/codelabs/pae-migrate-py3dstasks) | Module 8 [code](/mod8-cloudtasks) (2.x) | Module 9 [code](/mod9-py3dstasks) (3.x)
+9|Migrate to Python 3, Cloud Firestore & Cloud Tasks v2| _TBD_ | _TBD_ | Module 8 [code](/mod8-cloudtasks) (2.x) | Module 9 [code](/mod9-py3fstasks) (3.x)
10|Migrate to Cloud Firestore (data)| _TBD_ | _N/A_ | _N/A_ | _TBD_
11|Migrate to Cloud Functions| _TBD_ | _TBD_ | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 11 [code](/mod11-functions) (3.x)
@@ -130,20 +130,18 @@ If there is a logical codelab to do immediately after completing one, they will
- FINISH: [Module 8 code - Cloud Tasks](/mod8-cloudtasks) (2.x)
- NEXT: Module 9 - migrate to Python 3 and Cloud Datastore
-- [Module 9 codelab](http://g.co/codelabs/pae-migrate-py3dstasks): **Migrate a Python 2 Cloud NDB & Cloud Tasks app to a Python 3 Cloud Datastore app**
- - **Mixed migration recommendation**
- - Migrating to Python 3 is required, but...
- - Migrating to Cloud Datastore is optional as Cloud NDB works on 3.x; it's to give you the experience of doing it
- - This codelab includes the [Module 3 migration codelab](http://g.co/codelabs/pae-migrate-datastore), so skip if you complete this one
+- **Module 9 codelab** (TBD): **Migrate a Python 2 Cloud NDB & Cloud Tasks (v1) app to a Python 3 Cloud Firestore & Cloud Tasks (v2) app**
+ - **Optional** migrations
+ - Migrating to Python 3 is not required but recommended as Python 2 has been sunset
+ - Migrating to Cloud Firestore is *very* optional as Cloud NDB works on 3.x and most importantly, Cloud Firestore requires a completely new GCP project
- Python 2
- START: [Module 8 code - Cloud Tasks](/mod8-cloudtasks) (2.x)
- Python 3
- - FINISH: [Module 9 code - Cloud Datastore & Tasks](/mod9-py3dstasks) (3.x)
+ - FINISH: [Module 9 code - Cloud Firestore & Tasks](/mod9-py3fstasks) (3.x)
- RECOMMENDED:
- Module 11 - migrate to Cloud Functions
- Module 5 - migrate to Cloud Run container with Cloud Buildpacks
- Module 4 - migrate to Cloud Run container with Docker
- - Module 6 - migrate to Cloud Firestore (app)
- [Module 4 codelab](http://g.co/codelabs/pae-migrate-rundocker): **Migrate from App Engine to [Cloud Run](http://cloud.google.com/run) with Docker**
- **Optional** migration
diff --git a/mod9-py3dstasks/README.md b/mod9-py3dstasks/README.md
deleted file mode 100644
index 9513ffd..0000000
--- a/mod9-py3dstasks/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Module 9 - Migrate from Python 2 to 3 and Cloud NDB to Cloud Datastore
-
-This repo folder is the corresponding Python 3 code to the [Module 9 codelab](http://g.co/codelabs/pae-migrate-py3dstasks). The tutorial STARTs with the Python 2 code in the [Module 8 repo folder](/mod7-cloudtasks) and leads developers through migrating from Python 2 to 3, Cloud NDB to Cloud Firestore (plus any changes from Cloud Tasks v1 to v2), culminating in the code in this folder.
diff --git a/mod9-py3dstasks/.gcloudignore b/mod9-py3fstasks/.gcloudignore
similarity index 100%
rename from mod9-py3dstasks/.gcloudignore
rename to mod9-py3fstasks/.gcloudignore
diff --git a/mod9-py3fstasks/README.md b/mod9-py3fstasks/README.md
new file mode 100644
index 0000000..4b636ca
--- /dev/null
+++ b/mod9-py3fstasks/README.md
@@ -0,0 +1,3 @@
+# Module 9 - Migrate from Python 2 to 3 and Cloud NDB to Cloud Firestore
+
+This repo folder is the corresponding Python 3 code to the [Module 9 codelab](http://g.co/codelabs/pae-migrate-py3fstasks). The tutorial STARTs with the Python 2 code in the [Module 8 repo folder](/mod7-cloudtasks) and leads developers through migrating from Python 2 to 3, Cloud NDB to Cloud Firestore (skipping over a Cloud Datstore migration) plus any changes from Cloud Tasks v1 to v2, culminating in the code in this folder. One major addition to look for here vs. Module 8 is that App Engine `taskqueue` creates a `default` push queue while Cloud Tasks does not, so that now has to be done in code.
diff --git a/mod9-py3dstasks/app.yaml b/mod9-py3fstasks/app.yaml
similarity index 100%
rename from mod9-py3dstasks/app.yaml
rename to mod9-py3fstasks/app.yaml
diff --git a/mod9-py3dstasks/main.py b/mod9-py3fstasks/main.py
similarity index 100%
rename from mod9-py3dstasks/main.py
rename to mod9-py3fstasks/main.py
diff --git a/mod9-py3dstasks/requirements.txt b/mod9-py3fstasks/requirements.txt
similarity index 100%
rename from mod9-py3dstasks/requirements.txt
rename to mod9-py3fstasks/requirements.txt
diff --git a/mod9-py3dstasks/templates/index.html b/mod9-py3fstasks/templates/index.html
similarity index 100%
rename from mod9-py3dstasks/templates/index.html
rename to mod9-py3fstasks/templates/index.html
From fad00bb6af59cbe2f79f411dd7b787a7c111c609 Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Sat, 3 Jul 2021 00:35:04 -0700
Subject: [PATCH 04/56] README & gcloudignore tweaks, minor memory usage fix in
Mod9
---
README.md | 11 +++++------
mod0-baseline/.gcloudignore | 5 ++---
mod9-py3fstasks/main.py | 15 +++++++--------
3 files changed, 14 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index ca8b522..5fc7ac1 100644
--- a/README.md
+++ b/README.md
@@ -246,8 +246,8 @@ If your original app users does *not* have a user interface, i.e., mobile backen
- This repo, along with corresponding codelabs & videos are complementary to the official docs & code samples.
- The [official Python 2 to 3 migration documentation](https://cloud.google.com/appengine/docs/standard/python/migrate-to-python3)
- [Canonical migration code samples repo](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration)
- - Example: [GAE `ndb` to Cloud NDB](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/ndb/overview)
- - Example: [GAE `taskqueue` to Cloud Tasks](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/taskqueue)
+ - *Example:* [GAE `ndb` to Cloud NDB](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/ndb/overview) (similar to Module 2)
+ - *Example:* [GAE `taskqueue` to Cloud Tasks](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/taskqueue) (similar to Module 8)
## References
@@ -255,15 +255,14 @@ If your original app users does *not* have a user interface, i.e., mobile backen
- App Engine Migration
- [Migrate from Python 2 to 3](http://cloud.google.com/appengine/docs/standard/python/migrate-to-python3)
- [Migrate from App Engine `ndb` to Cloud NDB](http://cloud.google.com/appengine/docs/standard/python/migrate-to-python3/migrate-to-cloud-ndb) (Module 2)
- - [App Engine `ndb` to Cloud NDB official sample app](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/ndb/overview) (Module 2)
- [Migrate from App Engine `taskqueue` to Cloud Tasks](http://cloud.google.com/appengine/docs/standard/python/migrate-to-python3/migrate-to-cloud-ndb) (Modules 7-9)
- - [App Engine `app.yaml` to Cloud Run `service.yaml` tool](http://googlecloudplatform.github.io/app-engine-cloud-run-converter) (Modules 4 and 5)
- [Migrate from App Engine `db` to `ndb`](http://cloud.google.com/appengine/docs/standard/python/ndb/db_to_ndb) ("Module -1"; only for reviving "dead" Python 2.5 apps for 2.7)
- [Community contributed migration samples](https://github.com/GoogleCloudPlatform/appengine-python2-3-migration)
- Python App Engine
- - [Python 2 App Engine (Standard)](https://cloud.google.com/appengine/docs/standard/python/runtime)
- - [Python 3 App Engine (Standard)](https://cloud.google.com/appengine/docs/standard/python3/runtime)
+ - [App Engine 1st vs. 2nd generation runtimes](https://cloud.google.com/appengine/docs/standard/runtimes)
+ - [Python 2 App Engine (Standard) runtime](https://cloud.google.com/appengine/docs/standard/python/runtime)
+ - [Python 3 App Engine (Standard) runtime](https://cloud.google.com/appengine/docs/standard/python3/runtime)
- [Python App Engine (Flexible)](https://cloud.google.com/appengine/docs/flexible/python)
- Google Cloud Platform (GCP)
diff --git a/mod0-baseline/.gcloudignore b/mod0-baseline/.gcloudignore
index bcf97a8..ee1a5ea 100644
--- a/mod0-baseline/.gcloudignore
+++ b/mod0-baseline/.gcloudignore
@@ -9,14 +9,13 @@
.gcloudignore
# Ignore source code control maintenance files
-.git
+.git/
.gitignore
.hgignore
.hg/
# Python files
-*.pyc
-*.pyo
+*.py[cod]
__pycache__/
/setup.cfg
diff --git a/mod9-py3fstasks/main.py b/mod9-py3fstasks/main.py
index 9720083..3727ca5 100644
--- a/mod9-py3fstasks/main.py
+++ b/mod9-py3fstasks/main.py
@@ -37,7 +37,7 @@ def store_visit(remote_addr, user_agent):
'visitor': '{}: {}'.format(remote_addr, user_agent),
})
-def create_queue_if():
+def _create_queue_if():
'app-internal function creating default queue if it does not exist'
try:
ts_client.get_queue(name=QUEUE_PATH)
@@ -64,7 +64,7 @@ def fetch_visits(limit):
},
}
}
- if create_queue_if():
+ if _create_queue_if():
ts_client.create_task(parent=QUEUE_PATH, task=task)
return visits, oldest_str
@@ -79,12 +79,11 @@ def trim():
'(push) task queue handler to delete oldest visits'
oldest = float(request.get_json().get('oldest'))
query = fs_client.collection('Visit')
- visits = list(query.where('timestamp', '<',
- datetime.fromtimestamp(oldest)).stream())
- nvisits = len(visits)
- if nvisits:
- print('Deleting %d entities: ' % nvisits, end='')
- print(', '.join(str(v_id) for v_id in _delete_docs(visits)))
+ visits = query.where('timestamp', '<',
+ datetime.fromtimestamp(oldest)).stream()
+ dlist = ', '.join(str(v_id) for v_id in _delete_docs(visits))
+ if dlist:
+ print('Deleting %d entities: %s' % (dlist.count(',')+1, dlist))
else:
print('No entities older than: %s' % time.ctime(oldest))
return '' # need to return SOME string w/200
From 51818ac4b985bd27f0481379f85e7aea6bc15710 Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Sat, 17 Jul 2021 01:53:21 -0700
Subject: [PATCH 05/56] update copyright year & pkg version#s
---
mod0-baseline/app.yaml | 2 +-
mod0-baseline/main.py | 2 +-
mod1-flask/app.yaml | 2 +-
mod1-flask/appengine_config.py | 2 +-
mod11-functions/main.py | 2 +-
mod2a-cloudndb/app.yaml | 2 +-
mod2a-cloudndb/appengine_config.py | 2 +-
mod2a-cloudndb/main.py | 2 +-
mod2b-cloudndb/app.yaml | 2 +-
mod2b-cloudndb/main.py | 2 +-
mod3a-datastore/app.yaml | 2 +-
mod3a-datastore/appengine_config.py | 2 +-
mod3a-datastore/main.py | 2 +-
mod3b-datastore/app.yaml | 2 +-
mod3b-datastore/main.py | 2 +-
mod4a-rundocker/main.py | 2 +-
mod4b-rundocker/main.py | 2 +-
mod5-runbldpks/main.py | 2 +-
mod6-firestore/app.yaml | 2 +-
mod6-firestore/main.py | 2 +-
mod7-gaetasks/app.yaml | 2 +-
mod7-gaetasks/appengine_config.py | 2 +-
mod7-gaetasks/main.py | 2 +-
mod8-cloudtasks/app.yaml | 2 +-
mod8-cloudtasks/appengine_config.py | 2 +-
mod8-cloudtasks/main.py | 2 +-
mod9-py3fstasks/app.yaml | 2 +-
mod9-py3fstasks/main.py | 2 +-
28 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/mod0-baseline/app.yaml b/mod0-baseline/app.yaml
index 80fb603..0d4d8d6 100644
--- a/mod0-baseline/app.yaml
+++ b/mod0-baseline/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod0-baseline/main.py b/mod0-baseline/main.py
index f21d8b0..29ef681 100644
--- a/mod0-baseline/main.py
+++ b/mod0-baseline/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod1-flask/app.yaml b/mod1-flask/app.yaml
index 80fb603..0d4d8d6 100644
--- a/mod1-flask/app.yaml
+++ b/mod1-flask/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod1-flask/appengine_config.py b/mod1-flask/appengine_config.py
index 0ca8634..9760ebb 100644
--- a/mod1-flask/appengine_config.py
+++ b/mod1-flask/appengine_config.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod11-functions/main.py b/mod11-functions/main.py
index 30e2f02..6c0acc6 100644
--- a/mod11-functions/main.py
+++ b/mod11-functions/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod2a-cloudndb/app.yaml b/mod2a-cloudndb/app.yaml
index 03e7e70..0877f79 100644
--- a/mod2a-cloudndb/app.yaml
+++ b/mod2a-cloudndb/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod2a-cloudndb/appengine_config.py b/mod2a-cloudndb/appengine_config.py
index 4773cd7..2a41fb4 100644
--- a/mod2a-cloudndb/appengine_config.py
+++ b/mod2a-cloudndb/appengine_config.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod2a-cloudndb/main.py b/mod2a-cloudndb/main.py
index 1d95ed7..4c3015a 100644
--- a/mod2a-cloudndb/main.py
+++ b/mod2a-cloudndb/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod2b-cloudndb/app.yaml b/mod2b-cloudndb/app.yaml
index a74df21..a4ce819 100644
--- a/mod2b-cloudndb/app.yaml
+++ b/mod2b-cloudndb/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod2b-cloudndb/main.py b/mod2b-cloudndb/main.py
index 1d95ed7..4c3015a 100644
--- a/mod2b-cloudndb/main.py
+++ b/mod2b-cloudndb/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod3a-datastore/app.yaml b/mod3a-datastore/app.yaml
index 03e7e70..0877f79 100644
--- a/mod3a-datastore/app.yaml
+++ b/mod3a-datastore/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod3a-datastore/appengine_config.py b/mod3a-datastore/appengine_config.py
index 4773cd7..2a41fb4 100644
--- a/mod3a-datastore/appengine_config.py
+++ b/mod3a-datastore/appengine_config.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod3a-datastore/main.py b/mod3a-datastore/main.py
index 4eaa4dd..73a8eeb 100644
--- a/mod3a-datastore/main.py
+++ b/mod3a-datastore/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod3b-datastore/app.yaml b/mod3b-datastore/app.yaml
index a74df21..a4ce819 100644
--- a/mod3b-datastore/app.yaml
+++ b/mod3b-datastore/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod3b-datastore/main.py b/mod3b-datastore/main.py
index 4eaa4dd..73a8eeb 100644
--- a/mod3b-datastore/main.py
+++ b/mod3b-datastore/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod4a-rundocker/main.py b/mod4a-rundocker/main.py
index 1d95ed7..4c3015a 100644
--- a/mod4a-rundocker/main.py
+++ b/mod4a-rundocker/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod4b-rundocker/main.py b/mod4b-rundocker/main.py
index 4eaa4dd..73a8eeb 100644
--- a/mod4b-rundocker/main.py
+++ b/mod4b-rundocker/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod5-runbldpks/main.py b/mod5-runbldpks/main.py
index 1d95ed7..4c3015a 100644
--- a/mod5-runbldpks/main.py
+++ b/mod5-runbldpks/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod6-firestore/app.yaml b/mod6-firestore/app.yaml
index a173c6e..48dfc91 100644
--- a/mod6-firestore/app.yaml
+++ b/mod6-firestore/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod6-firestore/main.py b/mod6-firestore/main.py
index 5db626b..b9a83a7 100644
--- a/mod6-firestore/main.py
+++ b/mod6-firestore/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod7-gaetasks/app.yaml b/mod7-gaetasks/app.yaml
index 80fb603..0d4d8d6 100644
--- a/mod7-gaetasks/app.yaml
+++ b/mod7-gaetasks/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod7-gaetasks/appengine_config.py b/mod7-gaetasks/appengine_config.py
index 0ca8634..9760ebb 100644
--- a/mod7-gaetasks/appengine_config.py
+++ b/mod7-gaetasks/appengine_config.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod7-gaetasks/main.py b/mod7-gaetasks/main.py
index 289bd28..49e2b08 100644
--- a/mod7-gaetasks/main.py
+++ b/mod7-gaetasks/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod8-cloudtasks/app.yaml b/mod8-cloudtasks/app.yaml
index 03e7e70..0877f79 100644
--- a/mod8-cloudtasks/app.yaml
+++ b/mod8-cloudtasks/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod8-cloudtasks/appengine_config.py b/mod8-cloudtasks/appengine_config.py
index 4773cd7..2a41fb4 100644
--- a/mod8-cloudtasks/appengine_config.py
+++ b/mod8-cloudtasks/appengine_config.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod8-cloudtasks/main.py b/mod8-cloudtasks/main.py
index abd5876..10a1085 100644
--- a/mod8-cloudtasks/main.py
+++ b/mod8-cloudtasks/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod9-py3fstasks/app.yaml b/mod9-py3fstasks/app.yaml
index a74df21..a4ce819 100644
--- a/mod9-py3fstasks/app.yaml
+++ b/mod9-py3fstasks/app.yaml
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/mod9-py3fstasks/main.py b/mod9-py3fstasks/main.py
index 3727ca5..d6c524a 100644
--- a/mod9-py3fstasks/main.py
+++ b/mod9-py3fstasks/main.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
From 7550d7f1f76954506c90b2b8e33f7d9958eaa509 Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Sat, 17 Jul 2021 02:08:51 -0700
Subject: [PATCH 06/56] add Py3 legacy services app; update README
---
README.md | 9 +++++--
mod1b-flask/.gcloudignore | 24 +++++++++++++++++++
mod1b-flask/README.md | 5 ++++
mod1b-flask/app.yaml | 16 +++++++++++++
mod1b-flask/main.py | 41 ++++++++++++++++++++++++++++++++
mod1b-flask/requirements.txt | 2 ++
mod1b-flask/templates/index.html | 16 +++++++++++++
7 files changed, 111 insertions(+), 2 deletions(-)
create mode 100644 mod1b-flask/.gcloudignore
create mode 100644 mod1b-flask/README.md
create mode 100644 mod1b-flask/app.yaml
create mode 100644 mod1b-flask/main.py
create mode 100644 mod1b-flask/requirements.txt
create mode 100644 mod1b-flask/templates/index.html
diff --git a/README.md b/README.md
index 5fc7ac1..cccdc1c 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ The table below summarizes migration module resources currently available along
Module | Topic | Video | Codelab | START here | FINISH here
--- | --- | --- | --- | --- | ---
0|Baseline app| [link](http://twitter.com/googledevs/status/1407755281786867714?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_smsintro_201023&utm_content=-) | _N/A_ (no tutorial; just review the code) | _N/A_ | Module 0 [code](/mod0-baseline) (2.x)
-1|Migrate to Flask| _TBD_ | [link](http://g.co/codelabs/pae-migrate-flask) | Module 0 [code](/mod0-baseline) (2.x) | Module 1 [code](/mod1-flask) (2.x)
+1|Migrate to Flask| _TBD_ | [link](http://g.co/codelabs/pae-migrate-flask) | Module 0 [code](/mod0-baseline) (2.x) | Module 1 [code](/mod1-flask) (2.x) & [code](/mod1b-flask) (3.x)
2|Migrate to Cloud NDB| _TBD_ | [link](http://g.co/codelabs/pae-migrate-cloudndb) | Module 1 [code](/mod1-flask) (2.x) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x)
3|Migrate to Cloud Datastore| _TBD_ | [link](http://g.co/codelabs/pae-migrate-datastore) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x) | Module 3 [code](/mod3a-datastore) (2.x) & [code](/mod3b-datastore) (3.x)
4|Migrate to Cloud Run with Docker| _TBD_ | [link](http://g.co/codelabs/pae-migrate-rundocker) | Module 2 [code](/mod2a-cloudndb) (2.x) & Module 3 [code](/mod3b-datastore) (3.x) | Module 4 [code](/mod4a-rundocker) (2.x) & [code](/mod4b-rundocker) (3.x)
@@ -241,7 +241,7 @@ If your original app users does *not* have a user interface, i.e., mobile backen
- [Firebase mobile & web app platform](https://firebase.google.com) (and [Cloud Functions for Firebase](https://firebase.google.com/products/functions) [customized for Firebase])
-### Canonical code samples
+## Canonical code samples
- This repo, along with corresponding codelabs & videos are complementary to the official docs & code samples.
- The [official Python 2 to 3 migration documentation](https://cloud.google.com/appengine/docs/standard/python/migrate-to-python3)
@@ -250,6 +250,11 @@ If your original app users does *not* have a user interface, i.e., mobile backen
- *Example:* [GAE `taskqueue` to Cloud Tasks](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/taskqueue) (similar to Module 8)
+## Accessing legacy service in second generation
+
+Legacy App Engine services created for the first generation platform (Python 2, Java 8, PHP 5, and Go 1.11 & older) are available on a limited basis for second generation runtimes (Python 3, Java 11, PHP 7, and Go 1.12 & newer) as part of an early-access program. There is no video or codelab available yet, however, the Module 1 Flask migration [Python 2 sample](/mod1-flask) is available in [Python 3](/mod1b-flask) if you have access. (See its README to sign-up if interested.)
+
+
## References
- App Engine Migration
diff --git a/mod1b-flask/.gcloudignore b/mod1b-flask/.gcloudignore
new file mode 100644
index 0000000..bcf97a8
--- /dev/null
+++ b/mod1b-flask/.gcloudignore
@@ -0,0 +1,24 @@
+# This file specifies files that are *not* uploaded to Google Cloud Platform
+# using gcloud. It follows the same syntax as .gitignore, with the addition of
+# "#!include" directives (which insert the entries of the given .gitignore-style
+# file at that point).
+#
+# For more information, run:
+# $ gcloud topic gcloudignore
+#
+.gcloudignore
+
+# Ignore source code control maintenance files
+.git
+.gitignore
+.hgignore
+.hg/
+
+# Python files
+*.pyc
+*.pyo
+__pycache__/
+/setup.cfg
+
+# no need to upload README
+README.md
diff --git a/mod1b-flask/README.md b/mod1b-flask/README.md
new file mode 100644
index 0000000..70fe0aa
--- /dev/null
+++ b/mod1b-flask/README.md
@@ -0,0 +1,5 @@
+# Module 1 - Migrate from `webapp2` to Flask
+
+This repo folder is the corresponding Python 3 code to the [Module 1 codelab](http://g.co/codelabs/pae-migrate-flask). The tutorial STARTs with the Python 2 code in the [Module 0 repo folder](/mod0-baseline) and leads developers through migrating away from App Engine's `webapp2` web framework to Flask, culminating in the code in the [mod1-flask](/mod1-flask) folder. That is followed by a BONUS migration to Python 3, culminating in the code in *this* (`mod1b-flask`) folder. In the [next (Module 2) codelab](http://g.co/codelabs/pae-migrate-cloudndb), you will convert this app from the bundled `ndb` library to the Cloud NDB library for Datastore access.
+
+**NOTE**: Accessing a legacy service such as App Engine `ndb` from Python 3 (and next generation App Engine in general) is currently in private preview and requires access. Please complete [this form](https://docs.google.com/forms/d/e/1FAIpQLSd1hFLA2UFSYwIMxm9ZI3pwigORZBgjJRH0qrnhtE7nvhhRCQ/viewform) to register for this early-access program. Doing so opts you into the preview and give you access to the documentation.
diff --git a/mod1b-flask/app.yaml b/mod1b-flask/app.yaml
new file mode 100644
index 0000000..288ce87
--- /dev/null
+++ b/mod1b-flask/app.yaml
@@ -0,0 +1,16 @@
+# Copyright 2021 Google LLC
+#
+# 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.
+
+runtime: python38
+app_engine_apis: true
diff --git a/mod1b-flask/main.py b/mod1b-flask/main.py
new file mode 100644
index 0000000..bfc4c80
--- /dev/null
+++ b/mod1b-flask/main.py
@@ -0,0 +1,41 @@
+# Copyright 2021 Google LLC
+#
+# 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 flask import Flask, render_template, request
+from google.appengine.api import wrap_wsgi_app
+from google.appengine.ext import ndb
+
+app = Flask(__name__)
+app.wsgi_app = wrap_wsgi_app(app.wsgi_app)
+
+class Visit(ndb.Model):
+ 'Visit entity registers visitor IP address & timestamp'
+ visitor = ndb.StringProperty()
+ timestamp = ndb.DateTimeProperty(auto_now_add=True)
+
+def store_visit(remote_addr, user_agent):
+ 'create new Visit entity in Datastore'
+ Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
+
+def fetch_visits(limit):
+ 'get most recent visits'
+ return (v.to_dict() for v in Visit.query().order(
+ -Visit.timestamp).fetch(limit))
+
+@app.route('/')
+def root():
+ 'main application (GET) handler'
+ store_visit(request.remote_addr, request.user_agent)
+ visits = fetch_visits(10)
+ return render_template('index.html', visits=visits)
diff --git a/mod1b-flask/requirements.txt b/mod1b-flask/requirements.txt
new file mode 100644
index 0000000..c661cf7
--- /dev/null
+++ b/mod1b-flask/requirements.txt
@@ -0,0 +1,2 @@
+flask==1.1.2
+appengine-python-standard
diff --git a/mod1b-flask/templates/index.html b/mod1b-flask/templates/index.html
new file mode 100644
index 0000000..e140206
--- /dev/null
+++ b/mod1b-flask/templates/index.html
@@ -0,0 +1,16 @@
+
+
+
+VisitMe Example
+
+
+VisitMe example
+Last 10 visits
+
+{% for visit in visits %}
+ - {{ visit.timestamp.ctime() }} from {{ visit.visitor }}
+{% endfor %}
+
+
+
+
From b5c89997adb69e4d5a5389b93a3d2c69a0630cc4 Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Sat, 17 Jul 2021 17:07:44 -0700
Subject: [PATCH 07/56] README cleanup
---
README.md | 8 +++++---
mod1b-flask/README.md | 7 +++++--
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index cccdc1c..ebc796c 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,9 @@ Furthermore, deploying to GCP serverless platforms incur [minor build and storag
In App Engine's early days, users wanted Google to make the platform more flexible for developers and make their apps more portable. As a result, the team made significant changes to its 2nd-generation service which [launched in 2018](https://cloud.google.com/blog/products/gcp/introducing-app-engine-second-generation-runtimes-and-python-3-7). As a result, there are no longer any built-in services, allowing users to select from standalone GCP products or best-of-breed 3rd-party tools used by the broader community. Summary:
- **Legacy platform**: *Python 2* only, legacy built-in services
-- **Next generation**: *Python 3* only, external services, flexible platform
+- **Next generation**: *Python 3* only, use standalone services, flexible platform, some* built-in services available
+
+* see [Legacy services](#accessing-legacy-services-in-second-generation) section below
While the 2nd-gen platform is more flexible, users of the legacy platform have two challenges:
@@ -250,9 +252,9 @@ If your original app users does *not* have a user interface, i.e., mobile backen
- *Example:* [GAE `taskqueue` to Cloud Tasks](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/appengine/standard/migration/taskqueue) (similar to Module 8)
-## Accessing legacy service in second generation
+## Accessing legacy services in second generation
-Legacy App Engine services created for the first generation platform (Python 2, Java 8, PHP 5, and Go 1.11 & older) are available on a limited basis for second generation runtimes (Python 3, Java 11, PHP 7, and Go 1.12 & newer) as part of an early-access program. There is no video or codelab available yet, however, the Module 1 Flask migration [Python 2 sample](/mod1-flask) is available in [Python 3](/mod1b-flask) if you have access. (See its README to sign-up if interested.)
+Some legacy App Engine first generation platform (Python 2, Java 8, PHP 5, and Go 1.11 & older) services are available on a limited basis to second generation runtimes (Python 3, Java 11, PHP 7, and Go 1.12 & newer) as part of an early-access program. There is no video or codelab available yet, however the Module 1 Flask migration [Python 2 sample](/mod1-flask) is available in [Python 3](/mod1b-flask) if you have access. (See its README to sign-up if interested.)
## References
diff --git a/mod1b-flask/README.md b/mod1b-flask/README.md
index 70fe0aa..b264059 100644
--- a/mod1b-flask/README.md
+++ b/mod1b-flask/README.md
@@ -1,5 +1,8 @@
# Module 1 - Migrate from `webapp2` to Flask
-This repo folder is the corresponding Python 3 code to the [Module 1 codelab](http://g.co/codelabs/pae-migrate-flask). The tutorial STARTs with the Python 2 code in the [Module 0 repo folder](/mod0-baseline) and leads developers through migrating away from App Engine's `webapp2` web framework to Flask, culminating in the code in the [mod1-flask](/mod1-flask) folder. That is followed by a BONUS migration to Python 3, culminating in the code in *this* (`mod1b-flask`) folder. In the [next (Module 2) codelab](http://g.co/codelabs/pae-migrate-cloudndb), you will convert this app from the bundled `ndb` library to the Cloud NDB library for Datastore access.
+This repo folder is the corresponding Python 3 code to the [Module 1 codelab](http://g.co/codelabs/pae-migrate-flask). The tutorial STARTs with the Python 2 code in the [Module 0 repo folder](/mod0-baseline) and leads developers through migrating away from App Engine's `webapp2` web framework to Flask, culminating in the code in the [mod1-flask](/mod1-flask) folder. The codelab does **not** currently feature any bonus migration to Python 3, however, if you do so, and add in components from the legacy services private preview program (see sidebar below), it will culminate in the code in *this* (`mod1b-flask`) folder. In the [next (Module 2) codelab](http://g.co/codelabs/pae-migrate-cloudndb), users will migrate (the original Python 2 version of) this app from App Engine `ndb` to Cloud NDB for Datastore access.
+
+> **LEGACY SERVICES PRIVATE PREVIEW**: Accessing a legacy service such as App Engine `ndb` from Python 3 (and next generation App Engine in general) is currently in private preview and requires access. Please complete [this form](https://docs.google.com/forms/d/e/1FAIpQLSd1hFLA2UFSYwIMxm9ZI3pwigORZBgjJRH0qrnhtE7nvhhRCQ/viewform) to sign-up for this early-access program. Registering will get you in the queue to access the private preview, its documentation, and the announcements mailing list. For more info on what services are (or are not) available at this time, see this Subreddit](https://reddit.com/r/AppEngine/comments/o9wr72).
+
+
-**NOTE**: Accessing a legacy service such as App Engine `ndb` from Python 3 (and next generation App Engine in general) is currently in private preview and requires access. Please complete [this form](https://docs.google.com/forms/d/e/1FAIpQLSd1hFLA2UFSYwIMxm9ZI3pwigORZBgjJRH0qrnhtE7nvhhRCQ/viewform) to register for this early-access program. Doing so opts you into the preview and give you access to the documentation.
From c4e27b3aeff701b36a1a2bd0c331ed66ef12b26e Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Fri, 23 Jul 2021 18:37:38 -0700
Subject: [PATCH 08/56] add video links & README updates
---
README.md | 4 ++--
mod1b-flask/README.md | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index ebc796c..90a6682 100644
--- a/README.md
+++ b/README.md
@@ -72,8 +72,8 @@ The table below summarizes migration module resources currently available along
Module | Topic | Video | Codelab | START here | FINISH here
--- | --- | --- | --- | --- | ---
0|Baseline app| [link](http://twitter.com/googledevs/status/1407755281786867714?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_smsintro_201023&utm_content=-) | _N/A_ (no tutorial; just review the code) | _N/A_ | Module 0 [code](/mod0-baseline) (2.x)
-1|Migrate to Flask| _TBD_ | [link](http://g.co/codelabs/pae-migrate-flask) | Module 0 [code](/mod0-baseline) (2.x) | Module 1 [code](/mod1-flask) (2.x) & [code](/mod1b-flask) (3.x)
-2|Migrate to Cloud NDB| _TBD_ | [link](http://g.co/codelabs/pae-migrate-cloudndb) | Module 1 [code](/mod1-flask) (2.x) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x)
+1|Migrate to Flask| [link](https://twitter.com/googledevs/status/1413273119071031299?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrwa2flsk_201008&u%20tm_content=-) | [link](http://g.co/codelabs/pae-migrate-flask) | Module 0 [code](/mod0-baseline) (2.x) | Module 1 [code](/mod1-flask) (2.x) & [code](/mod1b-flask) (3.x)
+2|Migrate to Cloud NDB| [link](http://twitter.com/googledevs/status/1413273119071031299?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrwa2flsk_201008&utm_content=-) | [link](http://g.co/codelabs/pae-migrate-cloudndb) | Module 1 [code](/mod1-flask) (2.x) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x)
3|Migrate to Cloud Datastore| _TBD_ | [link](http://g.co/codelabs/pae-migrate-datastore) | Module 2 [code](/mod2a-cloudndb) (2.x) & [code](/mod2b-cloudndb) (3.x) | Module 3 [code](/mod3a-datastore) (2.x) & [code](/mod3b-datastore) (3.x)
4|Migrate to Cloud Run with Docker| _TBD_ | [link](http://g.co/codelabs/pae-migrate-rundocker) | Module 2 [code](/mod2a-cloudndb) (2.x) & Module 3 [code](/mod3b-datastore) (3.x) | Module 4 [code](/mod4a-rundocker) (2.x) & [code](/mod4b-rundocker) (3.x)
5|Migrate to Cloud Run with Buildpacks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-runbldpks) | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 5 [code](/mod5-runbldpks) (3.x)
diff --git a/mod1b-flask/README.md b/mod1b-flask/README.md
index b264059..f90ca49 100644
--- a/mod1b-flask/README.md
+++ b/mod1b-flask/README.md
@@ -2,7 +2,7 @@
This repo folder is the corresponding Python 3 code to the [Module 1 codelab](http://g.co/codelabs/pae-migrate-flask). The tutorial STARTs with the Python 2 code in the [Module 0 repo folder](/mod0-baseline) and leads developers through migrating away from App Engine's `webapp2` web framework to Flask, culminating in the code in the [mod1-flask](/mod1-flask) folder. The codelab does **not** currently feature any bonus migration to Python 3, however, if you do so, and add in components from the legacy services private preview program (see sidebar below), it will culminate in the code in *this* (`mod1b-flask`) folder. In the [next (Module 2) codelab](http://g.co/codelabs/pae-migrate-cloudndb), users will migrate (the original Python 2 version of) this app from App Engine `ndb` to Cloud NDB for Datastore access.
-> **LEGACY SERVICES PRIVATE PREVIEW**: Accessing a legacy service such as App Engine `ndb` from Python 3 (and next generation App Engine in general) is currently in private preview and requires access. Please complete [this form](https://docs.google.com/forms/d/e/1FAIpQLSd1hFLA2UFSYwIMxm9ZI3pwigORZBgjJRH0qrnhtE7nvhhRCQ/viewform) to sign-up for this early-access program. Registering will get you in the queue to access the private preview, its documentation, and the announcements mailing list. For more info on what services are (or are not) available at this time, see this Subreddit](https://reddit.com/r/AppEngine/comments/o9wr72).
+> **LEGACY SERVICES PRIVATE PREVIEW**: Accessing a legacy service such as App Engine `ndb` from Python 3 (and next generation App Engine in general) is currently in private preview and requires access. Please complete [this form](https://docs.google.com/forms/d/e/1FAIpQLSd1hFLA2UFSYwIMxm9ZI3pwigORZBgjJRH0qrnhtE7nvhhRCQ/viewform) to sign-up for the program. Registering will get you in the queue to access the private preview, its documentation, and the announcements mailing list. For more info on what services are (or are not) available (at this time), see [this thread in the r/AppEngine Subreddit](https://reddit.com/r/AppEngine/comments/o9wr72).
From c62451d306d7f8aef77cd61650ee8e47c491d9eb Mon Sep 17 00:00:00 2001
From: wesley chun
Date: Fri, 30 Jul 2021 23:47:56 -0700
Subject: [PATCH 09/56] add memcache sample; update TQ templates
---
README.md | 3 +-
mod0-baseline/.gcloudignore | 5 +--
mod12a-memcache/.gcloudignore | 24 +++++++++++++
mod12a-memcache/README.md | 3 ++
mod12a-memcache/app.yaml | 21 ++++++++++++
mod12a-memcache/appengine_config.py | 20 +++++++++++
mod12a-memcache/main.py | 51 ++++++++++++++++++++++++++++
mod12a-memcache/requirements.txt | 1 +
mod12a-memcache/templates/index.html | 16 +++++++++
mod7-gaetasks/templates/index.html | 2 +-
mod8-cloudtasks/templates/index.html | 2 +-
mod9-py3fstasks/templates/index.html | 2 +-
12 files changed, 144 insertions(+), 6 deletions(-)
create mode 100644 mod12a-memcache/.gcloudignore
create mode 100644 mod12a-memcache/README.md
create mode 100644 mod12a-memcache/app.yaml
create mode 100644 mod12a-memcache/appengine_config.py
create mode 100644 mod12a-memcache/main.py
create mode 100644 mod12a-memcache/requirements.txt
create mode 100644 mod12a-memcache/templates/index.html
diff --git a/README.md b/README.md
index 90a6682..233540f 100644
--- a/README.md
+++ b/README.md
@@ -78,11 +78,12 @@ Module | Topic | Video | Codelab | START here | FINISH here
4|Migrate to Cloud Run with Docker| _TBD_ | [link](http://g.co/codelabs/pae-migrate-rundocker) | Module 2 [code](/mod2a-cloudndb) (2.x) & Module 3 [code](/mod3b-datastore) (3.x) | Module 4 [code](/mod4a-rundocker) (2.x) & [code](/mod4b-rundocker) (3.x)
5|Migrate to Cloud Run with Buildpacks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-runbldpks) | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 5 [code](/mod5-runbldpks) (3.x)
6|Migrate to Cloud Firestore (app)| _TBD_ | [link](http://g.co/codelabs/pae-migrate-firestore) | Module 3 [code](/mod3b-datastore) (3.x) | Module 6 [code](/mod6-firestore) (3.x)
-7|Add App Engine push tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-gaetasks) | Module 1 [code]() (2.x) | Module 7 [code](/mod7-gaetasks) (2.x)
+7|Add App Engine push tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-gaetasks) | Module 1 [code](/mod1-flask) (2.x) | Module 7 [code](/mod7-gaetasks) (2.x)
8|Migrate to Cloud Tasks| _TBD_ | [link](http://g.co/codelabs/pae-migrate-cloudtasks) | Module 7 [code](/mod7-gaetasks) (2.x) | Module 8 [code](/mod8-cloudtasks) (2.x)
9|Migrate to Python 3, Cloud Firestore & Cloud Tasks v2| _TBD_ | _TBD_ | Module 8 [code](/mod8-cloudtasks) (2.x) | Module 9 [code](/mod9-py3fstasks) (3.x)
10|Migrate to Cloud Firestore (data)| _TBD_ | _N/A_ | _N/A_ | _TBD_
11|Migrate to Cloud Functions| _TBD_ | _TBD_ | Module 2 [code](/mod2b-cloudndb) (3.x) | Module 11 [code](/mod11-functions) (3.x)
+12|Add App Engine `memcache`| _TBD_ | _TBD_ | Module 1 [code](/mod1-flask) (2.x) | Module 12 [code](/mod12a-memcache) (2.x)
### Table of contents
diff --git a/mod0-baseline/.gcloudignore b/mod0-baseline/.gcloudignore
index ee1a5ea..bcf97a8 100644
--- a/mod0-baseline/.gcloudignore
+++ b/mod0-baseline/.gcloudignore
@@ -9,13 +9,14 @@
.gcloudignore
# Ignore source code control maintenance files
-.git/
+.git
.gitignore
.hgignore
.hg/
# Python files
-*.py[cod]
+*.pyc
+*.pyo
__pycache__/
/setup.cfg
diff --git a/mod12a-memcache/.gcloudignore b/mod12a-memcache/.gcloudignore
new file mode 100644
index 0000000..bcf97a8
--- /dev/null
+++ b/mod12a-memcache/.gcloudignore
@@ -0,0 +1,24 @@
+# This file specifies files that are *not* uploaded to Google Cloud Platform
+# using gcloud. It follows the same syntax as .gitignore, with the addition of
+# "#!include" directives (which insert the entries of the given .gitignore-style
+# file at that point).
+#
+# For more information, run:
+# $ gcloud topic gcloudignore
+#
+.gcloudignore
+
+# Ignore source code control maintenance files
+.git
+.gitignore
+.hgignore
+.hg/
+
+# Python files
+*.pyc
+*.pyo
+__pycache__/
+/setup.cfg
+
+# no need to upload README
+README.md
diff --git a/mod12a-memcache/README.md b/mod12a-memcache/README.md
new file mode 100644
index 0000000..f4447f9
--- /dev/null
+++ b/mod12a-memcache/README.md
@@ -0,0 +1,3 @@
+# Module 12 - Add usage of App Engine `memcache` to Flask `ndb` sample app
+
+This repo folder is the corresponding Python 2 code to the Module 12 codelab (TBD). The tutorial STARTs with the Python 2 code in the [Module 1 repo folder](/mod1-flask) and leads developers through adding usage of App Engine's `memcache`, culminating in the code in this folder.
diff --git a/mod12a-memcache/app.yaml b/mod12a-memcache/app.yaml
new file mode 100644
index 0000000..0d4d8d6
--- /dev/null
+++ b/mod12a-memcache/app.yaml
@@ -0,0 +1,21 @@
+# Copyright 2021 Google LLC
+#
+# 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.
+
+runtime: python27
+threadsafe: yes
+api_version: 1
+
+handlers:
+- url: /.*
+ script: main.app
diff --git a/mod12a-memcache/appengine_config.py b/mod12a-memcache/appengine_config.py
new file mode 100644
index 0000000..9760ebb
--- /dev/null
+++ b/mod12a-memcache/appengine_config.py
@@ -0,0 +1,20 @@
+# Copyright 2021 Google LLC
+#
+# 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 vendor
+
+# Set PATH to your libraries folder.
+PATH = 'lib'
+# Add libraries installed in the PATH folder.
+vendor.add(PATH)
diff --git a/mod12a-memcache/main.py b/mod12a-memcache/main.py
new file mode 100644
index 0000000..621032a
--- /dev/null
+++ b/mod12a-memcache/main.py
@@ -0,0 +1,51 @@
+# Copyright 2020 Google LLC
+#
+# 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 flask import Flask, render_template, request
+from google.appengine.api import memcache
+from google.appengine.ext import ndb
+
+app = Flask(__name__)
+HOUR = 3600
+
+class Visit(ndb.Model):
+ 'Visit entity registers visitor IP address & timestamp'
+ visitor = ndb.StringProperty()
+ timestamp = ndb.DateTimeProperty(auto_now_add=True)
+
+def store_visit(remote_addr, user_agent):
+ 'create new Visit entity in Datastore'
+ Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
+
+def fetch_visits(limit):
+ 'get most recent visits'
+ return [v.to_dict() for v in Visit.query().order(
+ -Visit.timestamp).fetch(limit)]
+
+@app.route('/')
+def root():
+ 'main application (GET) handler'
+ # check for cached visits
+ ip_addr, usr_agt = request.remote_addr, request.user_agent
+ visitor = '{}: {}'.format(ip_addr, usr_agt)
+ visits = memcache.get('visits')
+
+ # run DB query if cache empty or new visitor
+ if not visits or visits[0]['visitor'] != visitor:
+ store_visit(ip_addr, usr_agt)
+ visits = fetch_visits(10)
+ memcache.set('visits', visits, HOUR)
+
+ # send context to template renderer
+ return render_template('index.html', visits=visits)
diff --git a/mod12a-memcache/requirements.txt b/mod12a-memcache/requirements.txt
new file mode 100644
index 0000000..5c508e5
--- /dev/null
+++ b/mod12a-memcache/requirements.txt
@@ -0,0 +1 @@
+flask==1.1.2
diff --git a/mod12a-memcache/templates/index.html b/mod12a-memcache/templates/index.html
new file mode 100644
index 0000000..e140206
--- /dev/null
+++ b/mod12a-memcache/templates/index.html
@@ -0,0 +1,16 @@
+
+
+
+VisitMe Example
+
+
+VisitMe example
+Last 10 visits
+
+{% for visit in visits %}
+ - {{ visit.timestamp.ctime() }} from {{ visit.visitor }}
+{% endfor %}
+
+
+
+
diff --git a/mod7-gaetasks/templates/index.html b/mod7-gaetasks/templates/index.html
index 9f33130..952961e 100644
--- a/mod7-gaetasks/templates/index.html
+++ b/mod7-gaetasks/templates/index.html
@@ -12,7 +12,7 @@ Last 10 visits
{% endfor %}
-{% if oldest %}
+{% if oldest is defined %}
Deleting visits older than: {{ oldest }}
{% endif %}
diff --git a/mod8-cloudtasks/templates/index.html b/mod8-cloudtasks/templates/index.html
index 9f33130..952961e 100644
--- a/mod8-cloudtasks/templates/index.html
+++ b/mod8-cloudtasks/templates/index.html
@@ -12,7 +12,7 @@ Last 10 visits
{% endfor %}
-{% if oldest %}
+{% if oldest is defined %}
Deleting visits older than: {{ oldest }}
{% endif %}
diff --git a/mod9-py3fstasks/templates/index.html b/mod9-py3fstasks/templates/index.html
index 9f33130..952961e 100644
--- a/mod9-py3fstasks/templates/index.html
+++ b/mod9-py3fstasks/templates/index.html
@@ -12,7 +12,7 @@ Last 10 visits
{% endfor %}
-{% if oldest %}
+{% if oldest is defined %}
Deleting visits older than: {{ oldest }}
{% endif %}