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