From 1aed5cc79f82ecac3a3e5e526810a9ab97d72167 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 24 Sep 2015 14:43:37 -0700 Subject: [PATCH] Bringing App Engine storage sample up to standards --- appengine/bigquery/.gitignore | 1 + appengine/storage/.gitignore | 1 + appengine/storage/app.yaml | 12 --- appengine/storage/appengine_config.py | 4 + appengine/storage/index.html | 16 ---- appengine/storage/listing.html | 21 ----- appengine/storage/main.py | 115 +++++--------------------- appengine/storage/main_test.py | 35 ++++++++ appengine/storage/requirements.txt | 1 + 9 files changed, 64 insertions(+), 142 deletions(-) create mode 100644 appengine/bigquery/.gitignore create mode 100644 appengine/storage/.gitignore create mode 100644 appengine/storage/appengine_config.py delete mode 100644 appengine/storage/index.html delete mode 100644 appengine/storage/listing.html create mode 100644 appengine/storage/main_test.py create mode 100644 appengine/storage/requirements.txt diff --git a/appengine/bigquery/.gitignore b/appengine/bigquery/.gitignore new file mode 100644 index 00000000000..a65b41774ad --- /dev/null +++ b/appengine/bigquery/.gitignore @@ -0,0 +1 @@ +lib diff --git a/appengine/storage/.gitignore b/appengine/storage/.gitignore new file mode 100644 index 00000000000..a65b41774ad --- /dev/null +++ b/appengine/storage/.gitignore @@ -0,0 +1 @@ +lib diff --git a/appengine/storage/app.yaml b/appengine/storage/app.yaml index bf628eccb76..42ad35ed2a8 100644 --- a/appengine/storage/app.yaml +++ b/appengine/storage/app.yaml @@ -3,17 +3,5 @@ threadsafe: yes api_version: 1 handlers: -- url: / - static_files: index.html - upload: index.html - -- url: /favicon.ico - static_files: favicon.ico - upload: favicon.ico - - url: .* script: main.app - -libraries: -- name: jinja2 - version: latest diff --git a/appengine/storage/appengine_config.py b/appengine/storage/appengine_config.py new file mode 100644 index 00000000000..4fdc5d2b60f --- /dev/null +++ b/appengine/storage/appengine_config.py @@ -0,0 +1,4 @@ +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add('lib') diff --git a/appengine/storage/index.html b/appengine/storage/index.html deleted file mode 100644 index 267aaf1e0ec..00000000000 --- a/appengine/storage/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - -

Google Cloud Storage Bucket Lister

- bucket name: - - - - diff --git a/appengine/storage/listing.html b/appengine/storage/listing.html deleted file mode 100644 index 6c7f9614c0f..00000000000 --- a/appengine/storage/listing.html +++ /dev/null @@ -1,21 +0,0 @@ - - -

Google Cloud Storage Content Listing for Bucket {{ bucket_name }}

