Skip to content
This repository was archived by the owner on Oct 1, 2025. It is now read-only.

Commit 37a1ca1

Browse files
committed
add Module 22 code
1 parent 37312b1 commit 37a1ca1

28 files changed

+993
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ Module | Topic | Video | Codelab | START here | FINISH here
114114
14|Migrate service between projects| _TBD_ | _TBD_ | _TBD_ | _TBD_
115115
15|Add App Engine `blobstore`| [link](https://twitter.com/googledevs/status/1552384740052934657?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrblobstore_sms_202029&utm_content=-) | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-15-blobstore?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrblobstore_sms_202029&utm_content=-) | Module 0 [code](/mod0-baseline) (2.x) | Module 15 [code](/mod15-blobstore) (2.x)
116116
16|Migrate to Cloud Storage| [link](https://twitter.com/googledevs/status/1559285905961123845?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrcloudstorage_sms_202029&utm_content=-) | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-16-cloudstorage?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrcloudstorage_sms_202029&utm_content=-) | Module 15 [code](/mod15-blobstore) (2.x) | Module 16 [code](/mod16-cloudstorage) (2.x & 3.x)
117-
17|Migrate to Python 3 bundled services| [link](https://twitter.com/googledevs/status/1587855923254624259?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrwormhole_sms_202002&utm_content=-) | [link](http://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-17-bundled?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrwormhole_sms_202002&utm_content=-) | Module 1 [code](/mod1-flask) (2.x) | Module 1 [code](/mod1b-flask) (3.x)
117+
17|Migrate to Python 3 bundled services (Part 1)| [link](https://twitter.com/googledevs/status/1587855923254624259?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrwormhole_sms_202002&utm_content=-) | [link](http://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-17-bundled?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrwormhole_sms_202002&utm_content=-) | Module 1 [code](/mod1-flask) (2.x) | Module 1 [code](/mod1b-flask) (3.x)
118118
18|Add App Engine `taskqueue` pull tasks| [link](https://twitter.com/googledevs/status/1598013491381665808?utm_source=twitter&utm_medium=unpaidsoc&utm_campaign=CDR_wes_aap-serverless_mgrgaepull_sms_202013&utm_content=-) | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-18-gaepull?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrgaepull_sms_202013&utm_content=-) | Module 1 [code](/mod1-flask) (2.x) | Module 18 [code](/mod18-gaepull) (2.x)
119119
19|Migrate to Cloud Pub/Sub| _TBD_ | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-19-pubsub?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrpubsub_sms_202016&utm_content=-) | Module 18 [code](/mod18-gaepull) (2.x) | Module 19 [code](/mod19-pubsub) (2.x & 3.x)
120120
20|Add App Engine `users` | _TBD_ | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-20-gaeusers?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgrgaeusers_sms_202119&utm_content=-)| Module 1 [code](/mod1-flask) (2.x) | Module 20 [code](/mod20-gaeusers) (2.x)
121121
21|Migrate to Cloud Identity Platform | _TBD_ | [link](https://codelabs.developers.google.com/codelabs/cloud-gae-python-migrate-21-idenplat?utm_source=codelabs&utm_medium=et&utm_campaign=CDR_wes_aap-serverless_mgridenplat_sms_202119&utm_content=-)| Module 20 [code](/mod20-gaeusers) (2.x) | Module 21 [code](/mod21a-idenplat) (2.x) & [code](/mod21b-idenplat) (3.x)
122+
22|Migrate to Python 3 bundled services (Part 2)| _TBD_ | _N/A_ | Module 22 [code](/mod22-bundled) (2.x & 3.x) | _(same)_
122123

123124

124125
### Table of contents

mod22-bundled/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Module 22 - Extending support for App Engine bundled services in Python 3
2+
3+
There are six folders in this directory that contain full applications using the Blobstore, Deferred, and Mail bundled services. A Python 2 version using the `webapp2` framework is provided along with a modernized Python 3 equivalent using the Flask web framework and the Python 3 App Engine SDK.
4+
5+
Bundled service | Python 2 `webapp` | Python 3 Flask
6+
--- | --- | ---
7+
Blobstore | [code](blobstore2) | [code](blobstore3)
8+
Deferred | [code](deferred2) | [code](deferred3)
9+
Mail | [code](mail2) | [code](mail3)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# This file specifies files that are *not* uploaded to Google Cloud Platform
2+
# using gcloud. It follows the same syntax as .gitignore, with the addition of
3+
# "#!include" directives (which insert the entries of the given .gitignore-style
4+
# file at that point).
5+
#
6+
# For more information, run:
7+
# $ gcloud topic gcloudignore
8+
#
9+
.gcloudignore
10+
11+
# Source code control files
12+
.git/
13+
.gitignore
14+
.hgignore
15+
.hg/
16+
17+
# README/text files
18+
LICENSE
19+
*.md
20+
21+
# Tests/results (not in .gitignore)
22+
noxfile.py
23+
pylintrc
24+
pylintrc.test
25+
26+
# most of .gitignore (except `lib`)
27+
#
28+
# Python
29+
*.py[cod]
30+
__pycache__/
31+
/setup.cfg
32+
33+
# C extensions
34+
*.so
35+
36+
# Packages
37+
*.egg
38+
*.egg-info
39+
dist
40+
build
41+
eggs
42+
.eggs
43+
parts
44+
bin
45+
var
46+
sdist
47+
develop-eggs
48+
.installed.cfg
49+
lib64
50+
*.tgz
51+
52+
# Installer logs
53+
pip-log.txt
54+
55+
# Tests/results
56+
.nox/
57+
.pytest_cache/
58+
.cache
59+
.pytype
60+
.coverage
61+
coverage.xml
62+
*sponge_log.xml
63+
system_tests/local_test_setup
64+
65+
# Mac
66+
.DS_Store
67+
68+
# IDEs/editors
69+
*.sw[op]
70+
*~
71+
.vscode
72+
.idea
73+
74+
# Built documentation
75+
docs/_build
76+
docs.metadata
77+
78+
# Virtual environment
79+
env/

mod22-bundled/blobstore2/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Module 22 - Using Blobstore bundled service (Python 2)
2+
3+
This repo folder represents the Module 22 Python 2 sample app for the Blobstore bundled service.

mod22-bundled/blobstore2/app.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
runtime: python27
16+
threadsafe: yes
17+
api_version: 1
18+
19+
handlers:
20+
- url: /.*
21+
script: main.app

mod22-bundled/blobstore2/main.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import webapp2
16+
from google.appengine.ext import blobstore, ndb
17+
from google.appengine.ext.webapp import blobstore_handlers
18+
19+
UPLOAD_FORM = '''\
20+
<h2>Upload photo:</h2>
21+
<form action="%s" method="POST" enctype="multipart/form-data">
22+
<input type="file" name="file"><p></p><input type="submit">
23+
</form>'''
24+
25+
26+
class PhotoUpload(ndb.Model):
27+
'PhotoUpload entity for registering a photo'
28+
blob_key = ndb.BlobKeyProperty()
29+
30+
31+
class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
32+
'PhotoUploadHandler handles a photo upload (POST)'
33+
def post(self):
34+
uploads = self.get_uploads()
35+
blob_id = uploads[0].key() if uploads else None
36+
PhotoUpload(blob_key=blob_id).put()
37+
self.redirect('/view_photo/%s' % blob_id)
38+
39+
40+
class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):
41+
'ViewPhotoHandler handles a photo view/download (GET)'
42+
def get(self, blob_key):
43+
self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)
44+
45+
46+
class MainHandler(webapp2.RequestHandler):
47+
'main application (GET) handler'
48+
def get(self):
49+
self.response.write(
50+
UPLOAD_FORM % blobstore.create_upload_url('/upload_photo'))
51+
52+
53+
app = webapp2.WSGIApplication([
54+
('/', MainHandler),
55+
('/upload_photo', PhotoUploadHandler),
56+
('/view_photo/([^/]+)?', ViewPhotoHandler),
57+
], debug=True)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# This file specifies files that are *not* uploaded to Google Cloud Platform
2+
# using gcloud. It follows the same syntax as .gitignore, with the addition of
3+
# "#!include" directives (which insert the entries of the given .gitignore-style
4+
# file at that point).
5+
#
6+
# For more information, run:
7+
# $ gcloud topic gcloudignore
8+
#
9+
.gcloudignore
10+
11+
# Source code control files
12+
.git/
13+
.gitignore
14+
.hgignore
15+
.hg/
16+
17+
# README/text files
18+
LICENSE
19+
*.md
20+
21+
# Tests/results (not in .gitignore)
22+
noxfile.py
23+
pylintrc
24+
pylintrc.test
25+
26+
# most of .gitignore (except `lib`)
27+
#
28+
# Python
29+
*.py[cod]
30+
__pycache__/
31+
/setup.cfg
32+
33+
# C extensions
34+
*.so
35+
36+
# Packages
37+
*.egg
38+
*.egg-info
39+
dist
40+
build
41+
eggs
42+
.eggs
43+
parts
44+
bin
45+
var
46+
sdist
47+
develop-eggs
48+
.installed.cfg
49+
lib64
50+
*.tgz
51+
52+
# Installer logs
53+
pip-log.txt
54+
55+
# Tests/results
56+
.nox/
57+
.pytest_cache/
58+
.cache
59+
.pytype
60+
.coverage
61+
coverage.xml
62+
*sponge_log.xml
63+
system_tests/local_test_setup
64+
65+
# Mac
66+
.DS_Store
67+
68+
# IDEs/editors
69+
*.sw[op]
70+
*~
71+
.vscode
72+
.idea
73+
74+
# Built documentation
75+
docs/_build
76+
docs.metadata
77+
78+
# Virtual environment
79+
env/

mod22-bundled/blobstore3/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Module 22 - Using Blobstore bundled service (Python 3)
2+
3+
This repo folder represents the Module 22 Python 3 sample app for the Blobstore bundled service.

mod22-bundled/blobstore3/app.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
runtime: python310
16+
app_engine_apis: true
17+
18+
env_variables:
19+
NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

mod22-bundled/blobstore3/main.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from flask import Flask, abort, redirect, request
16+
from google.appengine.api import wrap_wsgi_app
17+
from google.appengine.ext import blobstore, ndb
18+
19+
UPLOAD_FORM = '''\
20+
<h2>Upload photo:</h2>
21+
<form action="%s" method="POST" enctype="multipart/form-data">
22+
<input type="file" name="file"><p></p><input type="submit">
23+
</form>'''
24+
25+
app = Flask(__name__)
26+
app.wsgi_app = wrap_wsgi_app(app.wsgi_app)
27+
28+
29+
class PhotoUpload(ndb.Model):
30+
'PhotoUpload entity for registering a photo'
31+
blob_key = ndb.BlobKeyProperty()
32+
33+
34+
class PhotoUploadHandler(blobstore.BlobstoreUploadHandler):
35+
'PhotoUploadHandler handles a photo upload (POST)'
36+
def post(self):
37+
uploads = self.get_uploads(request.environ)
38+
blob_id = uploads[0].key() if uploads else None
39+
PhotoUpload(blob_key=blob_id).put()
40+
return redirect('/view_photo/%s' % blob_id)
41+
42+
@app.route('/upload_photo', methods=['POST'])
43+
def upload_photo():
44+
'call upload handler for upload (POST) request'
45+
return PhotoUploadHandler().post()
46+
47+
48+
class ViewPhotoHandler(blobstore.BlobstoreDownloadHandler):
49+
'ViewPhotoHandler handles a photo view/download (GET)'
50+
def get(self, blob_key):
51+
if blobstore.get(blob_key):
52+
headers = self.send_blob(request.environ, blob_key)
53+
headers['Content-Type'] = None
54+
return '', headers
55+
abort(404)
56+
57+
@app.route('/view_photo/<photo_key>')
58+
def view_photo(photo_key):
59+
'call download handler for view (GET) request'
60+
return ViewPhotoHandler().get(photo_key)
61+
62+
63+
@app.route('/')
64+
def upload_form():
65+
'display photo upload HTML form'
66+
return UPLOAD_FORM % blobstore.create_upload_url('/upload_photo')

0 commit comments

Comments
 (0)