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/
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 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 @@
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 @@
# 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 @@
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 @@
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 @@
'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 @@
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