Skip to content

Commit f2f9e9c

Browse files
Add a python 3.11 storage app for App Engine Flex. (GoogleCloudPlatform#9251)
* Add a python 3.11 storage app for App Engine Flex. * This is a python 3.11 sample app. Remove references to other versions. * Update header. * Update the tag and copyright header. * Address PR comments. Remove references and usage of older python versions. * Fix an import issue. * Fix lint. --------- Co-authored-by: Maciej Strzelczyk <strzelczyk@google.com>
1 parent ba91b3c commit f2f9e9c

File tree

7 files changed

+264
-0
lines changed

7 files changed

+264
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Python Google Cloud Storage sample for Google App Engine Flexible Environment
2+
3+
[![Open in Cloud Shell][shell_img]][shell_link]
4+
5+
[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png
6+
[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/flexible_ubuntu/storage/README.md
7+
8+
This sample demonstrates how to use [Google Cloud Storage](https://cloud.google.com/storage/) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine).
9+
10+
## Setup
11+
12+
Before you can run or deploy the sample, you will need to do the following:
13+
14+
1. Enable the Cloud Storage API in the [Google Developers Console](https://console.developers.google.com/project/_/apiui/apiview/storage/overview).
15+
16+
2. Create a Cloud Storage Bucket. You can do this with the [Google Cloud SDK](https://cloud.google.com/sdk) with the following command:
17+
18+
$ gsutil mb gs://[your-bucket-name]
19+
20+
3. Set the default ACL on your bucket to public read in order to serve files directly from Cloud Storage. You can do this with the [Google Cloud SDK](https://cloud.google.com/sdk) with the following command:
21+
22+
$ gsutil defacl set public-read gs://[your-bucket-name]
23+
24+
4. Update the environment variables in ``app.yaml``.
25+
26+
## Running locally
27+
28+
Refer to the [top-level README](../README.md) for instructions on running and deploying.
29+
30+
When running locally, you can use the [Google Cloud SDK](https://cloud.google.com/sdk) to provide authentication to use Google Cloud APIs:
31+
32+
$ gcloud init
33+
34+
Then set environment variables before starting your application:
35+
36+
$ export CLOUD_STORAGE_BUCKET=[your-bucket-name]
37+
$ python main.py
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2023 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: python
16+
env: flex
17+
entrypoint: gunicorn -b :$PORT main:app
18+
19+
runtime_config:
20+
# We also support 3.8, 3.9 and 3.10.
21+
# See https://cloud.google.com/appengine/docs/flexible/python/runtime#interpreter for more info.
22+
runtime_version: "3.11"
23+
operating_system: "ubuntu22"
24+
25+
#[START gae_flex_storage_yaml]
26+
env_variables:
27+
CLOUD_STORAGE_BUCKET: [your-bucket-name]
28+
#[END gae_flex_storage_yaml]
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright 2023 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+
# [START gae_flex_ubuntu_storage_app]
16+
import logging
17+
import os
18+
from typing import Union
19+
20+
from flask import Flask, request
21+
from google.cloud import storage
22+
23+
app = Flask(__name__)
24+
25+
# Configure this environment variable via app.yaml
26+
CLOUD_STORAGE_BUCKET = os.environ["CLOUD_STORAGE_BUCKET"]
27+
28+
29+
@app.route("/")
30+
def index() -> str:
31+
return """
32+
<form method="POST" action="/upload" enctype="multipart/form-data">
33+
<input type="file" name="file">
34+
<input type="submit">
35+
</form>
36+
"""
37+
38+
39+
@app.route("/upload", methods=["POST"])
40+
def upload() -> str:
41+
"""Process the uploaded file and upload it to Google Cloud Storage."""
42+
uploaded_file = request.files.get("file")
43+
44+
if not uploaded_file:
45+
return "No file uploaded.", 400
46+
47+
# Create a Cloud Storage client.
48+
gcs = storage.Client()
49+
50+
# Get the bucket that the file will be uploaded to.
51+
bucket = gcs.get_bucket(CLOUD_STORAGE_BUCKET)
52+
53+
# Create a new blob and upload the file's content.
54+
blob = bucket.blob(uploaded_file.filename)
55+
56+
blob.upload_from_string(
57+
uploaded_file.read(), content_type=uploaded_file.content_type
58+
)
59+
60+
# Make the blob public. This is not necessary if the
61+
# entire bucket is public.
62+
# See https://cloud.google.com/storage/docs/access-control/making-data-public.
63+
blob.make_public()
64+
65+
# The public URL can be used to directly access the uploaded file via HTTP.
66+
return blob.public_url
67+
68+
69+
@app.errorhandler(500)
70+
def server_error(e: Union[Exception, int]) -> str:
71+
logging.exception("An error occurred during a request.")
72+
return (
73+
"""
74+
An internal error occurred: <pre>{}</pre>
75+
See logs for full stacktrace.
76+
""".format(
77+
e
78+
),
79+
500,
80+
)
81+
82+
83+
if __name__ == "__main__":
84+
# This is used when running locally. Gunicorn is used to run the
85+
# application on Google App Engine. See entrypoint in app.yaml.
86+
app.run(host="127.0.0.1", port=8080, debug=True)
87+
# [END gae_flex_ubuntu_storage_app]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright 2023 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 io
16+
import os
17+
import uuid
18+
19+
import flask
20+
import flask.testing
21+
from google.cloud import storage
22+
import pytest
23+
24+
import requests
25+
26+
import main
27+
28+
29+
@pytest.fixture
30+
def client() -> flask.testing.FlaskClient:
31+
main.app.testing = True
32+
return main.app.test_client()
33+
34+
35+
def test_index(client: flask.testing.FlaskClient) -> None:
36+
r = client.get("/")
37+
assert r.status_code == 200
38+
39+
40+
@pytest.fixture(scope="module")
41+
def blob_name() -> str:
42+
name = f"gae-flex-storage-{uuid.uuid4()}"
43+
yield name
44+
45+
bucket = storage.Client().bucket(os.environ["CLOUD_STORAGE_BUCKET"])
46+
blob = bucket.blob(name)
47+
blob.delete()
48+
49+
50+
def test_upload(client: flask.testing.FlaskClient, blob_name: str) -> None:
51+
# Upload a simple file
52+
file_content = b"This is some test content."
53+
54+
r = client.post("/upload", data={"file": (io.BytesIO(file_content), blob_name)})
55+
56+
assert r.status_code == 200
57+
58+
# The app should return the public cloud storage URL for the uploaded
59+
# file. Download and verify it.
60+
cloud_storage_url = r.data.decode("utf-8")
61+
r = requests.get(cloud_storage_url)
62+
assert r.text.encode("utf-8") == file_content
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2023 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+
# Default TEST_CONFIG_OVERRIDE for python repos.
16+
17+
# You can copy this file into your directory, then it will be imported from
18+
# the noxfile.py.
19+
20+
# The source of truth:
21+
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py
22+
23+
TEST_CONFIG_OVERRIDE = {
24+
# You can opt out from the test for specific Python versions.
25+
"ignored_versions": ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10"],
26+
# Old samples are opted out of enforcing Python type hints
27+
# All new samples should feature them
28+
"enforce_type_hints": True,
29+
# An envvar key for determining the project id to use. Change it
30+
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
31+
# build specific Cloud project. You can also use your own string
32+
# to use your own Cloud project.
33+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
34+
# 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',
35+
# If you need to use a specific version of pip,
36+
# change pip_version_override to the string representation
37+
# of the version number, for example, "20.2.4"
38+
"pip_version_override": None,
39+
# A dictionary you want to inject into your test. Don't put any
40+
# secrets here. These values will override predefined values.
41+
"envs": {
42+
"CLOUD_STORAGE_BUCKET": "python-docs-samples-tests-public"
43+
},
44+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest==7.0.1
2+
google-cloud-storage==2.1.0;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Flask==2.1.0;
2+
werkzeug==2.1.2;
3+
google-cloud-storage==2.1.0;
4+
gunicorn==20.1.0;

0 commit comments

Comments
 (0)