From 555d089b78ac4e66290363882ff2c0f5266e0f3c Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 12:51:18 -0800 Subject: [PATCH 1/9] Added Blobstore API with Google Cloud Storage sample --- .../standard/blobstore/{ => api}/README.md | 0 .../standard/blobstore/{ => api}/app.yaml | 0 .../standard/blobstore/{ => api}/main.py | 1 - .../standard/blobstore/{ => api}/main_test.py | 0 appengine/standard/blobstore/gcs/app.yaml | 8 +++ .../blobstore/gcs/appengine_config.py | 18 ++++++ appengine/standard/blobstore/gcs/main.py | 61 +++++++++++++++++++ .../standard/blobstore/gcs/requirements.txt | 1 + 8 files changed, 88 insertions(+), 1 deletion(-) rename appengine/standard/blobstore/{ => api}/README.md (100%) rename appengine/standard/blobstore/{ => api}/app.yaml (100%) rename appengine/standard/blobstore/{ => api}/main.py (99%) rename appengine/standard/blobstore/{ => api}/main_test.py (100%) create mode 100644 appengine/standard/blobstore/gcs/app.yaml create mode 100644 appengine/standard/blobstore/gcs/appengine_config.py create mode 100644 appengine/standard/blobstore/gcs/main.py create mode 100644 appengine/standard/blobstore/gcs/requirements.txt diff --git a/appengine/standard/blobstore/README.md b/appengine/standard/blobstore/api/README.md similarity index 100% rename from appengine/standard/blobstore/README.md rename to appengine/standard/blobstore/api/README.md diff --git a/appengine/standard/blobstore/app.yaml b/appengine/standard/blobstore/api/app.yaml similarity index 100% rename from appengine/standard/blobstore/app.yaml rename to appengine/standard/blobstore/api/app.yaml diff --git a/appengine/standard/blobstore/main.py b/appengine/standard/blobstore/api/main.py similarity index 99% rename from appengine/standard/blobstore/main.py rename to appengine/standard/blobstore/api/main.py index 841cbf1701d..da206aae719 100644 --- a/appengine/standard/blobstore/main.py +++ b/appengine/standard/blobstore/api/main.py @@ -14,7 +14,6 @@ """ Sample application that demonstrates how to use the App Engine Blobstore API. - For more information, see README.md. """ diff --git a/appengine/standard/blobstore/main_test.py b/appengine/standard/blobstore/api/main_test.py similarity index 100% rename from appengine/standard/blobstore/main_test.py rename to appengine/standard/blobstore/api/main_test.py diff --git a/appengine/standard/blobstore/gcs/app.yaml b/appengine/standard/blobstore/gcs/app.yaml new file mode 100644 index 00000000000..636a04bae7e --- /dev/null +++ b/appengine/standard/blobstore/gcs/app.yaml @@ -0,0 +1,8 @@ +runtime: python27 +api_version: 1 +threadsafe: yes + +handlers: +- url: .* + script: main.app + login: required diff --git a/appengine/standard/blobstore/gcs/appengine_config.py b/appengine/standard/blobstore/gcs/appengine_config.py new file mode 100644 index 00000000000..c903d9a0ac5 --- /dev/null +++ b/appengine/standard/blobstore/gcs/appengine_config.py @@ -0,0 +1,18 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add('lib') diff --git a/appengine/standard/blobstore/gcs/main.py b/appengine/standard/blobstore/gcs/main.py new file mode 100644 index 00000000000..1544e1640df --- /dev/null +++ b/appengine/standard/blobstore/gcs/main.py @@ -0,0 +1,61 @@ +"""A sample app that operates on GCS files with blobstore API.""" + +from __future__ import with_statement + +import cloudstorage as gcs +import main +import webapp2 + +from google.appengine.api import app_identity +from google.appengine.ext import blobstore +from google.appengine.ext.webapp import blobstore_handlers + + +def CreateFile(filename): + """Create a GCS file with GCS client lib. + + Args: + filename: GCS filename. + + Returns: + The corresponding string blobkey for this GCS file. + """ + # Create a GCS file with GCS client. + with gcs.open(filename, 'w') as f: + f.write('abcde\n') + + # Blobstore API requires extra /gs to distinguish against blobstore files. + blobstore_filename = '/gs' + filename + # This blob_key works with blobstore APIs that do not expect a + # corresponding BlobInfo in datastore. + return blobstore.create_gs_key(blobstore_filename) + + +class GCSHandler(webapp2.RequestHandler): + + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + default_gcs_bucket_name = app_identity.get_default_gcs_bucket_name() + gcs_filename = '/' + default_gcs_bucket_name + '/blobstore_demo' + blob_key = CreateFile(gcs_filename) + + # Fetch data. + self.response.write('Fetched data %s\n' % + blobstore.fetch_data(blob_key, 0, 2)) + + # Delete files. + blobstore.delete(blob_key) + + +class GCSServingHandler(blobstore_handlers.BlobstoreDownloadHandler): + + def get(self): + default_gcs_bucket = app_identity.get_default_gcs_bucket_name() + gcs_filename = '/' + default_gcs_bucket + '/blobstore_serving_demo' + blob_key = CreateFile(gcs_filename) + self.send_blob(blob_key) + + +app = webapp2.WSGIApplication([('/blobstore/ops', GCSHandler), + ('/blobstore/serve', GCSServingHandler)], + debug=True) diff --git a/appengine/standard/blobstore/gcs/requirements.txt b/appengine/standard/blobstore/gcs/requirements.txt new file mode 100644 index 00000000000..f2ec35f05f9 --- /dev/null +++ b/appengine/standard/blobstore/gcs/requirements.txt @@ -0,0 +1 @@ +GoogleAppEngineCloudStorageClient==1.9.22.1 From 995082c4795c28a270342721d70186b0e9d7cc22 Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 12:53:45 -0800 Subject: [PATCH 2/9] re-added space i accidentally deleted --- appengine/standard/blobstore/api/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/appengine/standard/blobstore/api/main.py b/appengine/standard/blobstore/api/main.py index da206aae719..841cbf1701d 100644 --- a/appengine/standard/blobstore/api/main.py +++ b/appengine/standard/blobstore/api/main.py @@ -14,6 +14,7 @@ """ Sample application that demonstrates how to use the App Engine Blobstore API. + For more information, see README.md. """ From 2d1e959aeff38a12f020f82bc6fd34d9616f455e Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 13:14:56 -0800 Subject: [PATCH 3/9] Fixed linting issues --- appengine/standard/blobstore/gcs/main.py | 62 ++++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/appengine/standard/blobstore/gcs/main.py b/appengine/standard/blobstore/gcs/main.py index 1544e1640df..4451adfa5b1 100644 --- a/appengine/standard/blobstore/gcs/main.py +++ b/appengine/standard/blobstore/gcs/main.py @@ -3,57 +3,57 @@ from __future__ import with_statement import cloudstorage as gcs -import main -import webapp2 from google.appengine.api import app_identity from google.appengine.ext import blobstore from google.appengine.ext.webapp import blobstore_handlers +import webapp2 def CreateFile(filename): - """Create a GCS file with GCS client lib. + """Create a GCS file with GCS client lib. + + Args: + filename: GCS filename. - Args: - filename: GCS filename. + Returns: + The corresponding string blobkey for this GCS file. - Returns: - The corresponding string blobkey for this GCS file. - """ - # Create a GCS file with GCS client. - with gcs.open(filename, 'w') as f: - f.write('abcde\n') + """ + # Create a GCS file with GCS client. + with gcs.open(filename, 'w') as f: + f.write('abcde\n') - # Blobstore API requires extra /gs to distinguish against blobstore files. - blobstore_filename = '/gs' + filename - # This blob_key works with blobstore APIs that do not expect a - # corresponding BlobInfo in datastore. - return blobstore.create_gs_key(blobstore_filename) + # Blobstore API requires extra /gs to distinguish against blobstore files. + blobstore_filename = '/gs' + filename + # This blob_key works with blobstore APIs that do not expect a + # corresponding BlobInfo in datastore. + return blobstore.create_gs_key(blobstore_filename) class GCSHandler(webapp2.RequestHandler): - def get(self): - self.response.headers['Content-Type'] = 'text/plain' - default_gcs_bucket_name = app_identity.get_default_gcs_bucket_name() - gcs_filename = '/' + default_gcs_bucket_name + '/blobstore_demo' - blob_key = CreateFile(gcs_filename) + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + default_gcs_bucket_name = app_identity.get_default_gcs_bucket_name() + gcs_filename = '/' + default_gcs_bucket_name + '/blobstore_demo' + blob_key = CreateFile(gcs_filename) - # Fetch data. - self.response.write('Fetched data %s\n' % - blobstore.fetch_data(blob_key, 0, 2)) + # Fetch data. + self.response.write('Fetched data %s\n' % + blobstore.fetch_data(blob_key, 0, 2)) - # Delete files. - blobstore.delete(blob_key) + # Delete files. + blobstore.delete(blob_key) class GCSServingHandler(blobstore_handlers.BlobstoreDownloadHandler): - def get(self): - default_gcs_bucket = app_identity.get_default_gcs_bucket_name() - gcs_filename = '/' + default_gcs_bucket + '/blobstore_serving_demo' - blob_key = CreateFile(gcs_filename) - self.send_blob(blob_key) + def get(self): + default_gcs_bucket = app_identity.get_default_gcs_bucket_name() + gcs_filename = '/' + default_gcs_bucket + '/blobstore_serving_demo' + blob_key = CreateFile(gcs_filename) + self.send_blob(blob_key) app = webapp2.WSGIApplication([('/blobstore/ops', GCSHandler), From a5a9f277ffceb680b7e3deebf23ae141cb2f15b6 Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 14:19:05 -0800 Subject: [PATCH 4/9] Improved comments and style --- appengine/standard/blobstore/gcs/main.py | 99 ++++++++++++++---------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/appengine/standard/blobstore/gcs/main.py b/appengine/standard/blobstore/gcs/main.py index 4451adfa5b1..1373b292caf 100644 --- a/appengine/standard/blobstore/gcs/main.py +++ b/appengine/standard/blobstore/gcs/main.py @@ -1,61 +1,78 @@ """A sample app that operates on GCS files with blobstore API.""" -from __future__ import with_statement - -import cloudstorage as gcs - +import cloudstorage from google.appengine.api import app_identity from google.appengine.ext import blobstore from google.appengine.ext.webapp import blobstore_handlers import webapp2 -def CreateFile(filename): - """Create a GCS file with GCS client lib. - - Args: - filename: GCS filename. - - Returns: - The corresponding string blobkey for this GCS file. - - """ - # Create a GCS file with GCS client. - with gcs.open(filename, 'w') as f: - f.write('abcde\n') - - # Blobstore API requires extra /gs to distinguish against blobstore files. - blobstore_filename = '/gs' + filename - # This blob_key works with blobstore APIs that do not expect a - # corresponding BlobInfo in datastore. - return blobstore.create_gs_key(blobstore_filename) - - -class GCSHandler(webapp2.RequestHandler): +# This handler creates a file in Cloud Storage using the cloudstorage +# client library and then reads the data back using the Blobstore API. +class CreateAndReadFileHandler(webapp2.RequestHandler): def get(self): + # Get the default Cloud Storage Bucket name and create a file name for + # the object in Cloud Storage. + bucket = app_identity.get_default_gcs_bucket_name() + + # Cloud Storage file names are in the format /bucket/object. + filename = '/{}/blobstore_demo'.format(bucket) + + # Create a file in Google Cloud Storage and write something to it + with cloudstorage.open(filename, 'w') as filehandle: + filehandle.write('abcde\n') + + # In order to read the contents of the file using the Blobstore API, + # you must create a blob_key from the Cloud Storage file name. + # Blobstore expects the filename to be in the format of + # /gs/bucket/object + blobstore_filename = '/gs{}'.format(filename) + blob_key = blobstore.create_gs_key(blobstore_filename) + + # Read the file's contents using the Blobstore API. + # The last two parameters specify the start and end index of bytes we + # want to read. + data = blobstore.fetch_data(blob_key, 0, 6) + + # Write the contents to the response. self.response.headers['Content-Type'] = 'text/plain' - default_gcs_bucket_name = app_identity.get_default_gcs_bucket_name() - gcs_filename = '/' + default_gcs_bucket_name + '/blobstore_demo' - blob_key = CreateFile(gcs_filename) + self.response.write(data) - # Fetch data. - self.response.write('Fetched data %s\n' % - blobstore.fetch_data(blob_key, 0, 2)) - - # Delete files. + # Delete the file from Google Cloud Storage using the blob_key. blobstore.delete(blob_key) -class GCSServingHandler(blobstore_handlers.BlobstoreDownloadHandler): +# This handler creates a file in Cloud Storage using the cloudstorage +# client library and then serves the file back using the Blobstore API. + +class CreateAndServeFileHandler(blobstore_handlers.BlobstoreDownloadHandler): def get(self): - default_gcs_bucket = app_identity.get_default_gcs_bucket_name() - gcs_filename = '/' + default_gcs_bucket + '/blobstore_serving_demo' - blob_key = CreateFile(gcs_filename) + # Get the default Cloud Storage Bucket name and create a file name for + # the object in Cloud Storage. + bucket = app_identity.get_default_gcs_bucket_name() + + # Cloud Storage file names are in the format /bucket/object. + filename = '/{}/blobstore_serving_demo'.format(bucket) + + # Create a file in Google Cloud Storage and write something to it + with cloudstorage.open(filename, 'w') as filehandle: + filehandle.write('abcde\n') + + # In order to read the contents of the file using the Blobstore API, + # you must create a blob_key from the Cloud Storage file name. + # Blobstore expects the filename to be in the format of + # /gs/bucket/object + blobstore_filename = '/gs{}'.format(filename) + blob_key = blobstore.create_gs_key(blobstore_filename) + + # BlobstoreDownloadHandler serves the file from Google Cloud Storage to + # your computer using blob_key self.send_blob(blob_key) -app = webapp2.WSGIApplication([('/blobstore/ops', GCSHandler), - ('/blobstore/serve', GCSServingHandler)], - debug=True) +app = webapp2.WSGIApplication([ + ('/', CreateAndReadFileHandler), + ('/blobstore/read', CreateAndReadFileHandler), + ('/blobstore/serve', CreateAndServeFileHandler)], debug=True) From f57ed6b3381399169c18441b10af67509470ae76 Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 14:35:23 -0800 Subject: [PATCH 5/9] Added create and read test --- appengine/standard/blobstore/gcs/main_test.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 appengine/standard/blobstore/gcs/main_test.py diff --git a/appengine/standard/blobstore/gcs/main_test.py b/appengine/standard/blobstore/gcs/main_test.py new file mode 100644 index 00000000000..834885bf7b3 --- /dev/null +++ b/appengine/standard/blobstore/gcs/main_test.py @@ -0,0 +1,26 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import webtest + +import main + + +def test_app(testbed, login): + app = webtest.TestApp(main.app) + + login() + response = app.get('/') + + assert 'abcde' in response From 89b10b2e628ab9089371edcfba4c58cdb43706a2 Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 14:51:22 -0800 Subject: [PATCH 6/9] Added test for create and serve --- appengine/standard/blobstore/gcs/main_test.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/appengine/standard/blobstore/gcs/main_test.py b/appengine/standard/blobstore/gcs/main_test.py index 834885bf7b3..0d9e848abe7 100644 --- a/appengine/standard/blobstore/gcs/main_test.py +++ b/appengine/standard/blobstore/gcs/main_test.py @@ -17,10 +17,21 @@ import main -def test_app(testbed, login): +def test_create_and_read(testbed, login): app = webtest.TestApp(main.app) login() - response = app.get('/') + response = app.get('/blobstore/read') assert 'abcde' in response + + +def test_create_and_serve(testbed, login): + app = webtest.TestApp(main.app) + + login() + response = app.get('/blobstore/serve') + served_file_header = response.headers['X-AppEngine-BlobKey'] + + assert 'encoded_gs_file' in served_file_header + From 348e1ec9f4f8094056c08007a60f2a76584c5c6a Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 14:56:59 -0800 Subject: [PATCH 7/9] removed logins from tests --- appengine/standard/blobstore/gcs/main_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/appengine/standard/blobstore/gcs/main_test.py b/appengine/standard/blobstore/gcs/main_test.py index 0d9e848abe7..96572e37c11 100644 --- a/appengine/standard/blobstore/gcs/main_test.py +++ b/appengine/standard/blobstore/gcs/main_test.py @@ -20,7 +20,6 @@ def test_create_and_read(testbed, login): app = webtest.TestApp(main.app) - login() response = app.get('/blobstore/read') assert 'abcde' in response @@ -29,7 +28,6 @@ def test_create_and_read(testbed, login): def test_create_and_serve(testbed, login): app = webtest.TestApp(main.app) - login() response = app.get('/blobstore/serve') served_file_header = response.headers['X-AppEngine-BlobKey'] From 694ddd986a3283257dad367133fad28dd3a614d4 Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Fri, 9 Dec 2016 15:02:12 -0800 Subject: [PATCH 8/9] Revised tests and sample based on code review --- appengine/standard/blobstore/gcs/main.py | 12 +++++------- appengine/standard/blobstore/gcs/main_test.py | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/appengine/standard/blobstore/gcs/main.py b/appengine/standard/blobstore/gcs/main.py index 1373b292caf..4ba0816ad90 100644 --- a/appengine/standard/blobstore/gcs/main.py +++ b/appengine/standard/blobstore/gcs/main.py @@ -9,7 +9,6 @@ # This handler creates a file in Cloud Storage using the cloudstorage # client library and then reads the data back using the Blobstore API. - class CreateAndReadFileHandler(webapp2.RequestHandler): def get(self): # Get the default Cloud Storage Bucket name and create a file name for @@ -19,13 +18,13 @@ def get(self): # Cloud Storage file names are in the format /bucket/object. filename = '/{}/blobstore_demo'.format(bucket) - # Create a file in Google Cloud Storage and write something to it + # Create a file in Google Cloud Storage and write something to it. with cloudstorage.open(filename, 'w') as filehandle: filehandle.write('abcde\n') # In order to read the contents of the file using the Blobstore API, # you must create a blob_key from the Cloud Storage file name. - # Blobstore expects the filename to be in the format of + # Blobstore expects the filename to be in the format of: # /gs/bucket/object blobstore_filename = '/gs{}'.format(filename) blob_key = blobstore.create_gs_key(blobstore_filename) @@ -45,7 +44,6 @@ def get(self): # This handler creates a file in Cloud Storage using the cloudstorage # client library and then serves the file back using the Blobstore API. - class CreateAndServeFileHandler(blobstore_handlers.BlobstoreDownloadHandler): def get(self): @@ -56,19 +54,19 @@ def get(self): # Cloud Storage file names are in the format /bucket/object. filename = '/{}/blobstore_serving_demo'.format(bucket) - # Create a file in Google Cloud Storage and write something to it + # Create a file in Google Cloud Storage and write something to it. with cloudstorage.open(filename, 'w') as filehandle: filehandle.write('abcde\n') # In order to read the contents of the file using the Blobstore API, # you must create a blob_key from the Cloud Storage file name. - # Blobstore expects the filename to be in the format of + # Blobstore expects the filename to be in the format of: # /gs/bucket/object blobstore_filename = '/gs{}'.format(filename) blob_key = blobstore.create_gs_key(blobstore_filename) # BlobstoreDownloadHandler serves the file from Google Cloud Storage to - # your computer using blob_key + # your computer using blob_key. self.send_blob(blob_key) diff --git a/appengine/standard/blobstore/gcs/main_test.py b/appengine/standard/blobstore/gcs/main_test.py index 96572e37c11..b5d1c27eb32 100644 --- a/appengine/standard/blobstore/gcs/main_test.py +++ b/appengine/standard/blobstore/gcs/main_test.py @@ -32,4 +32,3 @@ def test_create_and_serve(testbed, login): served_file_header = response.headers['X-AppEngine-BlobKey'] assert 'encoded_gs_file' in served_file_header - From fb81c9fe663001bd278f6cd043719fad7d221f21 Mon Sep 17 00:00:00 2001 From: Ryan Matsumoto Date: Wed, 4 Jan 2017 15:49:22 -0800 Subject: [PATCH 9/9] Added BlobReader code and tests --- .../standard/blobstore/blobreader/app.yaml | 8 +++ .../blobstore/blobreader/appengine_config.py | 18 +++++ .../standard/blobstore/blobreader/main.py | 69 +++++++++++++++++++ .../blobstore/blobreader/main_test.py | 25 +++++++ .../blobstore/blobreader/requirements.txt | 1 + 5 files changed, 121 insertions(+) create mode 100644 appengine/standard/blobstore/blobreader/app.yaml create mode 100644 appengine/standard/blobstore/blobreader/appengine_config.py create mode 100644 appengine/standard/blobstore/blobreader/main.py create mode 100644 appengine/standard/blobstore/blobreader/main_test.py create mode 100644 appengine/standard/blobstore/blobreader/requirements.txt diff --git a/appengine/standard/blobstore/blobreader/app.yaml b/appengine/standard/blobstore/blobreader/app.yaml new file mode 100644 index 00000000000..636a04bae7e --- /dev/null +++ b/appengine/standard/blobstore/blobreader/app.yaml @@ -0,0 +1,8 @@ +runtime: python27 +api_version: 1 +threadsafe: yes + +handlers: +- url: .* + script: main.app + login: required diff --git a/appengine/standard/blobstore/blobreader/appengine_config.py b/appengine/standard/blobstore/blobreader/appengine_config.py new file mode 100644 index 00000000000..c903d9a0ac5 --- /dev/null +++ b/appengine/standard/blobstore/blobreader/appengine_config.py @@ -0,0 +1,18 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add('lib') diff --git a/appengine/standard/blobstore/blobreader/main.py b/appengine/standard/blobstore/blobreader/main.py new file mode 100644 index 00000000000..b9e2c5f4467 --- /dev/null +++ b/appengine/standard/blobstore/blobreader/main.py @@ -0,0 +1,69 @@ +"""A sample app that operates on GCS files with blobstore API's BlobReader.""" + +import cloudstorage +from google.appengine.api import app_identity +from google.appengine.ext import blobstore +import webapp2 + + +class BlobreaderHandler(webapp2.RequestHandler): + def get(self): + # Get the default Cloud Storage Bucket name and create a file name for + # the object in Cloud Storage. + bucket = app_identity.get_default_gcs_bucket_name() + + # Cloud Storage file names are in the format /bucket/object. + filename = '/{}/blobreader_demo'.format(bucket) + + # Create a file in Google Cloud Storage and write something to it. + with cloudstorage.open(filename, 'w') as filehandle: + filehandle.write('abcde\n') + + # In order to read the contents of the file using the Blobstore API, + # you must create a blob_key from the Cloud Storage file name. + # Blobstore expects the filename to be in the format of: + # /gs/bucket/object + blobstore_filename = '/gs{}'.format(filename) + blob_key = blobstore.create_gs_key(blobstore_filename) + + # [START blob_reader] + # Instantiate a BlobReader for a given Blobstore blob_key. + blob_reader = blobstore.BlobReader(blob_key) + + # Instantiate a BlobReader for a given Blobstore blob_key, setting the + # buffer size to 1 MB. + blob_reader = blobstore.BlobReader(blob_key, buffer_size=1048576) + + # Instantiate a BlobReader for a given Blobstore blob_key, setting the + # initial read position. + blob_reader = blobstore.BlobReader(blob_key, position=0) + + # Read the entire value into memory. This may take a while depending + # on the size of the value and the size of the read buffer, and is not + # recommended for large values. + blob_reader_data = blob_reader.read() + + # Write the contents to the response. + self.response.headers['Content-Type'] = 'text/plain' + self.response.write(blob_reader_data) + + # Set the read position back to 0, then read and write 3 bytes. + blob_reader.seek(0) + blob_reader_data = blob_reader.read(3) + self.response.write(blob_reader_data) + self.response.write('\n') + + # Set the read position back to 0, then read and write one line (up to + # and including a '\n' character) at a time. + blob_reader.seek(0) + for line in blob_reader: + self.response.write(line) + # [END blob_reader] + + # Delete the file from Google Cloud Storage using the blob_key. + blobstore.delete(blob_key) + + +app = webapp2.WSGIApplication([ + ('/', BlobreaderHandler), + ('/blobreader', BlobreaderHandler)], debug=True) diff --git a/appengine/standard/blobstore/blobreader/main_test.py b/appengine/standard/blobstore/blobreader/main_test.py new file mode 100644 index 00000000000..b0cc5898a42 --- /dev/null +++ b/appengine/standard/blobstore/blobreader/main_test.py @@ -0,0 +1,25 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import webtest + +import main + + +def test_blobreader(testbed, login): + app = webtest.TestApp(main.app) + + response = app.get('/blobreader') + + assert 'abcde\nabc\nabcde\n' in response diff --git a/appengine/standard/blobstore/blobreader/requirements.txt b/appengine/standard/blobstore/blobreader/requirements.txt new file mode 100644 index 00000000000..f2ec35f05f9 --- /dev/null +++ b/appengine/standard/blobstore/blobreader/requirements.txt @@ -0,0 +1 @@ +GoogleAppEngineCloudStorageClient==1.9.22.1