Skip to content

Commit 02a0c8e

Browse files
chore(gae): restore 'firenotes' samples (#13344)
* chore: restore 'firenotes' samples * chore(gae): fix headers * chore(gae): delete README.md * chore(gae): add a note to app.yaml that this example is designed for Python 2.7 which is not supported * chore(gae): add comments that these samples are for illustration purposes only * chore(gae): update note for Python 2.7 and GAE first-generation End of Support.
1 parent bb70e5f commit 02a0c8e

File tree

12 files changed

+617
-0
lines changed

12 files changed

+617
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2021 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+
# This code is designed for Python 2.7 and
16+
# the App Engine first-generation Runtime which has reached End of Support.
17+
18+
runtime: python27
19+
api_version: 1
20+
threadsafe: true
21+
service: backend
22+
23+
handlers:
24+
- url: /.*
25+
script: main.app
26+
27+
env_variables:
28+
GAE_USE_SOCKETS_HTTPLIB : 'true'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2016 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 google.appengine.ext import vendor
16+
17+
# Add any libraries installed in the "lib" folder.
18+
vendor.add("lib")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2021 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+
indexes:
16+
17+
# AUTOGENERATED
18+
19+
# This index.yaml is automatically updated whenever the dev_appserver
20+
# detects that a new type of query is run. If you want to manage the
21+
# index.yaml file manually, remove the above marker line (the line
22+
# saying "# AUTOGENERATED"). If you want to manage some indexes
23+
# manually, move them above the marker line. The index.yaml file is
24+
# automatically uploaded to the admin console when you next deploy
25+
# your application using appcfg.py.
26+
27+
- kind: Note
28+
ancestor: yes
29+
properties:
30+
- name: created
31+
32+
- kind: Note
33+
ancestor: yes
34+
properties:
35+
- name: created
36+
direction: desc
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Copyright 2016 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 logging
16+
import os
17+
18+
from flask import Flask, jsonify, request
19+
import flask_cors
20+
from google.appengine.ext import ndb
21+
import google.auth.transport.requests
22+
import google.oauth2.id_token
23+
import requests_toolbelt.adapters.appengine
24+
25+
# Use the App Engine Requests adapter. This makes sure that Requests uses
26+
# URLFetch.
27+
requests_toolbelt.adapters.appengine.monkeypatch()
28+
HTTP_REQUEST = google.auth.transport.requests.Request()
29+
30+
app = Flask(__name__)
31+
flask_cors.CORS(app)
32+
33+
34+
class Note(ndb.Model):
35+
"""NDB model class for a user's note.
36+
37+
Key is user id from decrypted token.
38+
"""
39+
40+
friendly_id = ndb.StringProperty()
41+
message = ndb.TextProperty()
42+
created = ndb.DateTimeProperty(auto_now_add=True)
43+
44+
45+
# [START gae_python_query_database]
46+
# This code is for illustration purposes only.
47+
48+
def query_database(user_id):
49+
"""Fetches all notes associated with user_id.
50+
51+
Notes are ordered them by date created, with most recent note added
52+
first.
53+
"""
54+
ancestor_key = ndb.Key(Note, user_id)
55+
query = Note.query(ancestor=ancestor_key).order(-Note.created)
56+
notes = query.fetch()
57+
58+
note_messages = []
59+
60+
for note in notes:
61+
note_messages.append(
62+
{
63+
"friendly_id": note.friendly_id,
64+
"message": note.message,
65+
"created": note.created,
66+
}
67+
)
68+
69+
return note_messages
70+
71+
72+
# [END gae_python_query_database]
73+
74+
75+
@app.route("/notes", methods=["GET"])
76+
def list_notes():
77+
"""Returns a list of notes added by the current Firebase user."""
78+
79+
# Verify Firebase auth.
80+
# [START gae_python_verify_token]
81+
# This code is for illustration purposes only.
82+
83+
id_token = request.headers["Authorization"].split(" ").pop()
84+
claims = google.oauth2.id_token.verify_firebase_token(
85+
id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
86+
)
87+
if not claims:
88+
return "Unauthorized", 401
89+
# [END gae_python_verify_token]
90+
91+
notes = query_database(claims["sub"])
92+
93+
return jsonify(notes)
94+
95+
96+
@app.route("/notes", methods=["POST", "PUT"])
97+
def add_note():
98+
"""
99+
Adds a note to the user's notebook. The request should be in this format:
100+
101+
{
102+
"message": "note message."
103+
}
104+
"""
105+
106+
# Verify Firebase auth.
107+
id_token = request.headers["Authorization"].split(" ").pop()
108+
claims = google.oauth2.id_token.verify_firebase_token(
109+
id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
110+
)
111+
if not claims:
112+
return "Unauthorized", 401
113+
114+
# [START gae_python_create_entity]
115+
# This code is for illustration purposes only.
116+
117+
data = request.get_json()
118+
119+
# Populates note properties according to the model,
120+
# with the user ID as the key name.
121+
note = Note(parent=ndb.Key(Note, claims["sub"]), message=data["message"])
122+
123+
# Some providers do not provide one of these so either can be used.
124+
note.friendly_id = claims.get("name", claims.get("email", "Unknown"))
125+
# [END gae_python_create_entity]
126+
127+
# Stores note in database.
128+
note.put()
129+
130+
return "OK", 200
131+
132+
133+
@app.errorhandler(500)
134+
def server_error(e):
135+
# Log the error and stacktrace.
136+
logging.exception("An error occurred during a request.")
137+
return "An internal error occurred.", 500
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright 2016 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 json
16+
17+
from google.appengine.ext import ndb
18+
import jwt
19+
import mock
20+
import pytest
21+
22+
23+
@pytest.fixture
24+
def app():
25+
# Remove any existing pyjwt handlers, as firebase_helper will register
26+
# its own.
27+
try:
28+
jwt.unregister_algorithm("RS256")
29+
except KeyError:
30+
pass
31+
32+
import main
33+
34+
main.app.testing = True
35+
return main.app.test_client()
36+
37+
38+
@pytest.fixture
39+
def mock_token():
40+
patch = mock.patch("google.oauth2.id_token.verify_firebase_token")
41+
with patch as mock_verify:
42+
yield mock_verify
43+
44+
45+
@pytest.fixture
46+
def test_data():
47+
from main import Note
48+
49+
ancestor_key = ndb.Key(Note, "123")
50+
notes = [
51+
Note(parent=ancestor_key, message="1"),
52+
Note(parent=ancestor_key, message="2"),
53+
]
54+
ndb.put_multi(notes)
55+
yield
56+
57+
58+
def test_list_notes_with_mock_token(testbed, app, mock_token, test_data):
59+
mock_token.return_value = {"sub": "123"}
60+
61+
r = app.get("/notes", headers={"Authorization": "Bearer 123"})
62+
assert r.status_code == 200
63+
64+
data = json.loads(r.data)
65+
assert len(data) == 2
66+
assert data[0]["message"] == "2"
67+
68+
69+
def test_list_notes_with_bad_mock_token(testbed, app, mock_token):
70+
mock_token.return_value = None
71+
72+
r = app.get("/notes", headers={"Authorization": "Bearer 123"})
73+
assert r.status_code == 401
74+
75+
76+
def test_add_note_with_mock_token(testbed, app, mock_token):
77+
mock_token.return_value = {"sub": "123"}
78+
79+
r = app.post(
80+
"/notes",
81+
data=json.dumps({"message": "Hello, world!"}),
82+
content_type="application/json",
83+
headers={"Authorization": "Bearer 123"},
84+
)
85+
86+
assert r.status_code == 200
87+
88+
from main import Note
89+
90+
results = Note.query().fetch()
91+
assert len(results) == 1
92+
assert results[0].message == "Hello, world!"
93+
94+
95+
def test_add_note_with_bad_mock_token(testbed, app, mock_token):
96+
mock_token.return_value = None
97+
98+
r = app.post("/notes", headers={"Authorization": "Bearer 123"})
99+
assert r.status_code == 401
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# pin pytest to 4.6.11 for Python2.
2+
pytest==4.6.11; python_version < '3.0'
3+
pytest==8.3.2; python_version >= '3.0'
4+
mock==3.0.5; python_version < '3.0'
5+
mock==5.1.0; python_version >= '3.0'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Flask==1.1.4; python_version < '3.0'
2+
Flask==3.0.0; python_version > '3.0'
3+
pyjwt==1.7.1; python_version < '3.0'
4+
flask-cors==3.0.10
5+
google-auth==2.17.3; python_version < '3.0'
6+
google-auth==2.17.3; python_version > '3.0'
7+
requests==2.27.1
8+
requests-toolbelt==0.10.1
9+
Werkzeug==1.0.1; python_version < '3.0'
10+
Werkzeug==3.0.3; python_version > '3.0'
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2021 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+
# This code is for illustration purposes only.
16+
17+
# This code is designed for Python 2.7 and
18+
# the App Engine first-generation Runtime which has reached End of Support.
19+
20+
runtime: python27
21+
api_version: 1
22+
service: default
23+
threadsafe: true
24+
25+
handlers:
26+
27+
# root
28+
- url: /
29+
static_files: index.html
30+
upload: index.html
31+
32+
- url: /(.+)
33+
static_files: \1
34+
upload: (.+)

0 commit comments

Comments
 (0)