- - - - - - - - {% for obj in items %} - - - - - - - {% endfor %} -
Object NameModification TimeHashSize
{{ obj['name'] }}{{ obj['media']['timeCreated'] }}{{ obj['media']['hash'] }}{{ obj['media']['length'] }}
- - diff --git a/appengine/storage/main.py b/appengine/storage/main.py index 3f3471f997f..e952878ced8 100644 --- a/appengine/storage/main.py +++ b/appengine/storage/main.py @@ -14,109 +14,38 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Present formatted listings for Google Cloud Storage buckets. -This Google App Engine application takes a bucket name in the URL path and uses -the Google Cloud Storage JSON API and Google's Python client library to list -the bucket's contents. -For example, if this app is invoked with the URI -http://bucket-list.appspot.com/foo, it would extract the bucket name 'foo' and -issue a request to GCS for its contents. The app formats the listing into an -XML document, which is prepended with a reference to an XSLT style sheet for -human readable presentation. -For more information: -Google APIs Client Library for Python: - -Google Cloud Storage JSON API: - -Using OAuth 2.0 for Server to Server Applications: - -App Identity Python API Overview: - """ +Sample Google App Engine application that lists the objects in a Google Cloud +Storage bucket. -import os +For more information about Cloud Storage, see README.md in /storage. +For more information about Google App Engine, see README.md in /appengine. +""" -from apiclient.discovery import build as build_service -from google.appengine.ext import webapp -from google.appengine.ext.webapp.util import login_required -import httplib2 -import jinja2 -from oauth2client.client import OAuth2WebServerFlow +import json -# NOTE: You must provide a client ID and secret with access to the GCS JSON -# API. -# You can acquire a client ID and secret from the Google Developers Console. -# -CLIENT_ID = '' -CLIENT_SECRET = '' -SCOPE = 'https://www.googleapis.com/auth/devstorage.read_only' -USER_AGENT = 'app-engine-bucket-lister' +from googleapiclient import discovery +from oauth2client.client import GoogleCredentials +import webapp2 -# Since we don't plan to use all object attributes, we pass a fields argument -# to specify what the server should return. -FIELDS = 'items(name,media(timeCreated,hash,length))' +# The bucket that will be used to list objects. +BUCKET_NAME = '' -def GetBucketName(path): - bucket = path[1:] # Trim the preceding slash - if bucket[-1] == '/': - # Trim final slash, if necessary. - bucket = bucket[:-1] - return bucket +credentials = GoogleCredentials.get_application_default() +storage = discovery.build('storage', 'v1', credentials=credentials) -class MainHandler(webapp.RequestHandler): - @login_required +class MainPage(webapp2.RequestHandler): def get(self): - callback = self.request.host_url + '/oauth2callback' - flow = OAuth2WebServerFlow( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - redirect_uri=callback, - access_type='online', - scope=SCOPE, - user_agent=USER_AGENT) - - bucket = GetBucketName(self.request.path) - step2_url = flow.step1_get_authorize_url() - # Add state to remember which bucket to list. - self.redirect(step2_url + '&state=%s' % bucket) - - -class AuthHandler(webapp.RequestHandler): - @login_required - def get(self): - callback = self.request.host_url + '/oauth2callback' - flow = OAuth2WebServerFlow( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - redirect_uri=callback, - scope=SCOPE, - user_agent=USER_AGENT) - - # Exchange the code (in self.request.params) for an access token. - credentials = flow.step2_exchange(self.request.params) - http = credentials.authorize(httplib2.Http()) - - bucket = self.request.get('state') - storage = build_service('storage', 'v1beta1', http=http) - list_response = storage.objects().list(bucket=bucket, - fields=FIELDS).execute() - template_values = { - 'items': list_response['items'], 'bucket_name': bucket} + response = storage.objects().list(bucket=BUCKET_NAME).execute() - # We use a templating engine to format our output. For more - # information: - # - jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) - template = jinja_env.get_template('listing.html') - self.response.out.write(template.render(template_values)) + self.response.write( + '

Objects.list raw response:

' + '
{}
'.format( + json.dumps(response, sort_keys=True, indent=2))) -app = webapp.WSGIApplication( - [ - ('/oauth2callback', AuthHandler), - ('/..*', MainHandler) - ], - debug=True) +app = webapp2.WSGIApplication([ + ('/', MainPage) +], debug=True) diff --git a/appengine/storage/main_test.py b/appengine/storage/main_test.py new file mode 100644 index 00000000000..8648b8a1e6b --- /dev/null +++ b/appengine/storage/main_test.py @@ -0,0 +1,35 @@ +# Copyright 2015 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 re + +import tests +import webtest + +from . import main + + +class TestStorageSample(tests.AppEngineTestbedCase): + + def setUp(self): + super(TestStorageSample, self).setUp() + self.app = webtest.TestApp(main.app) + main.BUCKET_NAME = self.bucket_name + + def test_get(self): + response = self.app.get('/') + + self.assertEqual(response.status_int, 200) + self.assertRegexpMatches( + response.body, + re.compile(r'.*.*items.*etag.*', re.DOTALL)) diff --git a/appengine/storage/requirements.txt b/appengine/storage/requirements.txt new file mode 100644 index 00000000000..d8055e00cd9 --- /dev/null +++ b/appengine/storage/requirements.txt @@ -0,0 +1 @@ +google-api-python-client