From d667c9d1052d6ddc171a7a0753eff934d2da83bb Mon Sep 17 00:00:00 2001 From: Joe Gregorio Date: Mon, 17 Oct 2011 13:25:24 -0400 Subject: [PATCH] Move to using setuptools exclusively. Take the opportunity to clean up contrib/ as all that functionality has been subsumed in the oauth2decorator. Reviwed in http://codereview.appspot.com/5271053/ --- bin/enable-app-engine-project | 9 +- contrib/buzz/buzz_appengine.py | 173 ------------------- contrib/buzz/simple_wrapper.py | 82 --------- contrib_tests/buzz/test_simple_wrapper.py | 118 ------------- contrib_tests/test_account.oacurl.properties | 6 - expand-symlinks.py | 22 ++- setup.py | 34 ++-- setup_oauth2client.py | 28 ++- setup_utils.py | 52 ------ 9 files changed, 51 insertions(+), 473 deletions(-) delete mode 100644 contrib/buzz/buzz_appengine.py delete mode 100644 contrib/buzz/simple_wrapper.py delete mode 100644 contrib_tests/buzz/test_simple_wrapper.py delete mode 100644 contrib_tests/test_account.oacurl.properties delete mode 100644 setup_utils.py diff --git a/bin/enable-app-engine-project b/bin/enable-app-engine-project index b67cde8..e2f2a35 100755 --- a/bin/enable-app-engine-project +++ b/bin/enable-app-engine-project @@ -30,6 +30,7 @@ import gflags import logging import sys import os +import pkg_resources from distutils.dir_util import copy_tree from distutils.file_util import copy_file @@ -72,13 +73,13 @@ def find_source(module): basename = os.path.basename(m.__file__) if basename.startswith('__init__.'): isdir = True - location = os.path.dirname(m.__file__) + location = os.path.dirname( + pkg_resources.resource_filename(module, '__init__.py')) else: if os.path.isfile(m.__file__): location = m.__file__.rsplit('.', 1)[0] + '.py' else: # The file is an egg, extract to a temporary location - import pkg_resources location = pkg_resources.resource_filename(module, module + '.py') return (isdir, location) @@ -103,8 +104,8 @@ def main(argv): # Check if the supplied directory is an App Engine project by looking # for an app.yaml if not os.path.isfile(os.path.join(dir, 'app.yaml')): - sys.exit('The given directory is not a Google App Engine project: %s' % dir) - + sys.exit('The given directory is not a Google App Engine project: %s' % + dir) # Build up the set of file or directory copying actions we need to do action = [] # (src, dst, isdir) diff --git a/contrib/buzz/buzz_appengine.py b/contrib/buzz/buzz_appengine.py deleted file mode 100644 index 08ed864..0000000 --- a/contrib/buzz/buzz_appengine.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright (C) 2010 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.api import users -from google.appengine.ext import db - -import apiclient.ext.appengine -import logging -import simple_wrapper - - -class Flow(db.Model): - flow = apiclient.ext.appengine.FlowThreeLeggedProperty() - - -class Credentials(db.Model): - credentials = apiclient.ext.appengine.OAuthCredentialsProperty() - - -class oauth_required(object): - def __init__(self, *decorator_args, **decorator_kwargs): - """A decorator to require that a user has gone through the OAuth dance before accessing a handler. - - To use it, decorate your get() method like this: - @oauth_required - def get(self): - buzz_wrapper = oauth_handlers.build_buzz_wrapper_for_current_user() - user_profile_data = buzz_wrapper.get_profile() - self.response.out.write('Hello, ' + user_profile_data.displayName) - - We will redirect the user to the OAuth endpoint and afterwards the OAuth - will send the user back to the DanceFinishingHandler that you have configured. - - This should only used for GET requests since any payload in a POST request - will be lost. Any parameters in the original URL will be preserved. - """ - self.decorator_args = decorator_args - self.decorator_kwargs = decorator_kwargs - - def __load_settings_from_file__(self): - # Load settings from settings.py module if it's available - # Only return the keys that the user has explicitly set - try: - import settings - - # This uses getattr so that the user can set just the parameters they care about - flow_settings = { - 'consumer_key' : getattr(settings, 'CONSUMER_KEY', None), - 'consumer_secret' : getattr(settings, 'CONSUMER_SECRET', None), - 'user_agent' : getattr(settings, 'USER_AGENT', None), - 'domain' : getattr(settings, 'DOMAIN', None), - 'scope' : getattr(settings, 'SCOPE', None), - 'xoauth_display_name' : getattr(settings, 'XOAUTH_DISPLAY_NAME', None) - } - - # Strip out all the keys that weren't specified in the settings.py - # This is needed to ensure that those keys don't override what's - # specified in the decorator invocation - cleaned_flow_settings = {} - for key,value in flow_settings.items(): - if value is not None: - cleaned_flow_settings[key] = value - - return cleaned_flow_settings - except ImportError: - return {} - - def __load_settings__(self): - # Set up the default arguments and override them with whatever values have been given to the decorator - flow_settings = { - 'consumer_key' : 'anonymous', - 'consumer_secret' : 'anonymous', - 'user_agent' : 'google-api-client-python-buzz-webapp/1.0', - 'domain' : 'anonymous', - 'scope' : 'https://www.googleapis.com/auth/buzz', - 'xoauth_display_name' : 'Default Display Name For OAuth Application' - } - logging.info('OAuth settings: %s ' % flow_settings) - - # Override the defaults with whatever the user may have put into settings.py - settings_kwargs = self.__load_settings_from_file__() - flow_settings.update(settings_kwargs) - logging.info('OAuth settings: %s ' % flow_settings) - - # Override the defaults with whatever the user have specified in the decorator's invocation - flow_settings.update(self.decorator_kwargs) - logging.info('OAuth settings: %s ' % flow_settings) - return flow_settings - - def __call__(self, handler_method): - def check_oauth_credentials_wrapper(*args, **kwargs): - handler_instance = args[0] - # TODO(ade) Add support for POST requests - if handler_instance.request.method != 'GET': - raise webapp.Error('The check_oauth decorator can only be used for GET ' - 'requests') - - # Is this a request from the OAuth system after finishing the OAuth dance? - if handler_instance.request.get('oauth_verifier'): - user = users.get_current_user() - logging.debug('Finished OAuth dance for: %s' % user.email()) - - f = Flow.get_by_key_name(user.user_id()) - if f: - credentials = f.flow.step2_exchange(handler_instance.request.params) - c = Credentials(key_name=user.user_id(), credentials=credentials) - c.put() - - # We delete the flow so that a malicious actor can't pretend to be the OAuth service - # and replace a valid token with an invalid token - f.delete() - - handler_method(*args) - return - - # Find out who the user is. If we don't know who you are then we can't - # look up your OAuth credentials thus we must ensure the user is logged in. - user = users.get_current_user() - if not user: - handler_instance.redirect(users.create_login_url(handler_instance.request.uri)) - return - - # Now that we know who the user is look up their OAuth credentials - # if we don't find the credentials then send them through the OAuth dance - if not Credentials.get_by_key_name(user.user_id()): - flow_settings = self.__load_settings__() - - p = apiclient.discovery.build("buzz", "v1") - flow = apiclient.oauth.FlowThreeLegged(p.auth_discovery(), - consumer_key=flow_settings['consumer_key'], - consumer_secret=flow_settings['consumer_secret'], - user_agent=flow_settings['user_agent'], - domain=flow_settings['domain'], - scope=flow_settings['scope'], - xoauth_displayname=flow_settings['xoauth_display_name']) - - # The OAuth system needs to send the user right back here so that they - # get to the page they originally intended to visit. - oauth_return_url = handler_instance.request.uri - authorize_url = flow.step1_get_authorize_url(oauth_return_url) - - f = Flow(key_name=user.user_id(), flow=flow) - f.put() - - handler_instance.redirect(authorize_url) - return - - # If the user already has a token then call the wrapped handler - handler_method(*args) - return check_oauth_credentials_wrapper - -def build_buzz_wrapper_for_current_user(api_key=None): - user = users.get_current_user() - credentials = Credentials.get_by_key_name(user.user_id()).credentials - if not api_key: - try: - import settings - api_key = getattr(settings, 'API_KEY', None) - except ImportError: - return {} - return simple_wrapper.SimpleWrapper(api_key=api_key, - credentials=credentials) \ No newline at end of file diff --git a/contrib/buzz/simple_wrapper.py b/contrib/buzz/simple_wrapper.py deleted file mode 100644 index 49c6038..0000000 --- a/contrib/buzz/simple_wrapper.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/python2.4 -# -# Copyright (C) 2010 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. - -__author__ = 'ade@google.com (Ade Oshineye)' - -import apiclient.discovery -import httplib2 -import logging - -class SimpleWrapper(object): - "Simple client that exposes the bare minimum set of common Buzz operations" - - def __init__(self, api_key=None, credentials=None): - self.http = httplib2.Http() - if credentials: - logging.debug('Using api_client with credentials') - self.http = credentials.authorize(self.http) - self.api_client = apiclient.discovery.build('buzz', 'v1', http=self.http, developerKey=api_key) - else: - logging.debug('Using api_client that doesn\'t have credentials') - self.api_client = apiclient.discovery.build('buzz', 'v1', http=self.http, developerKey=api_key) - - def search(self, query, user_token=None, max_results=10): - if query is None or query.strip() is '': - return None - - json = self.api_client.activities().search(q=query, max_results=max_results).execute() - if json.has_key('items'): - return json['items'] - return [] - - def post(self, message_body, user_id='@me'): - if message_body is None or message_body.strip() is '': - return None - - activities = self.api_client.activities() - logging.info('Retrieved activities for: %s' % user_id) - activity = activities.insert(userId=user_id, body={ - 'data' : { - 'title': message_body, - 'object': { - 'content': message_body, - 'type': 'note'} - } - } - ).execute() - url = activity['links']['alternate'][0]['href'] - logging.info('Just created: %s' % url) - return url - - def get_profile(self, user_id='@me'): - user_profile_data = self.api_client.people().get(userId=user_id).execute() - return user_profile_data - - def get_follower_count(self, user_id='@me'): - return self.__get_group_count(user_id, '@followers') - - def get_following_count(self, user_id='@me'): - return self.__get_group_count(user_id, '@following') - - def __get_group_count(self, user_id, group_id): - # Fetching 0 results is a performance optimisation that minimises the - # amount of data that's getting retrieved from the server - cmd = self.api_client.people().list(userId=user_id, groupId=group_id, - max_results=0) - members = cmd.execute() - if 'totalResults' not in members.keys(): - return -1 - return members['totalResults'] diff --git a/contrib_tests/buzz/test_simple_wrapper.py b/contrib_tests/buzz/test_simple_wrapper.py deleted file mode 100644 index 29bf67b..0000000 --- a/contrib_tests/buzz/test_simple_wrapper.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/python2.4 -# -# Copyright 2010 Google Inc. All Rights Reserved. - -__author__ = 'ade@google.com (Ade Oshineye)' - -from contrib.buzz.simple_wrapper import SimpleWrapper - -import apiclient.oauth -import httplib2 -import logging -import oauth2 as oauth -import os -import pickle -import unittest - -class SimpleWrapperTest(unittest.TestCase): -# None of these tests make a remote call. We assume the underlying libraries -# and servers are working. - - def test_wrapper_rejects_empty_post(self): - wrapper = SimpleWrapper() - self.assertEquals(None, wrapper.post('', '108242092577082601423')) - - def test_wrapper_rejects_post_containing_only_whitespace(self): - wrapper = SimpleWrapper() - self.assertEquals(None, wrapper.post(' ', '108242092577082601423')) - - def test_wrapper_rejects_none_post(self): - wrapper = SimpleWrapper() - self.assertEquals(None, wrapper.post(None, '108242092577082601423')) - - def test_wrapper_rejects_empty_search(self): - wrapper = SimpleWrapper() - self.assertEquals(None, wrapper.search('')) - - def test_wrapper_rejects_search_containing_only_whitespace(self): - wrapper = SimpleWrapper() - self.assertEquals(None, wrapper.search(' ')) - - def test_wrapper_rejects_search_with_none(self): - wrapper = SimpleWrapper() - self.assertEquals(None, wrapper.search(None)) - - def test_wrapper_returns_minus_one_for_hidden_follower_count(self): - wrapper = SimpleWrapper() - self.assertEquals(-1, wrapper.get_follower_count(user_id='108242092577082601423')) - - def test_wrapper_returns_positive_value_for_visible_follower_count(self): - wrapper = SimpleWrapper() - count = wrapper.get_follower_count(user_id='googlebuzz') - self.assertTrue(count > 0, "Got %s instead" % count) - - def test_wrapper_returns_minus_one_for_hidden_following_count(self): - wrapper = SimpleWrapper() - self.assertEquals(-1, wrapper.get_following_count(user_id='108242092577082601423')) - - def test_wrapper_returns_positive_value_for_visible_following_count(self): - wrapper = SimpleWrapper() - count = wrapper.get_following_count(user_id='googlebuzz') - self.assertTrue(count > 0, "Got %s instead" % count) - -class SimpleWrapperRemoteTest(unittest.TestCase): - # These tests make remote calls - def __init__(self, method_name): - unittest.TestCase.__init__(self, method_name) - oauth_params_dict = {} - for line in open('./contrib_tests/test_account.oacurl.properties'): - line = line.strip() - if line.startswith('#'): - continue - key,value = line.split('=') - oauth_params_dict[key.strip()] = value.strip() - - consumer = oauth.Consumer(oauth_params_dict['consumerKey'], - oauth_params_dict['consumerSecret']) - token = oauth.Token(oauth_params_dict['accessToken'], - oauth_params_dict['accessTokenSecret']) - user_agent = 'google-api-client-python-buzz-webapp/1.0' - credentials = apiclient.oauth.OAuthCredentials(consumer, token, user_agent) - self.wrapper = SimpleWrapper(credentials=credentials) - - def test_searching_returns_results(self): - results = self.wrapper.search('oshineye') - self.assertTrue(results is not None) - - def test_searching_honours_max_results(self): - max = 5 - results = self.wrapper.search('oshineye', max_results=max) - self.assertEquals(max, len(results)) - - def test_can_fetch_profile(self): - profile = self.wrapper.get_profile('googlebuzz') - self.assertTrue(profile is not None) - - profile = self.wrapper.get_profile(user_id='adewale') - self.assertTrue(profile is not None) - - def test_can_post_without_user_id(self): - url = self.wrapper.post('test message') - self.assertTrue(url is not None) - self.assertTrue(url.startswith('https://profiles.google.com/'), url) - - def test_can_post_with_user_id(self): - url = self.wrapper.post('test message', '108242092577082601423') - self.assertTrue(url is not None) - self.assertTrue(url.startswith('https://profiles.google.com/'), url) - - def test_wrapper_returns_positive_value_for_hidden_follower_count_when_authorised(self): - count = self.wrapper.get_follower_count(user_id='108242092577082601423') - self.assertTrue(count > 0, "Got %s instead" % count) - - def test_wrapper_returns_positive_value_for_hidden_following_count_when_authorised(self): - count = self.wrapper.get_following_count(user_id='108242092577082601423') - self.assertTrue(count > 0, "Got %s instead" % count) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/contrib_tests/test_account.oacurl.properties b/contrib_tests/test_account.oacurl.properties deleted file mode 100644 index feadc7c..0000000 --- a/contrib_tests/test_account.oacurl.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Mon Oct 04 23:45:49 PDT 2010 -#A set of credentials for posting as http://www.google.com/profiles/108242092577082601423 -consumerSecret=anonymous -accessToken=1/80QKKG4CbMwOZjmW1udam-fVaiUOY1zO-8u3dhiLK6g -consumerKey=anonymous -accessTokenSecret=R6CnehJTZf9aKuSMtgkmX7KZ diff --git a/expand-symlinks.py b/expand-symlinks.py index 73d2bdd..39b2c21 100644 --- a/expand-symlinks.py +++ b/expand-symlinks.py @@ -18,7 +18,7 @@ """Copy files from source to dest expanding symlinks along the way. """ -from distutils.dir_util import copy_tree +from shutil import copytree import gflags import sys @@ -26,10 +26,27 @@ import sys FLAGS = gflags.FLAGS +# Ignore these files and directories when copying over files into the snapshot. +IGNORE = set(['.hg', 'httplib2', 'oauth2', 'simplejson', 'static', 'gflags.py', + 'gflags_validators.py']) + +# In addition to the above files also ignore these files and directories when +# copying over samples into the snapshot. +IGNORE_IN_SAMPLES = set(['apiclient', 'oauth2client', 'uritemplate']) + + gflags.DEFINE_string('source', '.', 'Directory name to copy from.') gflags.DEFINE_string('dest', 'snapshot', 'Directory name to copy to.') +def _ignore(path, names): + retval = set() + if path != '.': + retval = retval.union(IGNORE_IN_SAMPLES.intersection(names)) + retval = retval.union(IGNORE.intersection(names)) + return retval + + def main(argv): # Let the gflags module process the command-line arguments try: @@ -38,7 +55,8 @@ def main(argv): print '%s\\nUsage: %s ARGS\\n%s' % (e, argv[0], FLAGS) sys.exit(1) - copy_tree(FLAGS.source, FLAGS.dest, verbose=True) + copytree(FLAGS.source, FLAGS.dest, symlinks=True, + ignore=_ignore) if __name__ == '__main__': diff --git a/setup.py b/setup.py index c68bc68..abe8efc 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,6 @@ Also installs included versions of third party libraries, if those libraries are not already installed. """ -import setup_utils - from setuptools import setup packages = [ @@ -30,30 +28,28 @@ packages = [ 'apiclient.contrib.latitude', 'apiclient.contrib.moderator', 'uritemplate', -] + ] -install_requires = [] -py_modules = [] +install_requires = [ + 'httplib2', + 'oauth2', + 'python-gflags', + ] +try: + import json + needs_json = False +except ImportError: + needs_json = True -# (module to test for, install_requires to add if missing, packages to add if missing, py_modules to add if missing) -REQUIREMENTS = [ - ('httplib2', 'httplib2', 'httplib2', None), - ('oauth2', 'oauth2', 'oauth2', None), - ('gflags', 'python-gflags', None, ['gflags', 'gflags_validators']), - (['json', 'simplejson', 'django.utils'], 'simplejson', 'simplejson', None) -] - -for import_name, requires, package, modules in REQUIREMENTS: - if setup_utils.is_missing(import_name): - install_requires.append(requires) - +if needs_json: + install_requires.append('simplejson') long_desc = """The Google API Client for Python is a client library for accessing the Buzz, Moderator, and Latitude APIs.""" setup(name="google-api-python-client", - version="1.0beta4", + version="1.0beta5prerelease", description="Google API Client Library for Python", long_description=long_desc, author="Joe Gregorio", @@ -61,11 +57,9 @@ setup(name="google-api-python-client", url="http://code.google.com/p/google-api-python-client/", install_requires=install_requires, packages=packages, - py_modules=py_modules, package_data={ 'apiclient': ['contrib/*/*.json'] }, - scripts=['bin/enable-app-engine-project'], license="Apache 2.0", keywords="google api client", classifiers=['Development Status :: 4 - Beta', diff --git a/setup_oauth2client.py b/setup_oauth2client.py index e3f105e..e5f6a92 100644 --- a/setup_oauth2client.py +++ b/setup_oauth2client.py @@ -17,34 +17,30 @@ Also installs included versions of third party libraries, if those libraries are not already installed. """ -import setup_utils - from setuptools import setup packages = [ 'oauth2client', ] -install_requires = [] -py_modules = [] +install_requires = [ + 'httplib2', + 'python-gflags', + ] +try: + import json + needs_json = False +except ImportError + needs_json = True -# (module to test for, install_requires to add if missing, packages to add if missing, py_modules to add if missing) -REQUIREMENTS = [ - ('httplib2', 'httplib2', 'httplib2', None), - ('gflags', 'python-gflags', None, ['gflags', 'gflags_validators']), - (['json', 'simplejson', 'django.utils'], 'simplejson', 'simplejson', None) -] - -for import_name, requires, package, modules in REQUIREMENTS: - if setup_utils.is_missing(import_name): - install_requires.append(requires) - +if needs_json: + install_requires.append('simplejson') long_desc = """The oauth2client is a client library for OAuth 2.0.""" setup(name="oauth2client", - version="1.0beta4", + version="1.0beta5prerelease", description="OAuth 2.0 client library", long_description=long_desc, author="Joe Gregorio", diff --git a/setup_utils.py b/setup_utils.py deleted file mode 100644 index cc8b1c9..0000000 --- a/setup_utils.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2010 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. - -"""Utility functions for setup.py file(s).""" - - -__author__ = 'tom.h.miller@gmail.com (Tom Miller)' - -import sys - - -def is_missing(packages): - """Return True if a package can't be imported.""" - - retval = True - sys_path_original = sys.path[:] - # Remove the current directory from the list of paths to check when - # importing modules. - try: - # Sometimes it's represented by an empty string? - sys.path.remove('') - except ValueError: - import os.path - try: - sys.path.remove(os.path.abspath(os.path.curdir)) - except ValueError: - pass - if not isinstance(packages, type([])): - packages = [packages] - for name in packages: - try: - __import__(name) - retval = False - except ImportError: - retval = True - if retval == False: - break - - sys.path = sys_path_original - - return retval